Warning: file_get_contents(https://raw.githubusercontent.com/Den1xxx/Filemanager/master/languages/ru.json): failed to open stream: HTTP request failed! HTTP/1.1 404 Not Found in /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php on line 88

Warning: Cannot modify header information - headers already sent by (output started at /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php:88) in /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php on line 215

Warning: Cannot modify header information - headers already sent by (output started at /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php:88) in /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php on line 216

Warning: Cannot modify header information - headers already sent by (output started at /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php:88) in /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php on line 217

Warning: Cannot modify header information - headers already sent by (output started at /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php:88) in /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php on line 218

Warning: Cannot modify header information - headers already sent by (output started at /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php:88) in /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php on line 219

Warning: Cannot modify header information - headers already sent by (output started at /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php:88) in /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php on line 220
PK!|@&& version.rbnu[module Reline VERSION = '0.6.0' end PK!n  key_stroke.rbnu[class Reline::KeyStroke ESC_BYTE = 27 CSI_PARAMETER_BYTES_RANGE = 0x30..0x3f CSI_INTERMEDIATE_BYTES_RANGE = (0x20..0x2f) attr_accessor :encoding def initialize(config, encoding) @config = config @encoding = encoding end # Input exactly matches to a key sequence MATCHING = :matching # Input partially matches to a key sequence MATCHED = :matched # Input matches to a key sequence and the key sequence is a prefix of another key sequence MATCHING_MATCHED = :matching_matched # Input does not match to any key sequence UNMATCHED = :unmatched def match_status(input) matching = key_mapping.matching?(input) matched = key_mapping.get(input) if matching && matched MATCHING_MATCHED elsif matching MATCHING elsif matched MATCHED elsif input[0] == ESC_BYTE match_unknown_escape_sequence(input, vi_mode: @config.editing_mode_is?(:vi_insert, :vi_command)) else s = input.pack('c*').force_encoding(@encoding) if s.valid_encoding? s.size == 1 ? MATCHED : UNMATCHED else # Invalid string is MATCHING (part of valid string) or MATCHED (invalid bytes to be ignored) MATCHING_MATCHED end end end def expand(input) matched_bytes = nil (1..input.size).each do |i| bytes = input.take(i) status = match_status(bytes) matched_bytes = bytes if status == MATCHED || status == MATCHING_MATCHED break if status == MATCHED || status == UNMATCHED end return [[], []] unless matched_bytes func = key_mapping.get(matched_bytes) s = matched_bytes.pack('c*').force_encoding(@encoding) if func.is_a?(Array) # Perform simple macro expansion for single byte key bindings. # Multibyte key bindings and recursive macro expansion are not supported yet. marco = func.pack('c*').force_encoding(@encoding) keys = marco.chars.map do |c| f = key_mapping.get(c.bytes) Reline::Key.new(c, f.is_a?(Symbol) ? f : :ed_insert, false) end elsif func keys = [Reline::Key.new(s, func, false)] else if s.valid_encoding? && s.size == 1 keys = [Reline::Key.new(s, :ed_insert, false)] else keys = [] end end [keys, input.drop(matched_bytes.size)] end private # returns match status of CSI/SS3 sequence and matched length def match_unknown_escape_sequence(input, vi_mode: false) idx = 0 return UNMATCHED unless input[idx] == ESC_BYTE idx += 1 idx += 1 if input[idx] == ESC_BYTE case input[idx] when nil if idx == 1 # `ESC` return MATCHING_MATCHED else # `ESC ESC` return MATCHING end when 91 # == '['.ord # CSI sequence `ESC [ ... char` idx += 1 idx += 1 while idx < input.size && CSI_PARAMETER_BYTES_RANGE.cover?(input[idx]) idx += 1 while idx < input.size && CSI_INTERMEDIATE_BYTES_RANGE.cover?(input[idx]) when 79 # == 'O'.ord # SS3 sequence `ESC O char` idx += 1 else # `ESC char` or `ESC ESC char` return UNMATCHED if vi_mode end case input.size when idx MATCHING when idx + 1 MATCHED else UNMATCHED end end def key_mapping @config.key_bindings end end PK!+U+Uunicode/east_asian_width.rbnu[class Reline::Unicode::EastAsianWidth # This is based on EastAsianWidth.txt # UNICODE_VERSION = '15.1.0' CHUNK_LAST, CHUNK_WIDTH = [ [0x1f, 2], [0x7e, 1], [0x7f, 2], [0xa0, 1], [0xa1, -1], [0xa3, 1], [0xa4, -1], [0xa6, 1], [0xa8, -1], [0xa9, 1], [0xaa, -1], [0xac, 1], [0xae, -1], [0xaf, 1], [0xb4, -1], [0xb5, 1], [0xba, -1], [0xbb, 1], [0xbf, -1], [0xc5, 1], [0xc6, -1], [0xcf, 1], [0xd0, -1], [0xd6, 1], [0xd8, -1], [0xdd, 1], [0xe1, -1], [0xe5, 1], [0xe6, -1], [0xe7, 1], [0xea, -1], [0xeb, 1], [0xed, -1], [0xef, 1], [0xf0, -1], [0xf1, 1], [0xf3, -1], [0xf6, 1], [0xfa, -1], [0xfb, 1], [0xfc, -1], [0xfd, 1], [0xfe, -1], [0x100, 1], [0x101, -1], [0x110, 1], [0x111, -1], [0x112, 1], [0x113, -1], [0x11a, 1], [0x11b, -1], [0x125, 1], [0x127, -1], [0x12a, 1], [0x12b, -1], [0x130, 1], [0x133, -1], [0x137, 1], [0x138, -1], [0x13e, 1], [0x142, -1], [0x143, 1], [0x144, -1], [0x147, 1], [0x14b, -1], [0x14c, 1], [0x14d, -1], [0x151, 1], [0x153, -1], [0x165, 1], [0x167, -1], [0x16a, 1], [0x16b, -1], [0x1cd, 1], [0x1ce, -1], [0x1cf, 1], [0x1d0, -1], [0x1d1, 1], [0x1d2, -1], [0x1d3, 1], [0x1d4, -1], [0x1d5, 1], [0x1d6, -1], [0x1d7, 1], [0x1d8, -1], [0x1d9, 1], [0x1da, -1], [0x1db, 1], [0x1dc, -1], [0x250, 1], [0x251, -1], [0x260, 1], [0x261, -1], [0x2c3, 1], [0x2c4, -1], [0x2c6, 1], [0x2c7, -1], [0x2c8, 1], [0x2cb, -1], [0x2cc, 1], [0x2cd, -1], [0x2cf, 1], [0x2d0, -1], [0x2d7, 1], [0x2db, -1], [0x2dc, 1], [0x2dd, -1], [0x2de, 1], [0x2df, -1], [0x2ff, 1], [0x36f, 0], [0x390, 1], [0x3a1, -1], [0x3a2, 1], [0x3a9, -1], [0x3b0, 1], [0x3c1, -1], [0x3c2, 1], [0x3c9, -1], [0x400, 1], [0x401, -1], [0x40f, 1], [0x44f, -1], [0x450, 1], [0x451, -1], [0x482, 1], [0x487, 0], [0x590, 1], [0x5bd, 0], [0x5be, 1], [0x5bf, 0], [0x5c0, 1], [0x5c2, 0], [0x5c3, 1], [0x5c5, 0], [0x5c6, 1], [0x5c7, 0], [0x60f, 1], [0x61a, 0], [0x64a, 1], [0x65f, 0], [0x66f, 1], [0x670, 0], [0x6d5, 1], [0x6dc, 0], [0x6de, 1], [0x6e4, 0], [0x6e6, 1], [0x6e8, 0], [0x6e9, 1], [0x6ed, 0], [0x710, 1], [0x711, 0], [0x72f, 1], [0x74a, 0], [0x7a5, 1], [0x7b0, 0], [0x7ea, 1], [0x7f3, 0], [0x7fc, 1], [0x7fd, 0], [0x815, 1], [0x819, 0], [0x81a, 1], [0x823, 0], [0x824, 1], [0x827, 0], [0x828, 1], [0x82d, 0], [0x858, 1], [0x85b, 0], [0x897, 1], [0x89f, 0], [0x8c9, 1], [0x8e1, 0], [0x8e2, 1], [0x902, 0], [0x939, 1], [0x93a, 0], [0x93b, 1], [0x93c, 0], [0x940, 1], [0x948, 0], [0x94c, 1], [0x94d, 0], [0x950, 1], [0x957, 0], [0x961, 1], [0x963, 0], [0x980, 1], [0x981, 0], [0x9bb, 1], [0x9bc, 0], [0x9c0, 1], [0x9c4, 0], [0x9cc, 1], [0x9cd, 0], [0x9e1, 1], [0x9e3, 0], [0x9fd, 1], [0x9fe, 0], [0xa00, 1], [0xa02, 0], [0xa3b, 1], [0xa3c, 0], [0xa40, 1], [0xa42, 0], [0xa46, 1], [0xa48, 0], [0xa4a, 1], [0xa4d, 0], [0xa50, 1], [0xa51, 0], [0xa6f, 1], [0xa71, 0], [0xa74, 1], [0xa75, 0], [0xa80, 1], [0xa82, 0], [0xabb, 1], [0xabc, 0], [0xac0, 1], [0xac5, 0], [0xac6, 1], [0xac8, 0], [0xacc, 1], [0xacd, 0], [0xae1, 1], [0xae3, 0], [0xaf9, 1], [0xaff, 0], [0xb00, 1], [0xb01, 0], [0xb3b, 1], [0xb3c, 0], [0xb3e, 1], [0xb3f, 0], [0xb40, 1], [0xb44, 0], [0xb4c, 1], [0xb4d, 0], [0xb54, 1], [0xb56, 0], [0xb61, 1], [0xb63, 0], [0xb81, 1], [0xb82, 0], [0xbbf, 1], [0xbc0, 0], [0xbcc, 1], [0xbcd, 0], [0xbff, 1], [0xc00, 0], [0xc03, 1], [0xc04, 0], [0xc3b, 1], [0xc3c, 0], [0xc3d, 1], [0xc40, 0], [0xc45, 1], [0xc48, 0], [0xc49, 1], [0xc4d, 0], [0xc54, 1], [0xc56, 0], [0xc61, 1], [0xc63, 0], [0xc80, 1], [0xc81, 0], [0xcbb, 1], [0xcbc, 0], [0xcbe, 1], [0xcbf, 0], [0xcc5, 1], [0xcc6, 0], [0xccb, 1], [0xccd, 0], [0xce1, 1], [0xce3, 0], [0xcff, 1], [0xd01, 0], [0xd3a, 1], [0xd3c, 0], [0xd40, 1], [0xd44, 0], [0xd4c, 1], [0xd4d, 0], [0xd61, 1], [0xd63, 0], [0xd80, 1], [0xd81, 0], [0xdc9, 1], [0xdca, 0], [0xdd1, 1], [0xdd4, 0], [0xdd5, 1], [0xdd6, 0], [0xe30, 1], [0xe31, 0], [0xe33, 1], [0xe3a, 0], [0xe46, 1], [0xe4e, 0], [0xeb0, 1], [0xeb1, 0], [0xeb3, 1], [0xebc, 0], [0xec7, 1], [0xece, 0], [0xf17, 1], [0xf19, 0], [0xf34, 1], [0xf35, 0], [0xf36, 1], [0xf37, 0], [0xf38, 1], [0xf39, 0], [0xf70, 1], [0xf7e, 0], [0xf7f, 1], [0xf84, 0], [0xf85, 1], [0xf87, 0], [0xf8c, 1], [0xf97, 0], [0xf98, 1], [0xfbc, 0], [0xfc5, 1], [0xfc6, 0], [0x102c, 1], [0x1030, 0], [0x1031, 1], [0x1037, 0], [0x1038, 1], [0x103a, 0], [0x103c, 1], [0x103e, 0], [0x1057, 1], [0x1059, 0], [0x105d, 1], [0x1060, 0], [0x1070, 1], [0x1074, 0], [0x1081, 1], [0x1082, 0], [0x1084, 1], [0x1086, 0], [0x108c, 1], [0x108d, 0], [0x109c, 1], [0x109d, 0], [0x10ff, 1], [0x115f, 2], [0x135c, 1], [0x135f, 0], [0x1711, 1], [0x1714, 0], [0x1731, 1], [0x1733, 0], [0x1751, 1], [0x1753, 0], [0x1771, 1], [0x1773, 0], [0x17b3, 1], [0x17b5, 0], [0x17b6, 1], [0x17bd, 0], [0x17c5, 1], [0x17c6, 0], [0x17c8, 1], [0x17d3, 0], [0x17dc, 1], [0x17dd, 0], [0x180a, 1], [0x180d, 0], [0x180e, 1], [0x180f, 0], [0x1884, 1], [0x1886, 0], [0x18a8, 1], [0x18a9, 0], [0x191f, 1], [0x1922, 0], [0x1926, 1], [0x1928, 0], [0x1931, 1], [0x1932, 0], [0x1938, 1], [0x193b, 0], [0x1a16, 1], [0x1a18, 0], [0x1a1a, 1], [0x1a1b, 0], [0x1a55, 1], [0x1a56, 0], [0x1a57, 1], [0x1a5e, 0], [0x1a5f, 1], [0x1a60, 0], [0x1a61, 1], [0x1a62, 0], [0x1a64, 1], [0x1a6c, 0], [0x1a72, 1], [0x1a7c, 0], [0x1a7e, 1], [0x1a7f, 0], [0x1aaf, 1], [0x1abd, 0], [0x1abe, 1], [0x1ace, 0], [0x1aff, 1], [0x1b03, 0], [0x1b33, 1], [0x1b34, 0], [0x1b35, 1], [0x1b3a, 0], [0x1b3b, 1], [0x1b3c, 0], [0x1b41, 1], [0x1b42, 0], [0x1b6a, 1], [0x1b73, 0], [0x1b7f, 1], [0x1b81, 0], [0x1ba1, 1], [0x1ba5, 0], [0x1ba7, 1], [0x1ba9, 0], [0x1baa, 1], [0x1bad, 0], [0x1be5, 1], [0x1be6, 0], [0x1be7, 1], [0x1be9, 0], [0x1bec, 1], [0x1bed, 0], [0x1bee, 1], [0x1bf1, 0], [0x1c2b, 1], [0x1c33, 0], [0x1c35, 1], [0x1c37, 0], [0x1ccf, 1], [0x1cd2, 0], [0x1cd3, 1], [0x1ce0, 0], [0x1ce1, 1], [0x1ce8, 0], [0x1cec, 1], [0x1ced, 0], [0x1cf3, 1], [0x1cf4, 0], [0x1cf7, 1], [0x1cf9, 0], [0x1dbf, 1], [0x1dff, 0], [0x200f, 1], [0x2010, -1], [0x2012, 1], [0x2016, -1], [0x2017, 1], [0x2019, -1], [0x201b, 1], [0x201d, -1], [0x201f, 1], [0x2022, -1], [0x2023, 1], [0x2027, -1], [0x202f, 1], [0x2030, -1], [0x2031, 1], [0x2033, -1], [0x2034, 1], [0x2035, -1], [0x203a, 1], [0x203b, -1], [0x203d, 1], [0x203e, -1], [0x2073, 1], [0x2074, -1], [0x207e, 1], [0x207f, -1], [0x2080, 1], [0x2084, -1], [0x20ab, 1], [0x20ac, -1], [0x20cf, 1], [0x20dc, 0], [0x20e0, 1], [0x20e1, 0], [0x20e4, 1], [0x20f0, 0], [0x2102, 1], [0x2103, -1], [0x2104, 1], [0x2105, -1], [0x2108, 1], [0x2109, -1], [0x2112, 1], [0x2113, -1], [0x2115, 1], [0x2116, -1], [0x2120, 1], [0x2122, -1], [0x2125, 1], [0x2126, -1], [0x212a, 1], [0x212b, -1], [0x2152, 1], [0x2154, -1], [0x215a, 1], [0x215e, -1], [0x215f, 1], [0x216b, -1], [0x216f, 1], [0x2179, -1], [0x2188, 1], [0x2189, -1], [0x218f, 1], [0x2199, -1], [0x21b7, 1], [0x21b9, -1], [0x21d1, 1], [0x21d2, -1], [0x21d3, 1], [0x21d4, -1], [0x21e6, 1], [0x21e7, -1], [0x21ff, 1], [0x2200, -1], [0x2201, 1], [0x2203, -1], [0x2206, 1], [0x2208, -1], [0x220a, 1], [0x220b, -1], [0x220e, 1], [0x220f, -1], [0x2210, 1], [0x2211, -1], [0x2214, 1], [0x2215, -1], [0x2219, 1], [0x221a, -1], [0x221c, 1], [0x2220, -1], [0x2222, 1], [0x2223, -1], [0x2224, 1], [0x2225, -1], [0x2226, 1], [0x222c, -1], [0x222d, 1], [0x222e, -1], [0x2233, 1], [0x2237, -1], [0x223b, 1], [0x223d, -1], [0x2247, 1], [0x2248, -1], [0x224b, 1], [0x224c, -1], [0x2251, 1], [0x2252, -1], [0x225f, 1], [0x2261, -1], [0x2263, 1], [0x2267, -1], [0x2269, 1], [0x226b, -1], [0x226d, 1], [0x226f, -1], [0x2281, 1], [0x2283, -1], [0x2285, 1], [0x2287, -1], [0x2294, 1], [0x2295, -1], [0x2298, 1], [0x2299, -1], [0x22a4, 1], [0x22a5, -1], [0x22be, 1], [0x22bf, -1], [0x2311, 1], [0x2312, -1], [0x2319, 1], [0x231b, 2], [0x2328, 1], [0x232a, 2], [0x23e8, 1], [0x23ec, 2], [0x23ef, 1], [0x23f0, 2], [0x23f2, 1], [0x23f3, 2], [0x245f, 1], [0x24e9, -1], [0x24ea, 1], [0x254b, -1], [0x254f, 1], [0x2573, -1], [0x257f, 1], [0x258f, -1], [0x2591, 1], [0x2595, -1], [0x259f, 1], [0x25a1, -1], [0x25a2, 1], [0x25a9, -1], [0x25b1, 1], [0x25b3, -1], [0x25b5, 1], [0x25b7, -1], [0x25bb, 1], [0x25bd, -1], [0x25bf, 1], [0x25c1, -1], [0x25c5, 1], [0x25c8, -1], [0x25ca, 1], [0x25cb, -1], [0x25cd, 1], [0x25d1, -1], [0x25e1, 1], [0x25e5, -1], [0x25ee, 1], [0x25ef, -1], [0x25fc, 1], [0x25fe, 2], [0x2604, 1], [0x2606, -1], [0x2608, 1], [0x2609, -1], [0x260d, 1], [0x260f, -1], [0x2613, 1], [0x2615, 2], [0x261b, 1], [0x261c, -1], [0x261d, 1], [0x261e, -1], [0x263f, 1], [0x2640, -1], [0x2641, 1], [0x2642, -1], [0x2647, 1], [0x2653, 2], [0x265f, 1], [0x2661, -1], [0x2662, 1], [0x2665, -1], [0x2666, 1], [0x266a, -1], [0x266b, 1], [0x266d, -1], [0x266e, 1], [0x266f, -1], [0x267e, 1], [0x267f, 2], [0x2692, 1], [0x2693, 2], [0x269d, 1], [0x269f, -1], [0x26a0, 1], [0x26a1, 2], [0x26a9, 1], [0x26ab, 2], [0x26bc, 1], [0x26be, 2], [0x26bf, -1], [0x26c3, 1], [0x26c5, 2], [0x26cd, -1], [0x26ce, 2], [0x26d3, -1], [0x26d4, 2], [0x26e1, -1], [0x26e2, 1], [0x26e3, -1], [0x26e7, 1], [0x26e9, -1], [0x26ea, 2], [0x26f1, -1], [0x26f3, 2], [0x26f4, -1], [0x26f5, 2], [0x26f9, -1], [0x26fa, 2], [0x26fc, -1], [0x26fd, 2], [0x26ff, -1], [0x2704, 1], [0x2705, 2], [0x2709, 1], [0x270b, 2], [0x2727, 1], [0x2728, 2], [0x273c, 1], [0x273d, -1], [0x274b, 1], [0x274c, 2], [0x274d, 1], [0x274e, 2], [0x2752, 1], [0x2755, 2], [0x2756, 1], [0x2757, 2], [0x2775, 1], [0x277f, -1], [0x2794, 1], [0x2797, 2], [0x27af, 1], [0x27b0, 2], [0x27be, 1], [0x27bf, 2], [0x2b1a, 1], [0x2b1c, 2], [0x2b4f, 1], [0x2b50, 2], [0x2b54, 1], [0x2b55, 2], [0x2b59, -1], [0x2cee, 1], [0x2cf1, 0], [0x2d7e, 1], [0x2d7f, 0], [0x2ddf, 1], [0x2dff, 0], [0x2e7f, 1], [0x2e99, 2], [0x2e9a, 1], [0x2ef3, 2], [0x2eff, 1], [0x2fd5, 2], [0x2fef, 1], [0x3029, 2], [0x302d, 0], [0x303e, 2], [0x3040, 1], [0x3096, 2], [0x3098, 1], [0x309a, 0], [0x30ff, 2], [0x3104, 1], [0x312f, 2], [0x3130, 1], [0x318e, 2], [0x318f, 1], [0x31e3, 2], [0x31ee, 1], [0x321e, 2], [0x321f, 1], [0x3247, 2], [0x324f, -1], [0x4dbf, 2], [0x4dff, 1], [0xa48c, 2], [0xa48f, 1], [0xa4c6, 2], [0xa66e, 1], [0xa66f, 0], [0xa673, 1], [0xa67d, 0], [0xa69d, 1], [0xa69f, 0], [0xa6ef, 1], [0xa6f1, 0], [0xa801, 1], [0xa802, 0], [0xa805, 1], [0xa806, 0], [0xa80a, 1], [0xa80b, 0], [0xa824, 1], [0xa826, 0], [0xa82b, 1], [0xa82c, 0], [0xa8c3, 1], [0xa8c5, 0], [0xa8df, 1], [0xa8f1, 0], [0xa8fe, 1], [0xa8ff, 0], [0xa925, 1], [0xa92d, 0], [0xa946, 1], [0xa951, 0], [0xa95f, 1], [0xa97c, 2], [0xa97f, 1], [0xa982, 0], [0xa9b2, 1], [0xa9b3, 0], [0xa9b5, 1], [0xa9b9, 0], [0xa9bb, 1], [0xa9bd, 0], [0xa9e4, 1], [0xa9e5, 0], [0xaa28, 1], [0xaa2e, 0], [0xaa30, 1], [0xaa32, 0], [0xaa34, 1], [0xaa36, 0], [0xaa42, 1], [0xaa43, 0], [0xaa4b, 1], [0xaa4c, 0], [0xaa7b, 1], [0xaa7c, 0], [0xaaaf, 1], [0xaab0, 0], [0xaab1, 1], [0xaab4, 0], [0xaab6, 1], [0xaab8, 0], [0xaabd, 1], [0xaabf, 0], [0xaac0, 1], [0xaac1, 0], [0xaaeb, 1], [0xaaed, 0], [0xaaf5, 1], [0xaaf6, 0], [0xabe4, 1], [0xabe5, 0], [0xabe7, 1], [0xabe8, 0], [0xabec, 1], [0xabed, 0], [0xabff, 1], [0xd7a3, 2], [0xdfff, 1], [0xf8ff, -1], [0xfaff, 2], [0xfb1d, 1], [0xfb1e, 0], [0xfdff, 1], [0xfe0f, 0], [0xfe19, 2], [0xfe1f, 1], [0xfe2f, 0], [0xfe52, 2], [0xfe53, 1], [0xfe66, 2], [0xfe67, 1], [0xfe6b, 2], [0xff00, 1], [0xff60, 2], [0xffdf, 1], [0xffe6, 2], [0xfffc, 1], [0xfffd, -1], [0x101fc, 1], [0x101fd, 0], [0x102df, 1], [0x102e0, 0], [0x10375, 1], [0x1037a, 0], [0x10a00, 1], [0x10a03, 0], [0x10a04, 1], [0x10a06, 0], [0x10a0b, 1], [0x10a0f, 0], [0x10a37, 1], [0x10a3a, 0], [0x10a3e, 1], [0x10a3f, 0], [0x10ae4, 1], [0x10ae6, 0], [0x10d23, 1], [0x10d27, 0], [0x10eaa, 1], [0x10eac, 0], [0x10efc, 1], [0x10eff, 0], [0x10f45, 1], [0x10f50, 0], [0x10f81, 1], [0x10f85, 0], [0x11000, 1], [0x11001, 0], [0x11037, 1], [0x11046, 0], [0x1106f, 1], [0x11070, 0], [0x11072, 1], [0x11074, 0], [0x1107e, 1], [0x11081, 0], [0x110b2, 1], [0x110b6, 0], [0x110b8, 1], [0x110ba, 0], [0x110c1, 1], [0x110c2, 0], [0x110ff, 1], [0x11102, 0], [0x11126, 1], [0x1112b, 0], [0x1112c, 1], [0x11134, 0], [0x11172, 1], [0x11173, 0], [0x1117f, 1], [0x11181, 0], [0x111b5, 1], [0x111be, 0], [0x111c8, 1], [0x111cc, 0], [0x111ce, 1], [0x111cf, 0], [0x1122e, 1], [0x11231, 0], [0x11233, 1], [0x11234, 0], [0x11235, 1], [0x11237, 0], [0x1123d, 1], [0x1123e, 0], [0x11240, 1], [0x11241, 0], [0x112de, 1], [0x112df, 0], [0x112e2, 1], [0x112ea, 0], [0x112ff, 1], [0x11301, 0], [0x1133a, 1], [0x1133c, 0], [0x1133f, 1], [0x11340, 0], [0x11365, 1], [0x1136c, 0], [0x1136f, 1], [0x11374, 0], [0x11437, 1], [0x1143f, 0], [0x11441, 1], [0x11444, 0], [0x11445, 1], [0x11446, 0], [0x1145d, 1], [0x1145e, 0], [0x114b2, 1], [0x114b8, 0], [0x114b9, 1], [0x114ba, 0], [0x114be, 1], [0x114c0, 0], [0x114c1, 1], [0x114c3, 0], [0x115b1, 1], [0x115b5, 0], [0x115bb, 1], [0x115bd, 0], [0x115be, 1], [0x115c0, 0], [0x115db, 1], [0x115dd, 0], [0x11632, 1], [0x1163a, 0], [0x1163c, 1], [0x1163d, 0], [0x1163e, 1], [0x11640, 0], [0x116aa, 1], [0x116ab, 0], [0x116ac, 1], [0x116ad, 0], [0x116af, 1], [0x116b5, 0], [0x116b6, 1], [0x116b7, 0], [0x1171c, 1], [0x1171f, 0], [0x11721, 1], [0x11725, 0], [0x11726, 1], [0x1172b, 0], [0x1182e, 1], [0x11837, 0], [0x11838, 1], [0x1183a, 0], [0x1193a, 1], [0x1193c, 0], [0x1193d, 1], [0x1193e, 0], [0x11942, 1], [0x11943, 0], [0x119d3, 1], [0x119d7, 0], [0x119d9, 1], [0x119db, 0], [0x119df, 1], [0x119e0, 0], [0x11a00, 1], [0x11a0a, 0], [0x11a32, 1], [0x11a38, 0], [0x11a3a, 1], [0x11a3e, 0], [0x11a46, 1], [0x11a47, 0], [0x11a50, 1], [0x11a56, 0], [0x11a58, 1], [0x11a5b, 0], [0x11a89, 1], [0x11a96, 0], [0x11a97, 1], [0x11a99, 0], [0x11c2f, 1], [0x11c36, 0], [0x11c37, 1], [0x11c3d, 0], [0x11c3e, 1], [0x11c3f, 0], [0x11c91, 1], [0x11ca7, 0], [0x11ca9, 1], [0x11cb0, 0], [0x11cb1, 1], [0x11cb3, 0], [0x11cb4, 1], [0x11cb6, 0], [0x11d30, 1], [0x11d36, 0], [0x11d39, 1], [0x11d3a, 0], [0x11d3b, 1], [0x11d3d, 0], [0x11d3e, 1], [0x11d45, 0], [0x11d46, 1], [0x11d47, 0], [0x11d8f, 1], [0x11d91, 0], [0x11d94, 1], [0x11d95, 0], [0x11d96, 1], [0x11d97, 0], [0x11ef2, 1], [0x11ef4, 0], [0x11eff, 1], [0x11f01, 0], [0x11f35, 1], [0x11f3a, 0], [0x11f3f, 1], [0x11f40, 0], [0x11f41, 1], [0x11f42, 0], [0x1343f, 1], [0x13440, 0], [0x13446, 1], [0x13455, 0], [0x16aef, 1], [0x16af4, 0], [0x16b2f, 1], [0x16b36, 0], [0x16f4e, 1], [0x16f4f, 0], [0x16f8e, 1], [0x16f92, 0], [0x16fdf, 1], [0x16fe3, 2], [0x16fe4, 0], [0x16fef, 1], [0x16ff1, 2], [0x16fff, 1], [0x187f7, 2], [0x187ff, 1], [0x18cd5, 2], [0x18cff, 1], [0x18d08, 2], [0x1afef, 1], [0x1aff3, 2], [0x1aff4, 1], [0x1affb, 2], [0x1affc, 1], [0x1affe, 2], [0x1afff, 1], [0x1b122, 2], [0x1b131, 1], [0x1b132, 2], [0x1b14f, 1], [0x1b152, 2], [0x1b154, 1], [0x1b155, 2], [0x1b163, 1], [0x1b167, 2], [0x1b16f, 1], [0x1b2fb, 2], [0x1bc9c, 1], [0x1bc9e, 0], [0x1ceff, 1], [0x1cf2d, 0], [0x1cf2f, 1], [0x1cf46, 0], [0x1d166, 1], [0x1d169, 0], [0x1d17a, 1], [0x1d182, 0], [0x1d184, 1], [0x1d18b, 0], [0x1d1a9, 1], [0x1d1ad, 0], [0x1d241, 1], [0x1d244, 0], [0x1d9ff, 1], [0x1da36, 0], [0x1da3a, 1], [0x1da6c, 0], [0x1da74, 1], [0x1da75, 0], [0x1da83, 1], [0x1da84, 0], [0x1da9a, 1], [0x1da9f, 0], [0x1daa0, 1], [0x1daaf, 0], [0x1dfff, 1], [0x1e006, 0], [0x1e007, 1], [0x1e018, 0], [0x1e01a, 1], [0x1e021, 0], [0x1e022, 1], [0x1e024, 0], [0x1e025, 1], [0x1e02a, 0], [0x1e08e, 1], [0x1e08f, 0], [0x1e12f, 1], [0x1e136, 0], [0x1e2ad, 1], [0x1e2ae, 0], [0x1e2eb, 1], [0x1e2ef, 0], [0x1e4eb, 1], [0x1e4ef, 0], [0x1e8cf, 1], [0x1e8d6, 0], [0x1e943, 1], [0x1e94a, 0], [0x1f003, 1], [0x1f004, 2], [0x1f0ce, 1], [0x1f0cf, 2], [0x1f0ff, 1], [0x1f10a, -1], [0x1f10f, 1], [0x1f12d, -1], [0x1f12f, 1], [0x1f169, -1], [0x1f16f, 1], [0x1f18d, -1], [0x1f18e, 2], [0x1f190, -1], [0x1f19a, 2], [0x1f1ac, -1], [0x1f1ff, 1], [0x1f202, 2], [0x1f20f, 1], [0x1f23b, 2], [0x1f23f, 1], [0x1f248, 2], [0x1f24f, 1], [0x1f251, 2], [0x1f25f, 1], [0x1f265, 2], [0x1f2ff, 1], [0x1f320, 2], [0x1f32c, 1], [0x1f335, 2], [0x1f336, 1], [0x1f37c, 2], [0x1f37d, 1], [0x1f393, 2], [0x1f39f, 1], [0x1f3ca, 2], [0x1f3ce, 1], [0x1f3d3, 2], [0x1f3df, 1], [0x1f3f0, 2], [0x1f3f3, 1], [0x1f3f4, 2], [0x1f3f7, 1], [0x1f43e, 2], [0x1f43f, 1], [0x1f440, 2], [0x1f441, 1], [0x1f4fc, 2], [0x1f4fe, 1], [0x1f53d, 2], [0x1f54a, 1], [0x1f54e, 2], [0x1f54f, 1], [0x1f567, 2], [0x1f579, 1], [0x1f57a, 2], [0x1f594, 1], [0x1f596, 2], [0x1f5a3, 1], [0x1f5a4, 2], [0x1f5fa, 1], [0x1f64f, 2], [0x1f67f, 1], [0x1f6c5, 2], [0x1f6cb, 1], [0x1f6cc, 2], [0x1f6cf, 1], [0x1f6d2, 2], [0x1f6d4, 1], [0x1f6d7, 2], [0x1f6db, 1], [0x1f6df, 2], [0x1f6ea, 1], [0x1f6ec, 2], [0x1f6f3, 1], [0x1f6fc, 2], [0x1f7df, 1], [0x1f7eb, 2], [0x1f7ef, 1], [0x1f7f0, 2], [0x1f90b, 1], [0x1f93a, 2], [0x1f93b, 1], [0x1f945, 2], [0x1f946, 1], [0x1f9ff, 2], [0x1fa6f, 1], [0x1fa7c, 2], [0x1fa7f, 1], [0x1fa88, 2], [0x1fa8f, 1], [0x1fabd, 2], [0x1fabe, 1], [0x1fac5, 2], [0x1facd, 1], [0x1fadb, 2], [0x1fadf, 1], [0x1fae8, 2], [0x1faef, 1], [0x1faf8, 2], [0x1ffff, 1], [0x2fffd, 2], [0x2ffff, 1], [0x3fffd, 2], [0xe00ff, 1], [0xe01ef, 0], [0xeffff, 1], [0xffffd, -1], [0xfffff, 1], [0x10fffd, -1], [0x7fffffff, 1] ].transpose.map(&:freeze) end PK! M$$face.rbnu[# frozen_string_literal: true class Reline::Face SGR_PARAMETERS = { foreground: { black: 30, red: 31, green: 32, yellow: 33, blue: 34, magenta: 35, cyan: 36, white: 37, bright_black: 90, gray: 90, bright_red: 91, bright_green: 92, bright_yellow: 93, bright_blue: 94, bright_magenta: 95, bright_cyan: 96, bright_white: 97 }, background: { black: 40, red: 41, green: 42, yellow: 43, blue: 44, magenta: 45, cyan: 46, white: 47, bright_black: 100, gray: 100, bright_red: 101, bright_green: 102, bright_yellow: 103, bright_blue: 104, bright_magenta: 105, bright_cyan: 106, bright_white: 107, }, style: { reset: 0, bold: 1, faint: 2, italicized: 3, underlined: 4, slowly_blinking: 5, blinking: 5, rapidly_blinking: 6, negative: 7, concealed: 8, crossed_out: 9 } }.freeze class Config ESSENTIAL_DEFINE_NAMES = %i(default enhanced scrollbar).freeze RESET_SGR = "\e[0m".freeze def initialize(name, &block) @definition = {} block.call(self) ESSENTIAL_DEFINE_NAMES.each do |name| @definition[name] ||= { style: :reset, escape_sequence: RESET_SGR } end end attr_reader :definition def define(name, **values) values[:escape_sequence] = format_to_sgr(values.to_a).freeze @definition[name] = values end def reconfigure @definition.each_value do |values| values.delete(:escape_sequence) values[:escape_sequence] = format_to_sgr(values.to_a).freeze end end def [](name) @definition.dig(name, :escape_sequence) or raise ArgumentError, "unknown face: #{name}" end private def sgr_rgb(key, value) return nil unless rgb_expression?(value) if Reline::Face.truecolor? sgr_rgb_truecolor(key, value) else sgr_rgb_256color(key, value) end end def sgr_rgb_truecolor(key, value) case key when :foreground "38;2;" when :background "48;2;" end + value[1, 6].scan(/../).map(&:hex).join(";") end def sgr_rgb_256color(key, value) # 256 colors are # 0..15: standard colors, high intensity colors # 16..232: 216 colors (R, G, B each 6 steps) # 233..255: grayscale colors (24 steps) # This methods converts rgb_expression to 216 colors rgb = value[1, 6].scan(/../).map(&:hex) # Color steps are [0, 95, 135, 175, 215, 255] r, g, b = rgb.map { |v| v <= 95 ? v / 48 : (v - 35) / 40 } color = (16 + 36 * r + 6 * g + b) case key when :foreground "38;5;#{color}" when :background "48;5;#{color}" end end def format_to_sgr(ordered_values) sgr = "\e[" + ordered_values.map do |key_value| key, value = key_value case key when :foreground, :background case value when Symbol SGR_PARAMETERS[key][value] when String sgr_rgb(key, value) end when :style [ value ].flatten.map do |style_name| SGR_PARAMETERS[:style][style_name] end.then do |sgr_parameters| sgr_parameters.include?(nil) ? nil : sgr_parameters end end.then do |rendition_expression| unless rendition_expression raise ArgumentError, "invalid SGR parameter: #{value.inspect}" end rendition_expression end end.join(';') + "m" sgr == RESET_SGR ? RESET_SGR : RESET_SGR + sgr end def rgb_expression?(color) color.respond_to?(:match?) and color.match?(/\A#[0-9a-fA-F]{6}\z/) end end private_constant :SGR_PARAMETERS, :Config def self.truecolor? @force_truecolor || %w[truecolor 24bit].include?(ENV['COLORTERM']) end def self.force_truecolor @force_truecolor = true @configs&.each_value(&:reconfigure) end def self.[](name) @configs[name] end def self.config(name, &block) @configs ||= {} @configs[name] = Config.new(name, &block) end def self.configs @configs.transform_values(&:definition) end def self.load_initial_configs config(:default) do |conf| conf.define :default, style: :reset conf.define :enhanced, style: :reset conf.define :scrollbar, style: :reset end config(:completion_dialog) do |conf| conf.define :default, foreground: :bright_white, background: :gray conf.define :enhanced, foreground: :black, background: :white conf.define :scrollbar, foreground: :white, background: :gray end end def self.reset_to_initial_configs @configs = {} load_initial_configs end end PK!! history.rbnu[class Reline::History < Array def initialize(config) @config = config end def to_s 'HISTORY' end def delete_at(index) index = check_index(index) super(index) end def [](index) index = check_index(index) unless index.is_a?(Range) super(index) end def []=(index, val) index = check_index(index) super(index, Reline::Unicode.safe_encode(val, Reline.encoding_system_needs)) end def concat(*val) val.each do |v| push(*v) end end def push(*val) # If history_size is zero, all histories are dropped. return self if @config.history_size.zero? # If history_size is negative, history size is unlimited. if @config.history_size.positive? diff = size + val.size - @config.history_size if diff > 0 if diff <= size shift(diff) else diff -= size clear val.shift(diff) end end end super(*(val.map{ |v| Reline::Unicode.safe_encode(v, Reline.encoding_system_needs) })) end def <<(val) # If history_size is zero, all histories are dropped. return self if @config.history_size.zero? # If history_size is negative, history size is unlimited. if @config.history_size.positive? shift if size + 1 > @config.history_size end super(Reline::Unicode.safe_encode(val, Reline.encoding_system_needs)) end private def check_index(index) index += size if index < 0 if index < -2147483648 or 2147483647 < index raise RangeError.new("integer #{index} too big to convert to 'int'") end # If history_size is negative, history size is unlimited. if @config.history_size.positive? if index < -@config.history_size or @config.history_size < index raise RangeError.new("index=<#{index}>") end end raise IndexError.new("index=<#{index}>") if index < 0 or size <= index index end end PK!lM'b33line_editor.rbnu[require 'reline/kill_ring' require 'reline/unicode' require 'tempfile' class Reline::LineEditor # TODO: Use "private alias_method" idiom after drop Ruby 2.5. attr_reader :byte_pointer attr_accessor :confirm_multiline_termination_proc attr_accessor :completion_proc attr_accessor :completion_append_character attr_accessor :output_modifier_proc attr_accessor :prompt_proc attr_accessor :auto_indent_proc attr_accessor :dig_perfect_match_proc VI_MOTIONS = %i{ ed_prev_char ed_next_char vi_zero ed_move_to_beg ed_move_to_end vi_to_column vi_next_char vi_prev_char vi_next_word vi_prev_word vi_to_next_char vi_to_prev_char vi_end_word vi_next_big_word vi_prev_big_word vi_end_big_word } module CompletionState NORMAL = :normal MENU = :menu MENU_WITH_PERFECT_MATCH = :menu_with_perfect_match PERFECT_MATCH = :perfect_match end RenderedScreen = Struct.new(:base_y, :lines, :cursor_y, keyword_init: true) CompletionJourneyState = Struct.new(:line_index, :pre, :target, :post, :list, :pointer) NullActionState = [nil, nil].freeze class MenuInfo attr_reader :list def initialize(list) @list = list end def lines(screen_width) return [] if @list.empty? list = @list.sort sizes = list.map { |item| Reline::Unicode.calculate_width(item) } item_width = sizes.max + 2 num_cols = [screen_width / item_width, 1].max num_rows = list.size.fdiv(num_cols).ceil list_with_padding = list.zip(sizes).map { |item, size| item + ' ' * (item_width - size) } aligned = (list_with_padding + [nil] * (num_rows * num_cols - list_with_padding.size)).each_slice(num_rows).to_a.transpose aligned.map do |row| row.join.rstrip end end end MINIMUM_SCROLLBAR_HEIGHT = 1 def initialize(config) @config = config @completion_append_character = '' @screen_size = [0, 0] # Should be initialized with actual winsize in LineEditor#reset reset_variables end def io_gate Reline::IOGate end def encoding io_gate.encoding end def set_pasting_state(in_pasting) # While pasting, text to be inserted is stored to @continuous_insertion_buffer. # After pasting, this buffer should be force inserted. process_insert(force: true) if @in_pasting && !in_pasting @in_pasting = in_pasting end private def check_mode_string if @config.show_mode_in_prompt if @config.editing_mode_is?(:vi_command) @config.vi_cmd_mode_string elsif @config.editing_mode_is?(:vi_insert) @config.vi_ins_mode_string elsif @config.editing_mode_is?(:emacs) @config.emacs_mode_string else '?' end end end private def check_multiline_prompt(buffer, mode_string) if @vi_arg prompt = "(arg: #{@vi_arg}) " elsif @searching_prompt prompt = @searching_prompt else prompt = @prompt end if !@is_multiline mode_string = check_mode_string prompt = mode_string + prompt if mode_string [prompt] + [''] * (buffer.size - 1) elsif @prompt_proc prompt_list = @prompt_proc.(buffer).map { |pr| pr.gsub("\n", "\\n") } prompt_list.map!{ prompt } if @vi_arg or @searching_prompt prompt_list = [prompt] if prompt_list.empty? prompt_list = prompt_list.map{ |pr| mode_string + pr } if mode_string prompt = prompt_list[@line_index] prompt = prompt_list[0] if prompt.nil? prompt = prompt_list.last if prompt.nil? if buffer.size > prompt_list.size (buffer.size - prompt_list.size).times do prompt_list << prompt_list.last end end prompt_list else prompt = mode_string + prompt if mode_string [prompt] * buffer.size end end def reset(prompt = '') @screen_size = Reline::IOGate.get_screen_size reset_variables(prompt) @rendered_screen.base_y = Reline::IOGate.cursor_pos.y if ENV.key?('RELINE_ALT_SCROLLBAR') @full_block = '::' @upper_half_block = "''" @lower_half_block = '..' @block_elem_width = 2 elsif Reline::IOGate.win? @full_block = '█' @upper_half_block = '▀' @lower_half_block = '▄' @block_elem_width = 1 elsif encoding == Encoding::UTF_8 @full_block = '█' @upper_half_block = '▀' @lower_half_block = '▄' @block_elem_width = Reline::Unicode.calculate_width('█') else @full_block = '::' @upper_half_block = "''" @lower_half_block = '..' @block_elem_width = 2 end end def handle_signal handle_interrupted handle_resized end private def handle_resized return unless @resized @screen_size = Reline::IOGate.get_screen_size @resized = false scroll_into_view Reline::IOGate.move_cursor_up @rendered_screen.cursor_y @rendered_screen.base_y = Reline::IOGate.cursor_pos.y clear_rendered_screen_cache render end private def handle_interrupted return unless @interrupted @interrupted = false clear_dialogs render cursor_to_bottom_offset = @rendered_screen.lines.size - @rendered_screen.cursor_y Reline::IOGate.scroll_down cursor_to_bottom_offset Reline::IOGate.move_cursor_column 0 clear_rendered_screen_cache case @old_trap when 'DEFAULT', 'SYSTEM_DEFAULT' raise Interrupt when 'IGNORE' # Do nothing when 'EXIT' exit else @old_trap.call if @old_trap.respond_to?(:call) end end def set_signal_handlers Reline::IOGate.set_winch_handler do @resized = true end @old_trap = Signal.trap('INT') do @interrupted = true end end def finalize Signal.trap('INT', @old_trap) end def eof? @eof end def reset_variables(prompt = '') @prompt = prompt.gsub("\n", "\\n") @mark_pointer = nil @is_multiline = false @finished = false @history_pointer = nil @kill_ring ||= Reline::KillRing.new @vi_clipboard = '' @vi_arg = nil @waiting_proc = nil @vi_waiting_operator = nil @vi_waiting_operator_arg = nil @completion_journey_state = nil @completion_state = CompletionState::NORMAL @perfect_matched = nil @menu_info = nil @searching_prompt = nil @just_cursor_moving = false @eof = false @continuous_insertion_buffer = String.new(encoding: encoding) @scroll_partial_screen = 0 @drop_terminate_spaces = false @in_pasting = false @auto_indent_proc = nil @dialogs = [] @interrupted = false @resized = false @cache = {} @rendered_screen = RenderedScreen.new(base_y: 0, lines: [], cursor_y: 0) @input_lines = [[[""], 0, 0]] @input_lines_position = 0 @restoring = false @prev_action_state = NullActionState @next_action_state = NullActionState reset_line end def reset_line @byte_pointer = 0 @buffer_of_lines = [String.new(encoding: encoding)] @line_index = 0 @cache.clear @line_backup_in_history = nil end def multiline_on @is_multiline = true end def multiline_off @is_multiline = false end private def insert_new_line(cursor_line, next_line) @buffer_of_lines.insert(@line_index + 1, String.new(next_line, encoding: encoding)) @buffer_of_lines[@line_index] = cursor_line @line_index += 1 @byte_pointer = 0 if @auto_indent_proc && !@in_pasting if next_line.empty? ( # For compatibility, use this calculation instead of just `process_auto_indent @line_index - 1, cursor_dependent: false` indent1 = @auto_indent_proc.(@buffer_of_lines.take(@line_index - 1).push(''), @line_index - 1, 0, true) indent2 = @auto_indent_proc.(@buffer_of_lines.take(@line_index), @line_index - 1, @buffer_of_lines[@line_index - 1].bytesize, false) indent = indent2 || indent1 @buffer_of_lines[@line_index - 1] = ' ' * indent + @buffer_of_lines[@line_index - 1].gsub(/\A\s*/, '') ) process_auto_indent @line_index, add_newline: true else process_auto_indent @line_index - 1, cursor_dependent: false process_auto_indent @line_index, add_newline: true # Need for compatibility process_auto_indent @line_index, cursor_dependent: false end end end private def split_line_by_width(str, max_width, offset: 0) Reline::Unicode.split_line_by_width(str, max_width, encoding, offset: offset) end def current_byte_pointer_cursor calculate_width(current_line.byteslice(0, @byte_pointer)) end private def calculate_nearest_cursor(cursor) line_to_calc = current_line new_cursor_max = calculate_width(line_to_calc) new_cursor = 0 new_byte_pointer = 0 height = 1 max_width = screen_width if @config.editing_mode_is?(:vi_command) last_byte_size = Reline::Unicode.get_prev_mbchar_size(line_to_calc, line_to_calc.bytesize) if last_byte_size > 0 last_mbchar = line_to_calc.byteslice(line_to_calc.bytesize - last_byte_size, last_byte_size) last_width = Reline::Unicode.get_mbchar_width(last_mbchar) end_of_line_cursor = new_cursor_max - last_width else end_of_line_cursor = new_cursor_max end else end_of_line_cursor = new_cursor_max end line_to_calc.grapheme_clusters.each do |gc| mbchar = gc.encode(Encoding::UTF_8) mbchar_width = Reline::Unicode.get_mbchar_width(mbchar) now = new_cursor + mbchar_width if now > end_of_line_cursor or now > cursor break end new_cursor += mbchar_width if new_cursor > max_width * height height += 1 end new_byte_pointer += gc.bytesize end @byte_pointer = new_byte_pointer end def with_cache(key, *deps) cached_deps, value = @cache[key] if cached_deps != deps @cache[key] = [deps, value = yield(*deps, cached_deps, value)] end value end def modified_lines with_cache(__method__, whole_lines, finished?) do |whole, complete| modify_lines(whole, complete) end end def prompt_list with_cache(__method__, whole_lines, check_mode_string, @vi_arg, @searching_prompt) do |lines, mode_string| check_multiline_prompt(lines, mode_string) end end def screen_height @screen_size.first end def screen_width @screen_size.last end def screen_scroll_top @scroll_partial_screen end def wrapped_prompt_and_input_lines with_cache(__method__, @buffer_of_lines.size, modified_lines, prompt_list, screen_width) do |n, lines, prompts, width, prev_cache_key, cached_value| prev_n, prev_lines, prev_prompts, prev_width = prev_cache_key cached_wraps = {} if prev_width == width prev_n.times do |i| cached_wraps[[prev_prompts[i], prev_lines[i]]] = cached_value[i] end end n.times.map do |i| prompt = prompts[i] || '' line = lines[i] || '' if (cached = cached_wraps[[prompt, line]]) next cached end *wrapped_prompts, code_line_prompt = split_line_by_width(prompt, width) wrapped_lines = split_line_by_width(line, width, offset: calculate_width(code_line_prompt, true)) wrapped_prompts.map { |p| [p, ''] } + [[code_line_prompt, wrapped_lines.first]] + wrapped_lines.drop(1).map { |c| ['', c] } end end end def calculate_overlay_levels(overlay_levels) levels = [] overlay_levels.each do |x, w, l| levels.fill(l, x, w) end levels end def render_line_differential(old_items, new_items) old_levels = calculate_overlay_levels(old_items.zip(new_items).each_with_index.map {|((x, w, c), (nx, _nw, nc)), i| [x, w, c == nc && x == nx ? i : -1] if x }.compact) new_levels = calculate_overlay_levels(new_items.each_with_index.map { |(x, w), i| [x, w, i] if x }.compact).take(screen_width) base_x = 0 new_levels.zip(old_levels).chunk { |n, o| n == o ? :skip : n || :blank }.each do |level, chunk| width = chunk.size if level == :skip # do nothing elsif level == :blank Reline::IOGate.move_cursor_column base_x Reline::IOGate.write "#{Reline::IOGate.reset_color_sequence}#{' ' * width}" else x, w, content = new_items[level] cover_begin = base_x != 0 && new_levels[base_x - 1] == level cover_end = new_levels[base_x + width] == level pos = 0 unless x == base_x && w == width content, pos = Reline::Unicode.take_mbchar_range(content, base_x - x, width, cover_begin: cover_begin, cover_end: cover_end, padding: true) end Reline::IOGate.move_cursor_column x + pos Reline::IOGate.write "#{Reline::IOGate.reset_color_sequence}#{content}#{Reline::IOGate.reset_color_sequence}" end base_x += width end if old_levels.size > new_levels.size Reline::IOGate.move_cursor_column new_levels.size Reline::IOGate.erase_after_cursor end end # Calculate cursor position in word wrapped content. def wrapped_cursor_position prompt_width = calculate_width(prompt_list[@line_index], true) line_before_cursor = Reline::Unicode.escape_for_print(whole_lines[@line_index].byteslice(0, @byte_pointer)) wrapped_line_before_cursor = split_line_by_width(' ' * prompt_width + line_before_cursor, screen_width) wrapped_cursor_y = wrapped_prompt_and_input_lines[0...@line_index].sum(&:size) + wrapped_line_before_cursor.size - 1 wrapped_cursor_x = calculate_width(wrapped_line_before_cursor.last) [wrapped_cursor_x, wrapped_cursor_y] end def clear_dialogs @dialogs.each do |dialog| dialog.contents = nil dialog.trap_key = nil end end def update_dialogs(key = nil) wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position @dialogs.each do |dialog| dialog.trap_key = nil update_each_dialog(dialog, wrapped_cursor_x, wrapped_cursor_y - screen_scroll_top, key) end end def render_finished Reline::IOGate.buffered_output do render_differential([], 0, 0) lines = @buffer_of_lines.size.times.map do |i| line = Reline::Unicode.strip_non_printing_start_end(prompt_list[i]) + modified_lines[i] wrapped_lines = split_line_by_width(line, screen_width) wrapped_lines.last.empty? ? "#{line} " : line end Reline::IOGate.write lines.map { |l| "#{l}\r\n" }.join end end def print_nomultiline_prompt Reline::IOGate.disable_auto_linewrap(true) if Reline::IOGate.win? # Readline's test `TestRelineAsReadline#test_readline` requires first output to be prompt, not cursor reset escape sequence. Reline::IOGate.write Reline::Unicode.strip_non_printing_start_end(@prompt) if @prompt && !@is_multiline ensure Reline::IOGate.disable_auto_linewrap(false) if Reline::IOGate.win? end def render wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position new_lines = wrapped_prompt_and_input_lines.flatten(1)[screen_scroll_top, screen_height].map do |prompt, line| prompt_width = Reline::Unicode.calculate_width(prompt, true) [[0, prompt_width, prompt], [prompt_width, Reline::Unicode.calculate_width(line, true), line]] end if @menu_info @menu_info.lines(screen_width).each do |item| new_lines << [[0, Reline::Unicode.calculate_width(item), item]] end @menu_info = nil # TODO: do not change state here end @dialogs.each_with_index do |dialog, index| next unless dialog.contents x_range, y_range = dialog_range dialog, wrapped_cursor_y - screen_scroll_top y_range.each do |row| next if row < 0 || row >= screen_height dialog_rows = new_lines[row] ||= [] # index 0 is for prompt, index 1 is for line, index 2.. is for dialog dialog_rows[index + 2] = [x_range.begin, dialog.width, dialog.contents[row - y_range.begin]] end end Reline::IOGate.buffered_output do render_differential new_lines, wrapped_cursor_x, wrapped_cursor_y - screen_scroll_top end end # Reflects lines to be rendered and new cursor position to the screen # by calculating the difference from the previous render. private def render_differential(new_lines, new_cursor_x, new_cursor_y) Reline::IOGate.disable_auto_linewrap(true) if Reline::IOGate.win? rendered_lines = @rendered_screen.lines cursor_y = @rendered_screen.cursor_y if new_lines != rendered_lines # Hide cursor while rendering to avoid cursor flickering. Reline::IOGate.hide_cursor num_lines = [[new_lines.size, rendered_lines.size].max, screen_height].min if @rendered_screen.base_y + num_lines > screen_height Reline::IOGate.scroll_down(num_lines - cursor_y - 1) @rendered_screen.base_y = screen_height - num_lines cursor_y = num_lines - 1 end num_lines.times do |i| rendered_line = rendered_lines[i] || [] line_to_render = new_lines[i] || [] next if rendered_line == line_to_render Reline::IOGate.move_cursor_down i - cursor_y cursor_y = i unless rendered_lines[i] Reline::IOGate.move_cursor_column 0 Reline::IOGate.erase_after_cursor end render_line_differential(rendered_line, line_to_render) end @rendered_screen.lines = new_lines Reline::IOGate.show_cursor end Reline::IOGate.move_cursor_column new_cursor_x Reline::IOGate.move_cursor_down new_cursor_y - cursor_y @rendered_screen.cursor_y = new_cursor_y ensure Reline::IOGate.disable_auto_linewrap(false) if Reline::IOGate.win? end private def clear_rendered_screen_cache @rendered_screen.lines = [] @rendered_screen.cursor_y = 0 end def upper_space_height(wrapped_cursor_y) wrapped_cursor_y - screen_scroll_top end def rest_height(wrapped_cursor_y) screen_height - wrapped_cursor_y + screen_scroll_top - @rendered_screen.base_y - 1 end def rerender render unless @in_pasting end class DialogProcScope CompletionJourneyData = Struct.new(:preposing, :postposing, :list, :pointer) def initialize(line_editor, config, proc_to_exec, context) @line_editor = line_editor @config = config @proc_to_exec = proc_to_exec @context = context @cursor_pos = Reline::CursorPos.new end def context @context end def retrieve_completion_block(_unused = false) preposing, target, postposing, _quote = @line_editor.retrieve_completion_block [preposing, target, postposing] end def call_completion_proc_with_checking_args(pre, target, post) @line_editor.call_completion_proc_with_checking_args(pre, target, post) end def set_dialog(dialog) @dialog = dialog end def dialog @dialog end def set_cursor_pos(col, row) @cursor_pos.x = col @cursor_pos.y = row end def set_key(key) @key = key end def key @key end def cursor_pos @cursor_pos end def just_cursor_moving @line_editor.instance_variable_get(:@just_cursor_moving) end def screen_width @line_editor.screen_width end def screen_height @line_editor.screen_height end def preferred_dialog_height _wrapped_cursor_x, wrapped_cursor_y = @line_editor.wrapped_cursor_position [@line_editor.upper_space_height(wrapped_cursor_y), @line_editor.rest_height(wrapped_cursor_y), (screen_height + 6) / 5].max end def completion_journey_data @line_editor.dialog_proc_scope_completion_journey_data end def config @config end def call instance_exec(&@proc_to_exec) end end class Dialog attr_reader :name, :contents, :width attr_accessor :scroll_top, :pointer, :column, :vertical_offset, :trap_key def initialize(name, config, proc_scope) @name = name @config = config @proc_scope = proc_scope @width = nil @scroll_top = 0 @trap_key = nil end def set_cursor_pos(col, row) @proc_scope.set_cursor_pos(col, row) end def width=(v) @width = v end def contents=(contents) @contents = contents if contents and @width.nil? @width = contents.map{ |line| Reline::Unicode.calculate_width(line, true) }.max end end def call(key) @proc_scope.set_dialog(self) @proc_scope.set_key(key) dialog_render_info = @proc_scope.call if @trap_key if @trap_key.any?{ |i| i.is_a?(Array) } # multiple trap @trap_key.each do |t| @config.add_oneshot_key_binding(t, @name) end else @config.add_oneshot_key_binding(@trap_key, @name) end end dialog_render_info end end def add_dialog_proc(name, p, context = nil) dialog = Dialog.new(name, @config, DialogProcScope.new(self, @config, p, context)) if index = @dialogs.find_index { |d| d.name == name } @dialogs[index] = dialog else @dialogs << dialog end end DIALOG_DEFAULT_HEIGHT = 20 private def dialog_range(dialog, dialog_y) x_range = dialog.column...dialog.column + dialog.width y_range = dialog_y + dialog.vertical_offset...dialog_y + dialog.vertical_offset + dialog.contents.size [x_range, y_range] end private def update_each_dialog(dialog, cursor_column, cursor_row, key = nil) dialog.set_cursor_pos(cursor_column, cursor_row) dialog_render_info = dialog.call(key) if dialog_render_info.nil? or dialog_render_info.contents.nil? or dialog_render_info.contents.empty? dialog.contents = nil dialog.trap_key = nil return end contents = dialog_render_info.contents pointer = dialog.pointer if dialog_render_info.width dialog.width = dialog_render_info.width else dialog.width = contents.map { |l| calculate_width(l, true) }.max end height = dialog_render_info.height || DIALOG_DEFAULT_HEIGHT height = contents.size if contents.size < height if contents.size > height if dialog.pointer if dialog.pointer < 0 dialog.scroll_top = 0 elsif (dialog.pointer - dialog.scroll_top) >= (height - 1) dialog.scroll_top = dialog.pointer - (height - 1) elsif (dialog.pointer - dialog.scroll_top) < 0 dialog.scroll_top = dialog.pointer end pointer = dialog.pointer - dialog.scroll_top else dialog.scroll_top = 0 end contents = contents[dialog.scroll_top, height] end if dialog_render_info.scrollbar and dialog_render_info.contents.size > height bar_max_height = height * 2 moving_distance = (dialog_render_info.contents.size - height) * 2 position_ratio = dialog.scroll_top.zero? ? 0.0 : ((dialog.scroll_top * 2).to_f / moving_distance) bar_height = (bar_max_height * ((contents.size * 2).to_f / (dialog_render_info.contents.size * 2))).floor.to_i bar_height = MINIMUM_SCROLLBAR_HEIGHT if bar_height < MINIMUM_SCROLLBAR_HEIGHT scrollbar_pos = ((bar_max_height - bar_height) * position_ratio).floor.to_i else scrollbar_pos = nil end dialog.column = dialog_render_info.pos.x dialog.width += @block_elem_width if scrollbar_pos diff = (dialog.column + dialog.width) - screen_width if diff > 0 dialog.column -= diff end if rest_height(screen_scroll_top + cursor_row) - dialog_render_info.pos.y >= height dialog.vertical_offset = dialog_render_info.pos.y + 1 elsif cursor_row >= height dialog.vertical_offset = dialog_render_info.pos.y - height else dialog.vertical_offset = dialog_render_info.pos.y + 1 end if dialog.column < 0 dialog.column = 0 dialog.width = screen_width end face = Reline::Face[dialog_render_info.face || :default] scrollbar_sgr = face[:scrollbar] default_sgr = face[:default] enhanced_sgr = face[:enhanced] dialog.contents = contents.map.with_index do |item, i| line_sgr = i == pointer ? enhanced_sgr : default_sgr str_width = dialog.width - (scrollbar_pos.nil? ? 0 : @block_elem_width) str, = Reline::Unicode.take_mbchar_range(item, 0, str_width, padding: true) colored_content = "#{line_sgr}#{str}" if scrollbar_pos if scrollbar_pos <= (i * 2) and (i * 2 + 1) < (scrollbar_pos + bar_height) colored_content + scrollbar_sgr + @full_block elsif scrollbar_pos <= (i * 2) and (i * 2) < (scrollbar_pos + bar_height) colored_content + scrollbar_sgr + @upper_half_block elsif scrollbar_pos <= (i * 2 + 1) and (i * 2) < (scrollbar_pos + bar_height) colored_content + scrollbar_sgr + @lower_half_block else colored_content + scrollbar_sgr + ' ' * @block_elem_width end else colored_content end end end private def modify_lines(before, complete) if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: complete) after.lines("\n").map { |l| l.chomp('') } else before.map { |l| Reline::Unicode.escape_for_print(l) } end end def editing_mode @config.editing_mode end private def menu(list) @menu_info = MenuInfo.new(list) end private def filter_normalize_candidates(target, list) target = target.downcase if @config.completion_ignore_case list.select do |item| next unless item unless Encoding.compatible?(target.encoding, item.encoding) # Workaround for Readline test if defined?(::Readline) && ::Readline == ::Reline raise Encoding::CompatibilityError, "incompatible character encodings: #{target.encoding} and #{item.encoding}" end end if @config.completion_ignore_case item.downcase.start_with?(target) else item.start_with?(target) end end.map do |item| item.unicode_normalize rescue Encoding::CompatibilityError item end.uniq end private def perform_completion(preposing, target, postposing, quote, list) candidates = filter_normalize_candidates(target, list) case @completion_state when CompletionState::PERFECT_MATCH if @dig_perfect_match_proc @dig_perfect_match_proc.call(@perfect_matched) return end when CompletionState::MENU menu(candidates) return when CompletionState::MENU_WITH_PERFECT_MATCH menu(candidates) @completion_state = CompletionState::PERFECT_MATCH return end completed = Reline::Unicode.common_prefix(candidates, ignore_case: @config.completion_ignore_case) return if completed.empty? append_character = '' if candidates.include?(completed) if candidates.one? append_character = quote || completion_append_character.to_s @completion_state = CompletionState::PERFECT_MATCH elsif @config.show_all_if_ambiguous menu(candidates) @completion_state = CompletionState::PERFECT_MATCH else @completion_state = CompletionState::MENU_WITH_PERFECT_MATCH end @perfect_matched = completed else @completion_state = CompletionState::MENU menu(candidates) if @config.show_all_if_ambiguous end @buffer_of_lines[@line_index] = (preposing + completed + append_character + postposing).split("\n")[@line_index] || String.new(encoding: encoding) line_to_pointer = (preposing + completed + append_character).split("\n")[@line_index] || String.new(encoding: encoding) @byte_pointer = line_to_pointer.bytesize end def dialog_proc_scope_completion_journey_data return nil unless @completion_journey_state line_index = @completion_journey_state.line_index pre_lines = @buffer_of_lines[0...line_index].map { |line| line + "\n" } post_lines = @buffer_of_lines[(line_index + 1)..-1].map { |line| line + "\n" } DialogProcScope::CompletionJourneyData.new( pre_lines.join + @completion_journey_state.pre, @completion_journey_state.post + post_lines.join, @completion_journey_state.list, @completion_journey_state.pointer ) end private def move_completed_list(direction) @completion_journey_state ||= retrieve_completion_journey_state return false unless @completion_journey_state if (delta = { up: -1, down: +1 }[direction]) @completion_journey_state.pointer = (@completion_journey_state.pointer + delta) % @completion_journey_state.list.size end completed = @completion_journey_state.list[@completion_journey_state.pointer] set_current_line(@completion_journey_state.pre + completed + @completion_journey_state.post, @completion_journey_state.pre.bytesize + completed.bytesize) true end private def retrieve_completion_journey_state preposing, target, postposing, quote = retrieve_completion_block list = call_completion_proc(preposing, target, postposing, quote) return unless list.is_a?(Array) candidates = list.select{ |item| item.start_with?(target) } return if candidates.empty? pre = preposing.split("\n", -1).last || '' post = postposing.split("\n", -1).first || '' CompletionJourneyState.new( @line_index, pre, target, post, [target] + candidates, 0 ) end private def run_for_operators(key, method_symbol, &block) if @vi_waiting_operator if VI_MOTIONS.include?(method_symbol) old_byte_pointer = @byte_pointer @vi_arg = (@vi_arg || 1) * @vi_waiting_operator_arg block.(true) unless @waiting_proc byte_pointer_diff = @byte_pointer - old_byte_pointer @byte_pointer = old_byte_pointer method_obj = method(@vi_waiting_operator) wrap_method_call(@vi_waiting_operator, method_obj, byte_pointer_diff) cleanup_waiting end else # Ignores operator when not motion is given. block.(false) cleanup_waiting end @vi_arg = nil else block.(false) end end private def argumentable?(method_obj) method_obj and method_obj.parameters.any? { |param| param[0] == :key and param[1] == :arg } end private def inclusive?(method_obj) # If a motion method with the keyword argument "inclusive" follows the # operator, it must contain the character at the cursor position. method_obj and method_obj.parameters.any? { |param| param[0] == :key and param[1] == :inclusive } end def wrap_method_call(method_symbol, method_obj, key, with_operator = false) if @config.editing_mode_is?(:emacs, :vi_insert) and @vi_waiting_operator.nil? not_insertion = method_symbol != :ed_insert process_insert(force: not_insertion) end if @vi_arg and argumentable?(method_obj) if with_operator and inclusive?(method_obj) method_obj.(key, arg: @vi_arg, inclusive: true) else method_obj.(key, arg: @vi_arg) end else if with_operator and inclusive?(method_obj) method_obj.(key, inclusive: true) else method_obj.(key) end end end private def cleanup_waiting @waiting_proc = nil @vi_waiting_operator = nil @vi_waiting_operator_arg = nil @searching_prompt = nil @drop_terminate_spaces = false end ARGUMENT_DIGIT_METHODS = %i[ed_digit vi_zero ed_argument_digit] VI_WAITING_ACCEPT_METHODS = %i[vi_change_meta vi_delete_meta vi_yank ed_insert ed_argument_digit] private def process_key(key, method_symbol) if @waiting_proc cleanup_waiting unless key.size == 1 end if @vi_waiting_operator cleanup_waiting unless VI_WAITING_ACCEPT_METHODS.include?(method_symbol) || VI_MOTIONS.include?(method_symbol) end if @waiting_proc old_byte_pointer = @byte_pointer @waiting_proc.call(key) if @vi_waiting_operator byte_pointer_diff = @byte_pointer - old_byte_pointer @byte_pointer = old_byte_pointer method_obj = method(@vi_waiting_operator) wrap_method_call(@vi_waiting_operator, method_obj, byte_pointer_diff) cleanup_waiting end @kill_ring.process return end # Reject multibyte input (converted to ed_insert) in vi_command mode return if method_symbol == :ed_insert && @config.editing_mode_is?(:vi_command) if method_symbol and respond_to?(method_symbol, true) method_obj = method(method_symbol) end if @vi_arg if ARGUMENT_DIGIT_METHODS.include?(method_symbol) ed_argument_digit(key) else if argumentable?(method_obj) run_for_operators(key, method_symbol) do |with_operator| wrap_method_call(method_symbol, method_obj, key, with_operator) end elsif method_obj wrap_method_call(method_symbol, method_obj, key) end @kill_ring.process @vi_arg = nil end elsif method_obj if method_symbol == :ed_argument_digit wrap_method_call(method_symbol, method_obj, key) else run_for_operators(key, method_symbol) do |with_operator| wrap_method_call(method_symbol, method_obj, key, with_operator) end end @kill_ring.process end end def update(key) modified = input_key(key) unless @in_pasting scroll_into_view @just_cursor_moving = !modified update_dialogs(key) @just_cursor_moving = false end end def input_key(key) save_old_buffer @config.reset_oneshot_key_bindings if key.char.nil? process_insert(force: true) @eof = buffer_empty? finish return end @dialogs.each do |dialog| if key.method_symbol == dialog.name return end end @completion_occurs = false process_key(key.char, key.method_symbol) if @config.editing_mode_is?(:vi_command) and @byte_pointer > 0 and @byte_pointer == current_line.bytesize byte_size = Reline::Unicode.get_prev_mbchar_size(@buffer_of_lines[@line_index], @byte_pointer) @byte_pointer -= byte_size end @prev_action_state, @next_action_state = @next_action_state, NullActionState unless @completion_occurs @completion_state = CompletionState::NORMAL @completion_journey_state = nil end push_input_lines unless @restoring @restoring = false if @in_pasting clear_dialogs return end modified = @old_buffer_of_lines != @buffer_of_lines if !@completion_occurs && modified && !@config.disable_completion && @config.autocompletion # Auto complete starts only when edited process_insert(force: true) @completion_journey_state = retrieve_completion_journey_state end modified end def save_old_buffer @old_buffer_of_lines = @buffer_of_lines.dup end def push_input_lines if @old_buffer_of_lines == @buffer_of_lines @input_lines[@input_lines_position] = [@buffer_of_lines.dup, @byte_pointer, @line_index] else @input_lines = @input_lines[0..@input_lines_position] @input_lines_position += 1 @input_lines.push([@buffer_of_lines.dup, @byte_pointer, @line_index]) end trim_input_lines end MAX_INPUT_LINES = 100 def trim_input_lines if @input_lines.size > MAX_INPUT_LINES @input_lines.shift @input_lines_position -= 1 end end def scroll_into_view _wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position if wrapped_cursor_y < screen_scroll_top @scroll_partial_screen = wrapped_cursor_y end if wrapped_cursor_y >= screen_scroll_top + screen_height @scroll_partial_screen = wrapped_cursor_y - screen_height + 1 end end def call_completion_proc(pre, target, post, quote) Reline.core.instance_variable_set(:@completion_quote_character, quote) result = call_completion_proc_with_checking_args(pre, target, post) Reline.core.instance_variable_set(:@completion_quote_character, nil) result end def call_completion_proc_with_checking_args(pre, target, post) if @completion_proc and target argnum = @completion_proc.parameters.inject(0) { |result, item| case item.first when :req, :opt result + 1 when :rest break 3 end } case argnum when 1 result = @completion_proc.(target) when 2 result = @completion_proc.(target, pre) when 3..Float::INFINITY result = @completion_proc.(target, pre, post) end end result end private def process_auto_indent(line_index = @line_index, cursor_dependent: true, add_newline: false) return if @in_pasting return unless @auto_indent_proc line = @buffer_of_lines[line_index] byte_pointer = cursor_dependent && @line_index == line_index ? @byte_pointer : line.bytesize new_indent = @auto_indent_proc.(@buffer_of_lines.take(line_index + 1).push(''), line_index, byte_pointer, add_newline) return unless new_indent new_line = ' ' * new_indent + line.lstrip @buffer_of_lines[line_index] = new_line if @line_index == line_index indent_diff = new_line.bytesize - line.bytesize @byte_pointer = [@byte_pointer + indent_diff, 0].max end end def line() @buffer_of_lines.join("\n") unless eof? end def current_line @buffer_of_lines[@line_index] end def set_current_line(line, byte_pointer = nil) cursor = current_byte_pointer_cursor @buffer_of_lines[@line_index] = line if byte_pointer @byte_pointer = byte_pointer else calculate_nearest_cursor(cursor) end process_auto_indent end def retrieve_completion_block quote_characters = Reline.completer_quote_characters before = current_line.byteslice(0, @byte_pointer).grapheme_clusters quote = nil # Calcualte closing quote when cursor is at the end of the line if current_line.bytesize == @byte_pointer && !quote_characters.empty? escaped = false before.each do |c| if escaped escaped = false next elsif c == '\\' escaped = true elsif quote quote = nil if c == quote elsif quote_characters.include?(c) quote = c end end end word_break_characters = quote_characters + Reline.completer_word_break_characters break_index = before.rindex { |c| word_break_characters.include?(c) || quote_characters.include?(c) } || -1 preposing = before.take(break_index + 1).join target = before.drop(break_index + 1).join postposing = current_line.byteslice(@byte_pointer, current_line.bytesize - @byte_pointer) lines = whole_lines if @line_index > 0 preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing end if (lines.size - 1) > @line_index postposing = postposing + "\n" + lines[(@line_index + 1)..-1].join("\n") end [preposing.encode(encoding), target.encode(encoding), postposing.encode(encoding), quote&.encode(encoding)] end def confirm_multiline_termination temp_buffer = @buffer_of_lines.dup @confirm_multiline_termination_proc.(temp_buffer.join("\n") + "\n") end def insert_multiline_text(text) pre = @buffer_of_lines[@line_index].byteslice(0, @byte_pointer) post = @buffer_of_lines[@line_index].byteslice(@byte_pointer..) lines = (pre + Reline::Unicode.safe_encode(text, encoding).gsub(/\r\n?/, "\n") + post).split("\n", -1) lines << '' if lines.empty? @buffer_of_lines[@line_index, 1] = lines @line_index += lines.size - 1 @byte_pointer = @buffer_of_lines[@line_index].bytesize - post.bytesize end def insert_text(text) if @buffer_of_lines[@line_index].bytesize == @byte_pointer @buffer_of_lines[@line_index] += text else @buffer_of_lines[@line_index] = byteinsert(@buffer_of_lines[@line_index], @byte_pointer, text) end @byte_pointer += text.bytesize process_auto_indent end def delete_text(start = nil, length = nil) if start.nil? and length.nil? if @buffer_of_lines.size == 1 @buffer_of_lines[@line_index] = '' @byte_pointer = 0 elsif @line_index == (@buffer_of_lines.size - 1) and @line_index > 0 @buffer_of_lines.pop @line_index -= 1 @byte_pointer = 0 elsif @line_index < (@buffer_of_lines.size - 1) @buffer_of_lines.delete_at(@line_index) @byte_pointer = 0 end elsif not start.nil? and not length.nil? if current_line before = current_line.byteslice(0, start) after = current_line.byteslice(start + length, current_line.bytesize) set_current_line(before + after) end elsif start.is_a?(Range) range = start first = range.first last = range.last last = current_line.bytesize - 1 if last > current_line.bytesize last += current_line.bytesize if last < 0 first += current_line.bytesize if first < 0 range = range.exclude_end? ? first...last : first..last line = current_line.bytes.reject.with_index{ |c, i| range.include?(i) }.map{ |c| c.chr(Encoding::ASCII_8BIT) }.join.force_encoding(encoding) set_current_line(line) else set_current_line(current_line.byteslice(0, start)) end end def byte_pointer=(val) @byte_pointer = val end def whole_lines @buffer_of_lines.dup end def whole_buffer whole_lines.join("\n") end private def buffer_empty? current_line.empty? and @buffer_of_lines.size == 1 end def finished? @finished end def finish @finished = true @config.reset end private def byteslice!(str, byte_pointer, size) new_str = str.byteslice(0, byte_pointer) new_str << str.byteslice(byte_pointer + size, str.bytesize) [new_str, str.byteslice(byte_pointer, size)] end private def byteinsert(str, byte_pointer, other) new_str = str.byteslice(0, byte_pointer) new_str << other new_str << str.byteslice(byte_pointer, str.bytesize) new_str end private def calculate_width(str, allow_escape_code = false) Reline::Unicode.calculate_width(str, allow_escape_code) end private def key_delete(key) if @config.editing_mode_is?(:vi_insert) ed_delete_next_char(key) elsif @config.editing_mode_is?(:emacs) em_delete(key) end end private def key_newline(key) if @is_multiline next_line = current_line.byteslice(@byte_pointer, current_line.bytesize - @byte_pointer) cursor_line = current_line.byteslice(0, @byte_pointer) insert_new_line(cursor_line, next_line) end end private def complete(_key) return if @config.disable_completion process_insert(force: true) if @config.autocompletion @completion_state = CompletionState::NORMAL @completion_occurs = move_completed_list(:down) else @completion_journey_state = nil pre, target, post, quote = retrieve_completion_block result = call_completion_proc(pre, target, post, quote) if result.is_a?(Array) @completion_occurs = true perform_completion(pre, target, post, quote, result) end end end private def completion_journey_move(direction) return if @config.disable_completion process_insert(force: true) @completion_state = CompletionState::NORMAL @completion_occurs = move_completed_list(direction) end private def menu_complete(_key) completion_journey_move(:down) end private def menu_complete_backward(_key) completion_journey_move(:up) end private def completion_journey_up(_key) completion_journey_move(:up) if @config.autocompletion end # Editline:: +ed-unassigned+ This editor command always results in an error. # GNU Readline:: There is no corresponding macro. private def ed_unassigned(key) end # do nothing private def process_insert(force: false) return if @continuous_insertion_buffer.empty? or (@in_pasting and not force) insert_text(@continuous_insertion_buffer) @continuous_insertion_buffer.clear end # Editline:: +ed-insert+ (vi input: almost all; emacs: printable characters) # In insert mode, insert the input character left of the cursor # position. In replace mode, overwrite the character at the # cursor and move the cursor to the right by one character # position. Accept an argument to do this repeatedly. It is an # error if the input character is the NUL character (+Ctrl-@+). # Failure to enlarge the edit buffer also results in an error. # Editline:: +ed-digit+ (emacs: 0 to 9) If in argument input mode, append # the input digit to the argument being read. Otherwise, call # +ed-insert+. It is an error if the input character is not a # digit or if the existing argument is already greater than a # million. # GNU Readline:: +self-insert+ (a, b, A, 1, !, …) Insert yourself. private def ed_insert(str) begin str.encode(Encoding::UTF_8) rescue Encoding::UndefinedConversionError return end if @in_pasting @continuous_insertion_buffer << str return elsif not @continuous_insertion_buffer.empty? process_insert end insert_text(str) end alias_method :ed_digit, :ed_insert alias_method :self_insert, :ed_insert private def insert_raw_char(str, arg: 1) arg.times do if str == "\C-j" or str == "\C-m" key_newline(str) elsif str != "\0" # Ignore NUL. ed_insert(str) end end end private def ed_next_char(key, arg: 1) byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer) if (@byte_pointer < current_line.bytesize) @byte_pointer += byte_size elsif @config.editing_mode_is?(:emacs) and @byte_pointer == current_line.bytesize and @line_index < @buffer_of_lines.size - 1 @byte_pointer = 0 @line_index += 1 end arg -= 1 ed_next_char(key, arg: arg) if arg > 0 end alias_method :forward_char, :ed_next_char private def ed_prev_char(key, arg: 1) if @byte_pointer > 0 byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer) @byte_pointer -= byte_size elsif @config.editing_mode_is?(:emacs) and @byte_pointer == 0 and @line_index > 0 @line_index -= 1 @byte_pointer = current_line.bytesize end arg -= 1 ed_prev_char(key, arg: arg) if arg > 0 end alias_method :backward_char, :ed_prev_char private def vi_first_print(key) @byte_pointer = Reline::Unicode.vi_first_print(current_line) end private def ed_move_to_beg(key) @byte_pointer = 0 end alias_method :beginning_of_line, :ed_move_to_beg alias_method :vi_zero, :ed_move_to_beg private def ed_move_to_end(key) @byte_pointer = current_line.bytesize end alias_method :end_of_line, :ed_move_to_end private def generate_searcher(search_key) search_word = String.new(encoding: encoding) hit_pointer = nil lambda do |key| search_again = false case key when "\C-h", "\C-?" grapheme_clusters = search_word.grapheme_clusters if grapheme_clusters.size > 0 grapheme_clusters.pop search_word = grapheme_clusters.join end when "\C-r", "\C-s" search_again = true if search_key == key search_key = key else search_word << key end hit = nil if not search_word.empty? and @line_backup_in_history&.include?(search_word) hit_pointer = Reline::HISTORY.size hit = @line_backup_in_history else if search_again if search_word.empty? and Reline.last_incremental_search search_word = Reline.last_incremental_search end if @history_pointer case search_key when "\C-r" history_pointer_base = 0 history = Reline::HISTORY[0..(@history_pointer - 1)] when "\C-s" history_pointer_base = @history_pointer + 1 history = Reline::HISTORY[(@history_pointer + 1)..-1] end else history_pointer_base = 0 history = Reline::HISTORY end elsif @history_pointer case search_key when "\C-r" history_pointer_base = 0 history = Reline::HISTORY[0..@history_pointer] when "\C-s" history_pointer_base = @history_pointer history = Reline::HISTORY[@history_pointer..-1] end else history_pointer_base = 0 history = Reline::HISTORY end case search_key when "\C-r" hit_index = history.rindex { |item| item.include?(search_word) } when "\C-s" hit_index = history.index { |item| item.include?(search_word) } end if hit_index hit_pointer = history_pointer_base + hit_index hit = Reline::HISTORY[hit_pointer] end end case search_key when "\C-r" prompt_name = 'reverse-i-search' when "\C-s" prompt_name = 'i-search' end prompt_name = "failed #{prompt_name}" unless hit [search_word, prompt_name, hit_pointer] end end private def incremental_search_history(key) backup = @buffer_of_lines.dup, @line_index, @byte_pointer, @history_pointer, @line_backup_in_history searcher = generate_searcher(key) @searching_prompt = "(reverse-i-search)`': " termination_keys = ["\C-j"] termination_keys.concat(@config.isearch_terminators.chars) if @config.isearch_terminators @waiting_proc = ->(k) { if k == "\C-g" # cancel search and restore buffer @buffer_of_lines, @line_index, @byte_pointer, @history_pointer, @line_backup_in_history = backup @searching_prompt = nil @waiting_proc = nil elsif !termination_keys.include?(k) && (k.match?(/[[:print:]]/) || k == "\C-h" || k == "\C-?" || k == "\C-r" || k == "\C-s") search_word, prompt_name, hit_pointer = searcher.call(k) Reline.last_incremental_search = search_word @searching_prompt = "(%s)`%s'" % [prompt_name, search_word] @searching_prompt += ': ' unless @is_multiline move_history(hit_pointer, line: :end, cursor: :end) if hit_pointer else # terminaton_keys and other keys will terminalte move_history(@history_pointer, line: :end, cursor: :start) @searching_prompt = nil @waiting_proc = nil end } end private def vi_search_prev(key) incremental_search_history(key) end alias_method :reverse_search_history, :vi_search_prev private def vi_search_next(key) incremental_search_history(key) end alias_method :forward_search_history, :vi_search_next private def search_history(prefix, pointer_range) pointer_range.each do |pointer| lines = Reline::HISTORY[pointer].split("\n") lines.each_with_index do |line, index| return [pointer, index] if line.start_with?(prefix) end end nil end private def ed_search_prev_history(key, arg: 1) substr = prev_action_state_value(:search_history) == :empty ? '' : current_line.byteslice(0, @byte_pointer) return if @history_pointer == 0 return if @history_pointer.nil? && substr.empty? && !current_line.empty? history_range = 0...(@history_pointer || Reline::HISTORY.size) h_pointer, line_index = search_history(substr, history_range.reverse_each) return unless h_pointer move_history(h_pointer, line: line_index || :start, cursor: substr.empty? ? :end : @byte_pointer) arg -= 1 set_next_action_state(:search_history, :empty) if substr.empty? ed_search_prev_history(key, arg: arg) if arg > 0 end alias_method :history_search_backward, :ed_search_prev_history private def ed_search_next_history(key, arg: 1) substr = prev_action_state_value(:search_history) == :empty ? '' : current_line.byteslice(0, @byte_pointer) return if @history_pointer.nil? history_range = @history_pointer + 1...Reline::HISTORY.size h_pointer, line_index = search_history(substr, history_range) return if h_pointer.nil? and not substr.empty? move_history(h_pointer, line: line_index || :start, cursor: substr.empty? ? :end : @byte_pointer) arg -= 1 set_next_action_state(:search_history, :empty) if substr.empty? ed_search_next_history(key, arg: arg) if arg > 0 end alias_method :history_search_forward, :ed_search_next_history private def move_history(history_pointer, line:, cursor:) history_pointer ||= Reline::HISTORY.size return if history_pointer < 0 || history_pointer > Reline::HISTORY.size old_history_pointer = @history_pointer || Reline::HISTORY.size if old_history_pointer == Reline::HISTORY.size @line_backup_in_history = whole_buffer else Reline::HISTORY[old_history_pointer] = whole_buffer end if history_pointer == Reline::HISTORY.size buf = @line_backup_in_history @history_pointer = @line_backup_in_history = nil else buf = Reline::HISTORY[history_pointer] @history_pointer = history_pointer end @buffer_of_lines = buf.split("\n") @buffer_of_lines = [String.new(encoding: encoding)] if @buffer_of_lines.empty? @line_index = line == :start ? 0 : line == :end ? @buffer_of_lines.size - 1 : line @byte_pointer = cursor == :start ? 0 : cursor == :end ? current_line.bytesize : cursor end private def ed_prev_history(key, arg: 1) if @line_index > 0 cursor = current_byte_pointer_cursor @line_index -= 1 calculate_nearest_cursor(cursor) return end move_history( (@history_pointer || Reline::HISTORY.size) - 1, line: :end, cursor: @config.editing_mode_is?(:vi_command) ? :start : :end, ) arg -= 1 ed_prev_history(key, arg: arg) if arg > 0 end alias_method :previous_history, :ed_prev_history private def ed_next_history(key, arg: 1) if @line_index < (@buffer_of_lines.size - 1) cursor = current_byte_pointer_cursor @line_index += 1 calculate_nearest_cursor(cursor) return end move_history( (@history_pointer || Reline::HISTORY.size) + 1, line: :start, cursor: @config.editing_mode_is?(:vi_command) ? :start : :end, ) arg -= 1 ed_next_history(key, arg: arg) if arg > 0 end alias_method :next_history, :ed_next_history private def ed_newline(key) process_insert(force: true) if @is_multiline if @config.editing_mode_is?(:vi_command) if @line_index < (@buffer_of_lines.size - 1) ed_next_history(key) # means cursor down else # should check confirm_multiline_termination to finish? finish end else if @line_index == (@buffer_of_lines.size - 1) if confirm_multiline_termination finish else key_newline(key) end else # should check confirm_multiline_termination to finish? @line_index = @buffer_of_lines.size - 1 @byte_pointer = current_line.bytesize finish end end else finish end end private def em_delete_prev_char(key, arg: 1) arg.times do if @byte_pointer == 0 and @line_index > 0 @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index) @line_index -= 1 elsif @byte_pointer > 0 byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer) line, = byteslice!(current_line, @byte_pointer - byte_size, byte_size) set_current_line(line, @byte_pointer - byte_size) end end process_auto_indent end alias_method :backward_delete_char, :em_delete_prev_char # Editline:: +ed-kill-line+ (vi command: +D+, +Ctrl-K+; emacs: +Ctrl-K+, # +Ctrl-U+) + Kill from the cursor to the end of the line. # GNU Readline:: +kill-line+ (+C-k+) Kill the text from point to the end of # the line. With a negative numeric argument, kill backward # from the cursor to the beginning of the current line. private def ed_kill_line(key) if current_line.bytesize > @byte_pointer line, deleted = byteslice!(current_line, @byte_pointer, current_line.bytesize - @byte_pointer) set_current_line(line, line.bytesize) @kill_ring.append(deleted) elsif @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1 set_current_line(current_line + @buffer_of_lines.delete_at(@line_index + 1), current_line.bytesize) end end alias_method :kill_line, :ed_kill_line # Editline:: +vi_change_to_eol+ (vi command: +C+) + Kill and change from the cursor to the end of the line. private def vi_change_to_eol(key) ed_kill_line(key) @config.editing_mode = :vi_insert end # Editline:: +vi-kill-line-prev+ (vi: +Ctrl-U+) Delete the string from the # beginning of the edit buffer to the cursor and save it to the # cut buffer. # GNU Readline:: +unix-line-discard+ (+C-u+) Kill backward from the cursor # to the beginning of the current line. private def vi_kill_line_prev(key) if @byte_pointer > 0 line, deleted = byteslice!(current_line, 0, @byte_pointer) set_current_line(line, 0) @kill_ring.append(deleted, true) end end alias_method :unix_line_discard, :vi_kill_line_prev # Editline:: +em-kill-line+ (not bound) Delete the entire contents of the # edit buffer and save it to the cut buffer. +vi-kill-line-prev+ # GNU Readline:: +kill-whole-line+ (not bound) Kill all characters on the # current line, no matter where point is. private def em_kill_line(key) if current_line.size > 0 @kill_ring.append(current_line.dup, true) set_current_line('', 0) end end alias_method :kill_whole_line, :em_kill_line private def em_delete(key) if buffer_empty? and key == "\C-d" @eof = true finish elsif @byte_pointer < current_line.bytesize splitted_last = current_line.byteslice(@byte_pointer, current_line.bytesize) mbchar = splitted_last.grapheme_clusters.first line, = byteslice!(current_line, @byte_pointer, mbchar.bytesize) set_current_line(line) elsif @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1 set_current_line(current_line + @buffer_of_lines.delete_at(@line_index + 1), current_line.bytesize) end end alias_method :delete_char, :em_delete private def em_delete_or_list(key) if current_line.empty? or @byte_pointer < current_line.bytesize em_delete(key) elsif !@config.autocompletion # show completed list pre, target, post, quote = retrieve_completion_block result = call_completion_proc(pre, target, post, quote) if result.is_a?(Array) candidates = filter_normalize_candidates(target, result) menu(candidates) end end end alias_method :delete_char_or_list, :em_delete_or_list private def em_yank(key) yanked = @kill_ring.yank insert_text(yanked) if yanked end alias_method :yank, :em_yank private def em_yank_pop(key) yanked, prev_yank = @kill_ring.yank_pop if yanked line, = byteslice!(current_line, @byte_pointer - prev_yank.bytesize, prev_yank.bytesize) set_current_line(line, @byte_pointer - prev_yank.bytesize) insert_text(yanked) end end alias_method :yank_pop, :em_yank_pop private def ed_clear_screen(key) Reline::IOGate.clear_screen @screen_size = Reline::IOGate.get_screen_size @rendered_screen.base_y = 0 clear_rendered_screen_cache end alias_method :clear_screen, :ed_clear_screen private def em_next_word(key) if current_line.bytesize > @byte_pointer byte_size = Reline::Unicode.em_forward_word(current_line, @byte_pointer) @byte_pointer += byte_size end end alias_method :forward_word, :em_next_word private def ed_prev_word(key) if @byte_pointer > 0 byte_size = Reline::Unicode.em_backward_word(current_line, @byte_pointer) @byte_pointer -= byte_size end end alias_method :backward_word, :ed_prev_word private def em_delete_next_word(key) if current_line.bytesize > @byte_pointer byte_size = Reline::Unicode.em_forward_word(current_line, @byte_pointer) line, word = byteslice!(current_line, @byte_pointer, byte_size) set_current_line(line) @kill_ring.append(word) end end alias_method :kill_word, :em_delete_next_word private def ed_delete_prev_word(key) if @byte_pointer > 0 byte_size = Reline::Unicode.em_backward_word(current_line, @byte_pointer) line, word = byteslice!(current_line, @byte_pointer - byte_size, byte_size) set_current_line(line, @byte_pointer - byte_size) @kill_ring.append(word, true) end end alias_method :backward_kill_word, :ed_delete_prev_word private def ed_transpose_chars(key) if @byte_pointer > 0 if @byte_pointer < current_line.bytesize byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer) @byte_pointer += byte_size end back1_byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer) if (@byte_pointer - back1_byte_size) > 0 back2_byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer - back1_byte_size) back2_pointer = @byte_pointer - back1_byte_size - back2_byte_size line, back2_mbchar = byteslice!(current_line, back2_pointer, back2_byte_size) set_current_line(byteinsert(line, @byte_pointer - back2_byte_size, back2_mbchar)) end end end alias_method :transpose_chars, :ed_transpose_chars private def ed_transpose_words(key) left_word_start, middle_start, right_word_start, after_start = Reline::Unicode.ed_transpose_words(current_line, @byte_pointer) before = current_line.byteslice(0, left_word_start) left_word = current_line.byteslice(left_word_start, middle_start - left_word_start) middle = current_line.byteslice(middle_start, right_word_start - middle_start) right_word = current_line.byteslice(right_word_start, after_start - right_word_start) after = current_line.byteslice(after_start, current_line.bytesize - after_start) return if left_word.empty? or right_word.empty? from_head_to_left_word = before + right_word + middle + left_word set_current_line(from_head_to_left_word + after, from_head_to_left_word.bytesize) end alias_method :transpose_words, :ed_transpose_words private def em_capitol_case(key) if current_line.bytesize > @byte_pointer byte_size, new_str = Reline::Unicode.em_forward_word_with_capitalization(current_line, @byte_pointer) before = current_line.byteslice(0, @byte_pointer) after = current_line.byteslice((@byte_pointer + byte_size)..-1) set_current_line(before + new_str + after, @byte_pointer + new_str.bytesize) end end alias_method :capitalize_word, :em_capitol_case private def em_lower_case(key) if current_line.bytesize > @byte_pointer byte_size = Reline::Unicode.em_forward_word(current_line, @byte_pointer) part = current_line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar| mbchar =~ /[A-Z]/ ? mbchar.downcase : mbchar }.join rest = current_line.byteslice((@byte_pointer + byte_size)..-1) line = current_line.byteslice(0, @byte_pointer) + part set_current_line(line + rest, line.bytesize) end end alias_method :downcase_word, :em_lower_case private def em_upper_case(key) if current_line.bytesize > @byte_pointer byte_size = Reline::Unicode.em_forward_word(current_line, @byte_pointer) part = current_line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar| mbchar =~ /[a-z]/ ? mbchar.upcase : mbchar }.join rest = current_line.byteslice((@byte_pointer + byte_size)..-1) line = current_line.byteslice(0, @byte_pointer) + part set_current_line(line + rest, line.bytesize) end end alias_method :upcase_word, :em_upper_case private def em_kill_region(key) if @byte_pointer > 0 byte_size = Reline::Unicode.em_big_backward_word(current_line, @byte_pointer) line, deleted = byteslice!(current_line, @byte_pointer - byte_size, byte_size) set_current_line(line, @byte_pointer - byte_size) @kill_ring.append(deleted, true) end end alias_method :unix_word_rubout, :em_kill_region private def copy_for_vi(text) if @config.editing_mode_is?(:vi_insert) or @config.editing_mode_is?(:vi_command) @vi_clipboard = text end end private def vi_insert(key) @config.editing_mode = :vi_insert end private def vi_add(key) @config.editing_mode = :vi_insert ed_next_char(key) end private def vi_command_mode(key) ed_prev_char(key) @config.editing_mode = :vi_command end alias_method :vi_movement_mode, :vi_command_mode private def vi_next_word(key, arg: 1) if current_line.bytesize > @byte_pointer byte_size = Reline::Unicode.vi_forward_word(current_line, @byte_pointer, @drop_terminate_spaces) @byte_pointer += byte_size end arg -= 1 vi_next_word(key, arg: arg) if arg > 0 end private def vi_prev_word(key, arg: 1) if @byte_pointer > 0 byte_size = Reline::Unicode.vi_backward_word(current_line, @byte_pointer) @byte_pointer -= byte_size end arg -= 1 vi_prev_word(key, arg: arg) if arg > 0 end private def vi_end_word(key, arg: 1, inclusive: false) if current_line.bytesize > @byte_pointer byte_size = Reline::Unicode.vi_forward_end_word(current_line, @byte_pointer) @byte_pointer += byte_size end arg -= 1 if inclusive and arg.zero? byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer) if byte_size > 0 @byte_pointer += byte_size end end vi_end_word(key, arg: arg) if arg > 0 end private def vi_next_big_word(key, arg: 1) if current_line.bytesize > @byte_pointer byte_size = Reline::Unicode.vi_big_forward_word(current_line, @byte_pointer) @byte_pointer += byte_size end arg -= 1 vi_next_big_word(key, arg: arg) if arg > 0 end private def vi_prev_big_word(key, arg: 1) if @byte_pointer > 0 byte_size = Reline::Unicode.vi_big_backward_word(current_line, @byte_pointer) @byte_pointer -= byte_size end arg -= 1 vi_prev_big_word(key, arg: arg) if arg > 0 end private def vi_end_big_word(key, arg: 1, inclusive: false) if current_line.bytesize > @byte_pointer byte_size = Reline::Unicode.vi_big_forward_end_word(current_line, @byte_pointer) @byte_pointer += byte_size end arg -= 1 if inclusive and arg.zero? byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer) if byte_size > 0 @byte_pointer += byte_size end end vi_end_big_word(key, arg: arg) if arg > 0 end private def vi_delete_prev_char(key) if @byte_pointer == 0 and @line_index > 0 @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index) @line_index -= 1 process_auto_indent cursor_dependent: false elsif @byte_pointer > 0 byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer) @byte_pointer -= byte_size line, _ = byteslice!(current_line, @byte_pointer, byte_size) set_current_line(line) end end private def vi_insert_at_bol(key) ed_move_to_beg(key) @config.editing_mode = :vi_insert end private def vi_add_at_eol(key) ed_move_to_end(key) @config.editing_mode = :vi_insert end private def ed_delete_prev_char(key, arg: 1) deleted = +'' arg.times do if @byte_pointer > 0 byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer) @byte_pointer -= byte_size line, mbchar = byteslice!(current_line, @byte_pointer, byte_size) set_current_line(line) deleted.prepend(mbchar) end end copy_for_vi(deleted) end private def vi_change_meta(key, arg: nil) if @vi_waiting_operator set_current_line('', 0) if @vi_waiting_operator == :vi_change_meta_confirm && arg.nil? @vi_waiting_operator = nil @vi_waiting_operator_arg = nil else @drop_terminate_spaces = true @vi_waiting_operator = :vi_change_meta_confirm @vi_waiting_operator_arg = arg || 1 end end private def vi_change_meta_confirm(byte_pointer_diff) vi_delete_meta_confirm(byte_pointer_diff) @config.editing_mode = :vi_insert @drop_terminate_spaces = false end private def vi_delete_meta(key, arg: nil) if @vi_waiting_operator set_current_line('', 0) if @vi_waiting_operator == :vi_delete_meta_confirm && arg.nil? @vi_waiting_operator = nil @vi_waiting_operator_arg = nil else @vi_waiting_operator = :vi_delete_meta_confirm @vi_waiting_operator_arg = arg || 1 end end private def vi_delete_meta_confirm(byte_pointer_diff) if byte_pointer_diff > 0 line, cut = byteslice!(current_line, @byte_pointer, byte_pointer_diff) elsif byte_pointer_diff < 0 line, cut = byteslice!(current_line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff) else return end copy_for_vi(cut) set_current_line(line, @byte_pointer + (byte_pointer_diff < 0 ? byte_pointer_diff : 0)) end private def vi_yank(key, arg: nil) if @vi_waiting_operator copy_for_vi(current_line) if @vi_waiting_operator == :vi_yank_confirm && arg.nil? @vi_waiting_operator = nil @vi_waiting_operator_arg = nil else @vi_waiting_operator = :vi_yank_confirm @vi_waiting_operator_arg = arg || 1 end end private def vi_yank_confirm(byte_pointer_diff) if byte_pointer_diff > 0 cut = current_line.byteslice(@byte_pointer, byte_pointer_diff) elsif byte_pointer_diff < 0 cut = current_line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff) else return end copy_for_vi(cut) end private def vi_list_or_eof(key) if buffer_empty? @eof = true finish else ed_newline(key) end end alias_method :vi_end_of_transmission, :vi_list_or_eof alias_method :vi_eof_maybe, :vi_list_or_eof private def ed_delete_next_char(key, arg: 1) byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer) unless current_line.empty? || byte_size == 0 line, mbchar = byteslice!(current_line, @byte_pointer, byte_size) copy_for_vi(mbchar) if @byte_pointer > 0 && current_line.bytesize == @byte_pointer + byte_size byte_size = Reline::Unicode.get_prev_mbchar_size(line, @byte_pointer) set_current_line(line, @byte_pointer - byte_size) else set_current_line(line, @byte_pointer) end end arg -= 1 ed_delete_next_char(key, arg: arg) if arg > 0 end private def vi_to_history_line(key) if Reline::HISTORY.empty? return end move_history(0, line: :start, cursor: :start) end private def vi_histedit(key) path = Tempfile.open { |fp| fp.write whole_lines.join("\n") fp.path } system("#{ENV['EDITOR']} #{path}") @buffer_of_lines = File.read(path).split("\n") @buffer_of_lines = [String.new(encoding: encoding)] if @buffer_of_lines.empty? @line_index = 0 finish end private def vi_paste_prev(key, arg: 1) if @vi_clipboard.size > 0 cursor_point = @vi_clipboard.grapheme_clusters[0..-2].join set_current_line(byteinsert(current_line, @byte_pointer, @vi_clipboard), @byte_pointer + cursor_point.bytesize) end arg -= 1 vi_paste_prev(key, arg: arg) if arg > 0 end private def vi_paste_next(key, arg: 1) if @vi_clipboard.size > 0 byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer) line = byteinsert(current_line, @byte_pointer + byte_size, @vi_clipboard) set_current_line(line, @byte_pointer + @vi_clipboard.bytesize) end arg -= 1 vi_paste_next(key, arg: arg) if arg > 0 end private def ed_argument_digit(key) # key is expected to be `ESC digit` or `digit` num = key[/\d/].to_i @vi_arg = (@vi_arg || 0) * 10 + num end private def vi_to_column(key, arg: 0) # Implementing behavior of vi, not Readline's vi-mode. @byte_pointer, = current_line.grapheme_clusters.inject([0, 0]) { |(total_byte_size, total_width), gc| mbchar_width = Reline::Unicode.get_mbchar_width(gc) break [total_byte_size, total_width] if (total_width + mbchar_width) >= arg [total_byte_size + gc.bytesize, total_width + mbchar_width] } end private def vi_replace_char(key, arg: 1) @waiting_proc = ->(k) { if arg == 1 byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer) before = current_line.byteslice(0, @byte_pointer) remaining_point = @byte_pointer + byte_size after = current_line.byteslice(remaining_point, current_line.bytesize - remaining_point) set_current_line(before + k + after) @waiting_proc = nil elsif arg > 1 byte_size = 0 arg.times do byte_size += Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer + byte_size) end before = current_line.byteslice(0, @byte_pointer) remaining_point = @byte_pointer + byte_size after = current_line.byteslice(remaining_point, current_line.bytesize - remaining_point) replaced = k * arg set_current_line(before + replaced + after, @byte_pointer + replaced.bytesize) @waiting_proc = nil end } end private def vi_next_char(key, arg: 1, inclusive: false) @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, inclusive: inclusive) } end private def vi_to_next_char(key, arg: 1, inclusive: false) @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, need_prev_char: true, inclusive: inclusive) } end private def search_next_char(key, arg, need_prev_char: false, inclusive: false) prev_total = nil total = nil found = false current_line.byteslice(@byte_pointer..-1).grapheme_clusters.each do |mbchar| # total has [byte_size, cursor] unless total # skip cursor point width = Reline::Unicode.get_mbchar_width(mbchar) total = [mbchar.bytesize, width] else if key == mbchar arg -= 1 if arg.zero? found = true break end end width = Reline::Unicode.get_mbchar_width(mbchar) prev_total = total total = [total.first + mbchar.bytesize, total.last + width] end end if not need_prev_char and found and total byte_size, _ = total @byte_pointer += byte_size elsif need_prev_char and found and prev_total byte_size, _ = prev_total @byte_pointer += byte_size end if inclusive byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer) if byte_size > 0 @byte_pointer += byte_size end end @waiting_proc = nil end private def vi_prev_char(key, arg: 1) @waiting_proc = ->(key_for_proc) { search_prev_char(key_for_proc, arg) } end private def vi_to_prev_char(key, arg: 1) @waiting_proc = ->(key_for_proc) { search_prev_char(key_for_proc, arg, true) } end private def search_prev_char(key, arg, need_next_char = false) prev_total = nil total = nil found = false current_line.byteslice(0..@byte_pointer).grapheme_clusters.reverse_each do |mbchar| # total has [byte_size, cursor] unless total # skip cursor point width = Reline::Unicode.get_mbchar_width(mbchar) total = [mbchar.bytesize, width] else if key == mbchar arg -= 1 if arg.zero? found = true break end end width = Reline::Unicode.get_mbchar_width(mbchar) prev_total = total total = [total.first + mbchar.bytesize, total.last + width] end end if not need_next_char and found and total byte_size, _ = total @byte_pointer -= byte_size elsif need_next_char and found and prev_total byte_size, _ = prev_total @byte_pointer -= byte_size end @waiting_proc = nil end private def vi_join_lines(key, arg: 1) if @buffer_of_lines.size > @line_index + 1 next_line = @buffer_of_lines.delete_at(@line_index + 1).lstrip set_current_line(current_line + ' ' + next_line, current_line.bytesize) end arg -= 1 vi_join_lines(key, arg: arg) if arg > 0 end private def em_set_mark(key) @mark_pointer = [@byte_pointer, @line_index] end alias_method :set_mark, :em_set_mark private def em_exchange_mark(key) return unless @mark_pointer new_pointer = [@byte_pointer, @line_index] @byte_pointer, @line_index = @mark_pointer @mark_pointer = new_pointer end alias_method :exchange_point_and_mark, :em_exchange_mark private def emacs_editing_mode(key) @config.editing_mode = :emacs end private def vi_editing_mode(key) @config.editing_mode = :vi_insert end private def move_undo_redo(direction) @restoring = true return unless (0..@input_lines.size - 1).cover?(@input_lines_position + direction) @input_lines_position += direction buffer_of_lines, byte_pointer, line_index = @input_lines[@input_lines_position] @buffer_of_lines = buffer_of_lines.dup @line_index = line_index @byte_pointer = byte_pointer end private def undo(_key) move_undo_redo(-1) end private def redo(_key) move_undo_redo(+1) end private def prev_action_state_value(type) @prev_action_state[0] == type ? @prev_action_state[1] : nil end private def set_next_action_state(type, value) @next_action_state = [type, value] end private def re_read_init_file(_key) @config.reload end end PK!= key_actor.rbnu[module Reline::KeyActor end require 'reline/key_actor/base' require 'reline/key_actor/composite' require 'reline/key_actor/emacs' require 'reline/key_actor/vi_command' require 'reline/key_actor/vi_insert' PK!`Xkey_actor/base.rbnu[class Reline::KeyActor::Base def initialize(mappings = nil) @matching_bytes = {} @key_bindings = {} add_mappings(mappings) if mappings end def add_mappings(mappings) add([27], :ed_ignore) 128.times do |key| func = mappings[key] meta_func = mappings[key | 0b10000000] add([key], func) if func add([27, key], meta_func) if meta_func end end def add(key, func) (1...key.size).each do |size| @matching_bytes[key.take(size)] = true end @key_bindings[key] = func end def matching?(key) @matching_bytes[key] end def get(key) @key_bindings[key] end def clear @matching_bytes.clear @key_bindings.clear end end PK!key_actor/emacs.rbnu[module Reline::KeyActor EMACS_MAPPING = [ # 0 ^@ :em_set_mark, # 1 ^A :ed_move_to_beg, # 2 ^B :ed_prev_char, # 3 ^C :ed_ignore, # 4 ^D :em_delete, # 5 ^E :ed_move_to_end, # 6 ^F :ed_next_char, # 7 ^G nil, # 8 ^H :em_delete_prev_char, # 9 ^I :complete, # 10 ^J :ed_newline, # 11 ^K :ed_kill_line, # 12 ^L :ed_clear_screen, # 13 ^M :ed_newline, # 14 ^N :ed_next_history, # 15 ^O :ed_ignore, # 16 ^P :ed_prev_history, # 17 ^Q :ed_quoted_insert, # 18 ^R :vi_search_prev, # 19 ^S :vi_search_next, # 20 ^T :ed_transpose_chars, # 21 ^U :unix_line_discard, # 22 ^V :ed_quoted_insert, # 23 ^W :em_kill_region, # 24 ^X nil, # 25 ^Y :em_yank, # 26 ^Z :ed_ignore, # 27 ^[ nil, # 28 ^\ :ed_ignore, # 29 ^] :ed_ignore, # 30 ^^ nil, # 31 ^_ :undo, # 32 SPACE :ed_insert, # 33 ! :ed_insert, # 34 " :ed_insert, # 35 # :ed_insert, # 36 $ :ed_insert, # 37 % :ed_insert, # 38 & :ed_insert, # 39 ' :ed_insert, # 40 ( :ed_insert, # 41 ) :ed_insert, # 42 * :ed_insert, # 43 + :ed_insert, # 44 , :ed_insert, # 45 - :ed_insert, # 46 . :ed_insert, # 47 / :ed_insert, # 48 0 :ed_digit, # 49 1 :ed_digit, # 50 2 :ed_digit, # 51 3 :ed_digit, # 52 4 :ed_digit, # 53 5 :ed_digit, # 54 6 :ed_digit, # 55 7 :ed_digit, # 56 8 :ed_digit, # 57 9 :ed_digit, # 58 : :ed_insert, # 59 ; :ed_insert, # 60 < :ed_insert, # 61 = :ed_insert, # 62 > :ed_insert, # 63 ? :ed_insert, # 64 @ :ed_insert, # 65 A :ed_insert, # 66 B :ed_insert, # 67 C :ed_insert, # 68 D :ed_insert, # 69 E :ed_insert, # 70 F :ed_insert, # 71 G :ed_insert, # 72 H :ed_insert, # 73 I :ed_insert, # 74 J :ed_insert, # 75 K :ed_insert, # 76 L :ed_insert, # 77 M :ed_insert, # 78 N :ed_insert, # 79 O :ed_insert, # 80 P :ed_insert, # 81 Q :ed_insert, # 82 R :ed_insert, # 83 S :ed_insert, # 84 T :ed_insert, # 85 U :ed_insert, # 86 V :ed_insert, # 87 W :ed_insert, # 88 X :ed_insert, # 89 Y :ed_insert, # 90 Z :ed_insert, # 91 [ :ed_insert, # 92 \ :ed_insert, # 93 ] :ed_insert, # 94 ^ :ed_insert, # 95 _ :ed_insert, # 96 ` :ed_insert, # 97 a :ed_insert, # 98 b :ed_insert, # 99 c :ed_insert, # 100 d :ed_insert, # 101 e :ed_insert, # 102 f :ed_insert, # 103 g :ed_insert, # 104 h :ed_insert, # 105 i :ed_insert, # 106 j :ed_insert, # 107 k :ed_insert, # 108 l :ed_insert, # 109 m :ed_insert, # 110 n :ed_insert, # 111 o :ed_insert, # 112 p :ed_insert, # 113 q :ed_insert, # 114 r :ed_insert, # 115 s :ed_insert, # 116 t :ed_insert, # 117 u :ed_insert, # 118 v :ed_insert, # 119 w :ed_insert, # 120 x :ed_insert, # 121 y :ed_insert, # 122 z :ed_insert, # 123 { :ed_insert, # 124 | :ed_insert, # 125 } :ed_insert, # 126 ~ :ed_insert, # 127 ^? :em_delete_prev_char, # 128 M-^@ nil, # 129 M-^A nil, # 130 M-^B nil, # 131 M-^C nil, # 132 M-^D nil, # 133 M-^E nil, # 134 M-^F nil, # 135 M-^G nil, # 136 M-^H :ed_delete_prev_word, # 137 M-^I nil, # 138 M-^J :key_newline, # 139 M-^K nil, # 140 M-^L :ed_clear_screen, # 141 M-^M :key_newline, # 142 M-^N nil, # 143 M-^O nil, # 144 M-^P nil, # 145 M-^Q nil, # 146 M-^R nil, # 147 M-^S nil, # 148 M-^T nil, # 149 M-^U nil, # 150 M-^V nil, # 151 M-^W nil, # 152 M-^X nil, # 153 M-^Y :em_yank_pop, # 154 M-^Z nil, # 155 M-^[ nil, # 156 M-^\ nil, # 157 M-^] nil, # 158 M-^^ nil, # 159 M-^_ :redo, # 160 M-SPACE :em_set_mark, # 161 M-! nil, # 162 M-" nil, # 163 M-# nil, # 164 M-$ nil, # 165 M-% nil, # 166 M-& nil, # 167 M-' nil, # 168 M-( nil, # 169 M-) nil, # 170 M-* nil, # 171 M-+ nil, # 172 M-, nil, # 173 M-- nil, # 174 M-. nil, # 175 M-/ nil, # 176 M-0 :ed_argument_digit, # 177 M-1 :ed_argument_digit, # 178 M-2 :ed_argument_digit, # 179 M-3 :ed_argument_digit, # 180 M-4 :ed_argument_digit, # 181 M-5 :ed_argument_digit, # 182 M-6 :ed_argument_digit, # 183 M-7 :ed_argument_digit, # 184 M-8 :ed_argument_digit, # 185 M-9 :ed_argument_digit, # 186 M-: nil, # 187 M-; nil, # 188 M-< nil, # 189 M-= nil, # 190 M-> nil, # 191 M-? nil, # 192 M-@ nil, # 193 M-A nil, # 194 M-B :ed_prev_word, # 195 M-C :em_capitol_case, # 196 M-D :em_delete_next_word, # 197 M-E nil, # 198 M-F :em_next_word, # 199 M-G nil, # 200 M-H nil, # 201 M-I nil, # 202 M-J nil, # 203 M-K nil, # 204 M-L :em_lower_case, # 205 M-M nil, # 206 M-N :vi_search_next, # 207 M-O nil, # 208 M-P :vi_search_prev, # 209 M-Q nil, # 210 M-R nil, # 211 M-S nil, # 212 M-T nil, # 213 M-U :em_upper_case, # 214 M-V nil, # 215 M-W nil, # 216 M-X nil, # 217 M-Y :em_yank_pop, # 218 M-Z nil, # 219 M-[ nil, # 220 M-\ nil, # 221 M-] nil, # 222 M-^ nil, # 223 M-_ nil, # 224 M-` nil, # 225 M-a nil, # 226 M-b :ed_prev_word, # 227 M-c :em_capitol_case, # 228 M-d :em_delete_next_word, # 229 M-e nil, # 230 M-f :em_next_word, # 231 M-g nil, # 232 M-h nil, # 233 M-i nil, # 234 M-j nil, # 235 M-k nil, # 236 M-l :em_lower_case, # 237 M-m nil, # 238 M-n :vi_search_next, # 239 M-o nil, # 240 M-p :vi_search_prev, # 241 M-q nil, # 242 M-r nil, # 243 M-s nil, # 244 M-t :ed_transpose_words, # 245 M-u :em_upper_case, # 246 M-v nil, # 247 M-w nil, # 248 M-x nil, # 249 M-y nil, # 250 M-z nil, # 251 M-{ nil, # 252 M-| nil, # 253 M-} nil, # 254 M-~ nil, # 255 M-^? :ed_delete_prev_word # EOF ] end PK!yxDDkey_actor/composite.rbnu[class Reline::KeyActor::Composite def initialize(key_actors) @key_actors = key_actors end def matching?(key) @key_actors.any? { |key_actor| key_actor.matching?(key) } end def get(key) @key_actors.each do |key_actor| func = key_actor.get(key) return func if func end nil end end PK!baA2key_actor/vi_command.rbnu[module Reline::KeyActor VI_COMMAND_MAPPING = [ # 0 ^@ nil, # 1 ^A :ed_move_to_beg, # 2 ^B nil, # 3 ^C :ed_ignore, # 4 ^D :vi_end_of_transmission, # 5 ^E :ed_move_to_end, # 6 ^F nil, # 7 ^G nil, # 8 ^H :ed_prev_char, # 9 ^I nil, # 10 ^J :ed_newline, # 11 ^K :ed_kill_line, # 12 ^L :ed_clear_screen, # 13 ^M :ed_newline, # 14 ^N :ed_next_history, # 15 ^O :ed_ignore, # 16 ^P :ed_prev_history, # 17 ^Q :ed_ignore, # 18 ^R :vi_search_prev, # 19 ^S :ed_ignore, # 20 ^T :ed_transpose_chars, # 21 ^U :vi_kill_line_prev, # 22 ^V :ed_quoted_insert, # 23 ^W :ed_delete_prev_word, # 24 ^X nil, # 25 ^Y :em_yank, # 26 ^Z nil, # 27 ^[ nil, # 28 ^\ :ed_ignore, # 29 ^] nil, # 30 ^^ nil, # 31 ^_ nil, # 32 SPACE :ed_next_char, # 33 ! nil, # 34 " nil, # 35 # :vi_comment_out, # 36 $ :ed_move_to_end, # 37 % nil, # 38 & nil, # 39 ' nil, # 40 ( nil, # 41 ) nil, # 42 * nil, # 43 + :ed_next_history, # 44 , nil, # 45 - :ed_prev_history, # 46 . nil, # 47 / :vi_search_prev, # 48 0 :vi_zero, # 49 1 :ed_argument_digit, # 50 2 :ed_argument_digit, # 51 3 :ed_argument_digit, # 52 4 :ed_argument_digit, # 53 5 :ed_argument_digit, # 54 6 :ed_argument_digit, # 55 7 :ed_argument_digit, # 56 8 :ed_argument_digit, # 57 9 :ed_argument_digit, # 58 : nil, # 59 ; nil, # 60 < nil, # 61 = nil, # 62 > nil, # 63 ? :vi_search_next, # 64 @ :vi_alias, # 65 A :vi_add_at_eol, # 66 B :vi_prev_big_word, # 67 C :vi_change_to_eol, # 68 D :ed_kill_line, # 69 E :vi_end_big_word, # 70 F :vi_prev_char, # 71 G :vi_to_history_line, # 72 H nil, # 73 I :vi_insert_at_bol, # 74 J :vi_join_lines, # 75 K :vi_search_prev, # 76 L nil, # 77 M nil, # 78 N nil, # 79 O nil, # 80 P :vi_paste_prev, # 81 Q nil, # 82 R nil, # 83 S nil, # 84 T :vi_to_prev_char, # 85 U nil, # 86 V nil, # 87 W :vi_next_big_word, # 88 X :ed_delete_prev_char, # 89 Y nil, # 90 Z nil, # 91 [ nil, # 92 \ nil, # 93 ] nil, # 94 ^ :vi_first_print, # 95 _ nil, # 96 ` nil, # 97 a :vi_add, # 98 b :vi_prev_word, # 99 c :vi_change_meta, # 100 d :vi_delete_meta, # 101 e :vi_end_word, # 102 f :vi_next_char, # 103 g nil, # 104 h :ed_prev_char, # 105 i :vi_insert, # 106 j :ed_next_history, # 107 k :ed_prev_history, # 108 l :ed_next_char, # 109 m nil, # 110 n nil, # 111 o nil, # 112 p :vi_paste_next, # 113 q nil, # 114 r :vi_replace_char, # 115 s nil, # 116 t :vi_to_next_char, # 117 u nil, # 118 v :vi_histedit, # 119 w :vi_next_word, # 120 x :ed_delete_next_char, # 121 y :vi_yank, # 122 z nil, # 123 { nil, # 124 | :vi_to_column, # 125 } nil, # 126 ~ nil, # 127 ^? :em_delete_prev_char, # 128 M-^@ nil, # 129 M-^A nil, # 130 M-^B nil, # 131 M-^C nil, # 132 M-^D nil, # 133 M-^E nil, # 134 M-^F nil, # 135 M-^G nil, # 136 M-^H nil, # 137 M-^I nil, # 138 M-^J nil, # 139 M-^K nil, # 140 M-^L nil, # 141 M-^M nil, # 142 M-^N nil, # 143 M-^O nil, # 144 M-^P nil, # 145 M-^Q nil, # 146 M-^R nil, # 147 M-^S nil, # 148 M-^T nil, # 149 M-^U nil, # 150 M-^V nil, # 151 M-^W nil, # 152 M-^X nil, # 153 M-^Y nil, # 154 M-^Z nil, # 155 M-^[ nil, # 156 M-^\ nil, # 157 M-^] nil, # 158 M-^^ nil, # 159 M-^_ nil, # 160 M-SPACE nil, # 161 M-! nil, # 162 M-" nil, # 163 M-# nil, # 164 M-$ nil, # 165 M-% nil, # 166 M-& nil, # 167 M-' nil, # 168 M-( nil, # 169 M-) nil, # 170 M-* nil, # 171 M-+ nil, # 172 M-, nil, # 173 M-- nil, # 174 M-. nil, # 175 M-/ nil, # 176 M-0 nil, # 177 M-1 nil, # 178 M-2 nil, # 179 M-3 nil, # 180 M-4 nil, # 181 M-5 nil, # 182 M-6 nil, # 183 M-7 nil, # 184 M-8 nil, # 185 M-9 nil, # 186 M-: nil, # 187 M-; nil, # 188 M-< nil, # 189 M-= nil, # 190 M-> nil, # 191 M-? nil, # 192 M-@ nil, # 193 M-A nil, # 194 M-B nil, # 195 M-C nil, # 196 M-D nil, # 197 M-E nil, # 198 M-F nil, # 199 M-G nil, # 200 M-H nil, # 201 M-I nil, # 202 M-J nil, # 203 M-K nil, # 204 M-L nil, # 205 M-M nil, # 206 M-N nil, # 207 M-O nil, # 208 M-P nil, # 209 M-Q nil, # 210 M-R nil, # 211 M-S nil, # 212 M-T nil, # 213 M-U nil, # 214 M-V nil, # 215 M-W nil, # 216 M-X nil, # 217 M-Y nil, # 218 M-Z nil, # 219 M-[ nil, # 220 M-\ nil, # 221 M-] nil, # 222 M-^ nil, # 223 M-_ nil, # 224 M-` nil, # 225 M-a nil, # 226 M-b nil, # 227 M-c nil, # 228 M-d nil, # 229 M-e nil, # 230 M-f nil, # 231 M-g nil, # 232 M-h nil, # 233 M-i nil, # 234 M-j nil, # 235 M-k nil, # 236 M-l nil, # 237 M-m nil, # 238 M-n nil, # 239 M-o nil, # 240 M-p nil, # 241 M-q nil, # 242 M-r nil, # 243 M-s nil, # 244 M-t nil, # 245 M-u nil, # 246 M-v nil, # 247 M-w nil, # 248 M-x nil, # 249 M-y nil, # 250 M-z nil, # 251 M-{ nil, # 252 M-| nil, # 253 M-} nil, # 254 M-~ nil, # 255 M-^? nil # EOF ] end PK!Sggkey_actor/vi_insert.rbnu[module Reline::KeyActor VI_INSERT_MAPPING = [ # 0 ^@ nil, # 1 ^A :ed_insert, # 2 ^B :ed_insert, # 3 ^C :ed_insert, # 4 ^D :vi_list_or_eof, # 5 ^E :ed_insert, # 6 ^F :ed_insert, # 7 ^G :ed_insert, # 8 ^H :vi_delete_prev_char, # 9 ^I :complete, # 10 ^J :ed_newline, # 11 ^K :ed_insert, # 12 ^L :ed_insert, # 13 ^M :ed_newline, # 14 ^N :menu_complete, # 15 ^O :ed_insert, # 16 ^P :menu_complete_backward, # 17 ^Q :ed_ignore, # 18 ^R :vi_search_prev, # 19 ^S :vi_search_next, # 20 ^T :ed_transpose_chars, # 21 ^U :vi_kill_line_prev, # 22 ^V :ed_quoted_insert, # 23 ^W :ed_delete_prev_word, # 24 ^X :ed_insert, # 25 ^Y :em_yank, # 26 ^Z :ed_insert, # 27 ^[ :vi_command_mode, # 28 ^\ :ed_ignore, # 29 ^] :ed_insert, # 30 ^^ :ed_insert, # 31 ^_ :ed_insert, # 32 SPACE :ed_insert, # 33 ! :ed_insert, # 34 " :ed_insert, # 35 # :ed_insert, # 36 $ :ed_insert, # 37 % :ed_insert, # 38 & :ed_insert, # 39 ' :ed_insert, # 40 ( :ed_insert, # 41 ) :ed_insert, # 42 * :ed_insert, # 43 + :ed_insert, # 44 , :ed_insert, # 45 - :ed_insert, # 46 . :ed_insert, # 47 / :ed_insert, # 48 0 :ed_digit, # 49 1 :ed_digit, # 50 2 :ed_digit, # 51 3 :ed_digit, # 52 4 :ed_digit, # 53 5 :ed_digit, # 54 6 :ed_digit, # 55 7 :ed_digit, # 56 8 :ed_digit, # 57 9 :ed_digit, # 58 : :ed_insert, # 59 ; :ed_insert, # 60 < :ed_insert, # 61 = :ed_insert, # 62 > :ed_insert, # 63 ? :ed_insert, # 64 @ :ed_insert, # 65 A :ed_insert, # 66 B :ed_insert, # 67 C :ed_insert, # 68 D :ed_insert, # 69 E :ed_insert, # 70 F :ed_insert, # 71 G :ed_insert, # 72 H :ed_insert, # 73 I :ed_insert, # 74 J :ed_insert, # 75 K :ed_insert, # 76 L :ed_insert, # 77 M :ed_insert, # 78 N :ed_insert, # 79 O :ed_insert, # 80 P :ed_insert, # 81 Q :ed_insert, # 82 R :ed_insert, # 83 S :ed_insert, # 84 T :ed_insert, # 85 U :ed_insert, # 86 V :ed_insert, # 87 W :ed_insert, # 88 X :ed_insert, # 89 Y :ed_insert, # 90 Z :ed_insert, # 91 [ :ed_insert, # 92 \ :ed_insert, # 93 ] :ed_insert, # 94 ^ :ed_insert, # 95 _ :ed_insert, # 96 ` :ed_insert, # 97 a :ed_insert, # 98 b :ed_insert, # 99 c :ed_insert, # 100 d :ed_insert, # 101 e :ed_insert, # 102 f :ed_insert, # 103 g :ed_insert, # 104 h :ed_insert, # 105 i :ed_insert, # 106 j :ed_insert, # 107 k :ed_insert, # 108 l :ed_insert, # 109 m :ed_insert, # 110 n :ed_insert, # 111 o :ed_insert, # 112 p :ed_insert, # 113 q :ed_insert, # 114 r :ed_insert, # 115 s :ed_insert, # 116 t :ed_insert, # 117 u :ed_insert, # 118 v :ed_insert, # 119 w :ed_insert, # 120 x :ed_insert, # 121 y :ed_insert, # 122 z :ed_insert, # 123 { :ed_insert, # 124 | :ed_insert, # 125 } :ed_insert, # 126 ~ :ed_insert, # 127 ^? :vi_delete_prev_char, # 128 M-^@ nil, # 129 M-^A nil, # 130 M-^B nil, # 131 M-^C nil, # 132 M-^D nil, # 133 M-^E nil, # 134 M-^F nil, # 135 M-^G nil, # 136 M-^H nil, # 137 M-^I nil, # 138 M-^J :key_newline, # 139 M-^K nil, # 140 M-^L nil, # 141 M-^M :key_newline, # 142 M-^N nil, # 143 M-^O nil, # 144 M-^P nil, # 145 M-^Q nil, # 146 M-^R nil, # 147 M-^S nil, # 148 M-^T nil, # 149 M-^U nil, # 150 M-^V nil, # 151 M-^W nil, # 152 M-^X nil, # 153 M-^Y nil, # 154 M-^Z nil, # 155 M-^[ nil, # 156 M-^\ nil, # 157 M-^] nil, # 158 M-^^ nil, # 159 M-^_ nil, # 160 M-SPACE nil, # 161 M-! nil, # 162 M-" nil, # 163 M-# nil, # 164 M-$ nil, # 165 M-% nil, # 166 M-& nil, # 167 M-' nil, # 168 M-( nil, # 169 M-) nil, # 170 M-* nil, # 171 M-+ nil, # 172 M-, nil, # 173 M-- nil, # 174 M-. nil, # 175 M-/ nil, # 176 M-0 nil, # 177 M-1 nil, # 178 M-2 nil, # 179 M-3 nil, # 180 M-4 nil, # 181 M-5 nil, # 182 M-6 nil, # 183 M-7 nil, # 184 M-8 nil, # 185 M-9 nil, # 186 M-: nil, # 187 M-; nil, # 188 M-< nil, # 189 M-= nil, # 190 M-> nil, # 191 M-? nil, # 192 M-@ nil, # 193 M-A nil, # 194 M-B nil, # 195 M-C nil, # 196 M-D nil, # 197 M-E nil, # 198 M-F nil, # 199 M-G nil, # 200 M-H nil, # 201 M-I nil, # 202 M-J nil, # 203 M-K nil, # 204 M-L nil, # 205 M-M nil, # 206 M-N nil, # 207 M-O nil, # 208 M-P nil, # 209 M-Q nil, # 210 M-R nil, # 211 M-S nil, # 212 M-T nil, # 213 M-U nil, # 214 M-V nil, # 215 M-W nil, # 216 M-X nil, # 217 M-Y nil, # 218 M-Z nil, # 219 M-[ nil, # 220 M-\ nil, # 221 M-] nil, # 222 M-^ nil, # 223 M-_ nil, # 224 M-` nil, # 225 M-a nil, # 226 M-b nil, # 227 M-c nil, # 228 M-d nil, # 229 M-e nil, # 230 M-f nil, # 231 M-g nil, # 232 M-h nil, # 233 M-i nil, # 234 M-j nil, # 235 M-k nil, # 236 M-l nil, # 237 M-m nil, # 238 M-n nil, # 239 M-o nil, # 240 M-p nil, # 241 M-q nil, # 242 M-r nil, # 243 M-s nil, # 244 M-t nil, # 245 M-u nil, # 246 M-v nil, # 247 M-w nil, # 248 M-x nil, # 249 M-y nil, # 250 M-z nil, # 251 M-{ nil, # 252 M-| nil, # 253 M-} nil, # 254 M-~ nil, # 255 M-^? nil # EOF ] end PK!8/44 unicode.rbnu[class Reline::Unicode EscapedPairs = { 0x00 => '^@', 0x01 => '^A', # C-a 0x02 => '^B', 0x03 => '^C', 0x04 => '^D', 0x05 => '^E', 0x06 => '^F', 0x07 => '^G', 0x08 => '^H', # Backspace 0x09 => '^I', 0x0A => '^J', 0x0B => '^K', 0x0C => '^L', 0x0D => '^M', # Enter 0x0E => '^N', 0x0F => '^O', 0x10 => '^P', 0x11 => '^Q', 0x12 => '^R', 0x13 => '^S', 0x14 => '^T', 0x15 => '^U', 0x16 => '^V', 0x17 => '^W', 0x18 => '^X', 0x19 => '^Y', 0x1A => '^Z', # C-z 0x1B => '^[', # C-[ C-3 0x1D => '^]', # C-] 0x1E => '^^', # C-~ C-6 0x1F => '^_', # C-_ C-7 0x7F => '^?', # C-? C-8 } EscapedChars = EscapedPairs.keys.map(&:chr) NON_PRINTING_START = "\1" NON_PRINTING_END = "\2" CSI_REGEXP = /\e\[[\d;]*[ABCDEFGHJKSTfminsuhl]/ OSC_REGEXP = /\e\]\d+(?:;[^;\a\e]+)*(?:\a|\e\\)/ WIDTH_SCANNER = /\G(?:(#{NON_PRINTING_START})|(#{NON_PRINTING_END})|(#{CSI_REGEXP})|(#{OSC_REGEXP})|(\X))/o def self.escape_for_print(str) str.chars.map! { |gr| case gr when -"\n" gr when -"\t" -' ' else EscapedPairs[gr.ord] || gr end }.join end def self.safe_encode(str, encoding) # Reline only supports utf-8 convertible string. converted = str.encode(encoding, invalid: :replace, undef: :replace) return converted if str.encoding == Encoding::UTF_8 || converted.encoding == Encoding::UTF_8 || converted.ascii_only? # This code is essentially doing the same thing as # `str.encode(utf8, **replace_options).encode(encoding, **replace_options)` # but also avoids unneccesary irreversible encoding conversion. converted.gsub(/\X/) do |c| c.encode(Encoding::UTF_8) c rescue Encoding::UndefinedConversionError '?' end end require 'reline/unicode/east_asian_width' def self.get_mbchar_width(mbchar) ord = mbchar.ord if ord <= 0x1F # in EscapedPairs return 2 elsif ord <= 0x7E # printable ASCII chars return 1 end utf8_mbchar = mbchar.encode(Encoding::UTF_8) ord = utf8_mbchar.ord chunk_index = EastAsianWidth::CHUNK_LAST.bsearch_index { |o| ord <= o } size = EastAsianWidth::CHUNK_WIDTH[chunk_index] if size == -1 Reline.ambiguous_width elsif size == 1 && utf8_mbchar.size >= 2 second_char_ord = utf8_mbchar[1].ord # Halfwidth Dakuten Handakuten # Only these two character has Letter Modifier category and can be combined in a single grapheme cluster (second_char_ord == 0xFF9E || second_char_ord == 0xFF9F) ? 2 : 1 else size end end def self.calculate_width(str, allow_escape_code = false) if allow_escape_code width = 0 rest = str.encode(Encoding::UTF_8) in_zero_width = false rest.scan(WIDTH_SCANNER) do |non_printing_start, non_printing_end, csi, osc, gc| case when non_printing_start in_zero_width = true when non_printing_end in_zero_width = false when csi, osc when gc unless in_zero_width width += get_mbchar_width(gc) end end end width else str.encode(Encoding::UTF_8).grapheme_clusters.inject(0) { |w, gc| w + get_mbchar_width(gc) } end end # This method is used by IRB def self.split_by_width(str, max_width) lines = split_line_by_width(str, max_width) [lines, lines.size] end def self.split_line_by_width(str, max_width, encoding = str.encoding, offset: 0) lines = [String.new(encoding: encoding)] width = offset rest = str.encode(Encoding::UTF_8) in_zero_width = false seq = String.new(encoding: encoding) rest.scan(WIDTH_SCANNER) do |non_printing_start, non_printing_end, csi, osc, gc| case when non_printing_start in_zero_width = true when non_printing_end in_zero_width = false when csi lines.last << csi unless in_zero_width if csi == -"\e[m" || csi == -"\e[0m" seq.clear else seq << csi end end when osc lines.last << osc seq << osc unless in_zero_width when gc unless in_zero_width mbchar_width = get_mbchar_width(gc) if (width += mbchar_width) > max_width width = mbchar_width lines << seq.dup end end lines.last << gc end end # The cursor moves to next line in first if width == max_width lines << String.new(encoding: encoding) end lines end def self.strip_non_printing_start_end(prompt) prompt.gsub(/\x01([^\x02]*)(?:\x02|\z)/) { $1 } end # Take a chunk of a String cut by width with escape sequences. def self.take_range(str, start_col, max_width) take_mbchar_range(str, start_col, max_width).first end def self.take_mbchar_range(str, start_col, width, cover_begin: false, cover_end: false, padding: false) chunk = String.new(encoding: str.encoding) end_col = start_col + width total_width = 0 rest = str.encode(Encoding::UTF_8) in_zero_width = false chunk_start_col = nil chunk_end_col = nil has_csi = false rest.scan(WIDTH_SCANNER) do |non_printing_start, non_printing_end, csi, osc, gc| case when non_printing_start in_zero_width = true when non_printing_end in_zero_width = false when csi has_csi = true chunk << csi when osc chunk << osc when gc if in_zero_width chunk << gc next end mbchar_width = get_mbchar_width(gc) prev_width = total_width total_width += mbchar_width if (cover_begin || padding ? total_width <= start_col : prev_width < start_col) # Current character haven't reached start_col yet next elsif padding && !cover_begin && prev_width < start_col && start_col < total_width # Add preceding padding. This padding might have background color. chunk << ' ' chunk_start_col ||= start_col chunk_end_col = total_width next elsif (cover_end ? prev_width < end_col : total_width <= end_col) # Current character is in the range chunk << gc chunk_start_col ||= prev_width chunk_end_col = total_width break if total_width >= end_col else # Current character exceeds end_col if padding && end_col < total_width # Add succeeding padding. This padding might have background color. chunk << ' ' chunk_start_col ||= prev_width chunk_end_col = end_col end break end end end chunk_start_col ||= start_col chunk_end_col ||= start_col if padding && chunk_end_col < end_col # Append padding. This padding should not include background color. chunk << "\e[0m" if has_csi chunk << ' ' * (end_col - chunk_end_col) chunk_end_col = end_col end [chunk, chunk_start_col, chunk_end_col - chunk_start_col] end def self.get_next_mbchar_size(line, byte_pointer) grapheme = line.byteslice(byte_pointer..-1).grapheme_clusters.first grapheme ? grapheme.bytesize : 0 end def self.get_prev_mbchar_size(line, byte_pointer) if byte_pointer.zero? 0 else grapheme = line.byteslice(0..(byte_pointer - 1)).grapheme_clusters.last grapheme ? grapheme.bytesize : 0 end end def self.em_forward_word(line, byte_pointer) gcs = line.byteslice(byte_pointer..).grapheme_clusters nonwords = gcs.take_while { |c| !word_character?(c) } words = gcs.drop(nonwords.size).take_while { |c| word_character?(c) } nonwords.sum(&:bytesize) + words.sum(&:bytesize) end def self.em_forward_word_with_capitalization(line, byte_pointer) gcs = line.byteslice(byte_pointer..).grapheme_clusters nonwords = gcs.take_while { |c| !word_character?(c) } words = gcs.drop(nonwords.size).take_while { |c| word_character?(c) } [nonwords.sum(&:bytesize) + words.sum(&:bytesize), nonwords.join + words.join.capitalize] end def self.em_backward_word(line, byte_pointer) gcs = line.byteslice(0, byte_pointer).grapheme_clusters.reverse nonwords = gcs.take_while { |c| !word_character?(c) } words = gcs.drop(nonwords.size).take_while { |c| word_character?(c) } nonwords.sum(&:bytesize) + words.sum(&:bytesize) end def self.em_big_backward_word(line, byte_pointer) gcs = line.byteslice(0, byte_pointer).grapheme_clusters.reverse spaces = gcs.take_while { |c| space_character?(c) } nonspaces = gcs.drop(spaces.size).take_while { |c| !space_character?(c) } spaces.sum(&:bytesize) + nonspaces.sum(&:bytesize) end def self.ed_transpose_words(line, byte_pointer) gcs = line.byteslice(0, byte_pointer).grapheme_clusters pos = gcs.size gcs += line.byteslice(byte_pointer..).grapheme_clusters pos += 1 while pos < gcs.size && !word_character?(gcs[pos]) if pos == gcs.size # 'aaa bbb [cursor] ' pos -= 1 while pos > 0 && !word_character?(gcs[pos - 1]) second_word_end = gcs.size else # 'aaa [cursor]bbb' pos += 1 while pos < gcs.size && word_character?(gcs[pos]) second_word_end = pos end pos -= 1 while pos > 0 && word_character?(gcs[pos - 1]) second_word_start = pos pos -= 1 while pos > 0 && !word_character?(gcs[pos - 1]) first_word_end = pos pos -= 1 while pos > 0 && word_character?(gcs[pos - 1]) first_word_start = pos [first_word_start, first_word_end, second_word_start, second_word_end].map do |idx| gcs.take(idx).sum(&:bytesize) end end def self.vi_big_forward_word(line, byte_pointer) gcs = line.byteslice(byte_pointer..).grapheme_clusters nonspaces = gcs.take_while { |c| !space_character?(c) } spaces = gcs.drop(nonspaces.size).take_while { |c| space_character?(c) } nonspaces.sum(&:bytesize) + spaces.sum(&:bytesize) end def self.vi_big_forward_end_word(line, byte_pointer) gcs = line.byteslice(byte_pointer..).grapheme_clusters first = gcs.shift(1) spaces = gcs.take_while { |c| space_character?(c) } nonspaces = gcs.drop(spaces.size).take_while { |c| !space_character?(c) } matched = spaces + nonspaces matched.pop first.sum(&:bytesize) + matched.sum(&:bytesize) end def self.vi_big_backward_word(line, byte_pointer) gcs = line.byteslice(0, byte_pointer).grapheme_clusters.reverse spaces = gcs.take_while { |c| space_character?(c) } nonspaces = gcs.drop(spaces.size).take_while { |c| !space_character?(c) } spaces.sum(&:bytesize) + nonspaces.sum(&:bytesize) end def self.vi_forward_word(line, byte_pointer, drop_terminate_spaces = false) gcs = line.byteslice(byte_pointer..).grapheme_clusters return 0 if gcs.empty? c = gcs.first matched = if word_character?(c) gcs.take_while { |c| word_character?(c) } elsif space_character?(c) gcs.take_while { |c| space_character?(c) } else gcs.take_while { |c| !word_character?(c) && !space_character?(c) } end return matched.sum(&:bytesize) if drop_terminate_spaces spaces = gcs.drop(matched.size).take_while { |c| space_character?(c) } matched.sum(&:bytesize) + spaces.sum(&:bytesize) end def self.vi_forward_end_word(line, byte_pointer) gcs = line.byteslice(byte_pointer..).grapheme_clusters return 0 if gcs.empty? return gcs.first.bytesize if gcs.size == 1 start = gcs.shift skips = [start] if space_character?(start) || space_character?(gcs.first) spaces = gcs.take_while { |c| space_character?(c) } skips += spaces gcs.shift(spaces.size) end start_with_word = word_character?(gcs.first) matched = gcs.take_while { |c| start_with_word ? word_character?(c) : !word_character?(c) && !space_character?(c) } matched.pop skips.sum(&:bytesize) + matched.sum(&:bytesize) end def self.vi_backward_word(line, byte_pointer) gcs = line.byteslice(0, byte_pointer).grapheme_clusters.reverse spaces = gcs.take_while { |c| space_character?(c) } gcs.shift(spaces.size) start_with_word = word_character?(gcs.first) matched = gcs.take_while { |c| start_with_word ? word_character?(c) : !word_character?(c) && !space_character?(c) } spaces.sum(&:bytesize) + matched.sum(&:bytesize) end def self.common_prefix(list, ignore_case: false) return '' if list.empty? common_prefix_gcs = list.first.grapheme_clusters list.each do |item| gcs = item.grapheme_clusters common_prefix_gcs = common_prefix_gcs.take_while.with_index do |gc, i| ignore_case ? gc.casecmp?(gcs[i]) : gc == gcs[i] end end common_prefix_gcs.join end def self.vi_first_print(line) gcs = line.grapheme_clusters spaces = gcs.take_while { |c| space_character?(c) } spaces.sum(&:bytesize) end def self.word_character?(s) s.encode(Encoding::UTF_8).match?(/\p{Word}/) if s rescue Encoding::UndefinedConversionError false end def self.space_character?(s) s.match?(/\s/) if s end end PK!'g} } kill_ring.rbnu[class Reline::KillRing include Enumerable module State FRESH = :fresh CONTINUED = :continued PROCESSED = :processed YANK = :yank end RingPoint = Struct.new(:backward, :forward, :str) do def initialize(str) super(nil, nil, str) end def ==(other) equal?(other) end end class RingBuffer attr_reader :size attr_reader :head def initialize(max = 1024) @max = max @size = 0 @head = nil # reading head of ring-shaped tape end def <<(point) if @size.zero? @head = point @head.backward = @head @head.forward = @head @size = 1 elsif @size >= @max tail = @head.forward new_tail = tail.forward @head.forward = point point.backward = @head new_tail.backward = point point.forward = new_tail @head = point else tail = @head.forward @head.forward = point point.backward = @head tail.backward = point point.forward = tail @head = point @size += 1 end end def empty? @size.zero? end end def initialize(max = 1024) @ring = RingBuffer.new(max) @ring_pointer = nil @buffer = nil @state = State::FRESH end def append(string, before_p = false) case @state when State::FRESH, State::YANK @ring << RingPoint.new(+string) @state = State::CONTINUED when State::CONTINUED, State::PROCESSED if before_p @ring.head.str.prepend(string) else @ring.head.str.concat(string) end @state = State::CONTINUED end end def process case @state when State::FRESH # nothing to do when State::CONTINUED @state = State::PROCESSED when State::PROCESSED @state = State::FRESH when State::YANK # nothing to do end end def yank unless @ring.empty? @state = State::YANK @ring_pointer = @ring.head @ring_pointer.str else nil end end def yank_pop if @state == State::YANK prev_yank = @ring_pointer.str @ring_pointer = @ring_pointer.backward [@ring_pointer.str, prev_yank] else nil end end def each start = head = @ring.head loop do break if head.nil? yield head.str head = head.backward break if head == start end end end PK!E(( config.rbnu[class Reline::Config attr_reader :test_mode KEYSEQ_PATTERN = /\\(?:C|Control)-[A-Za-z_]|\\(?:M|Meta)-[0-9A-Za-z_]|\\(?:C|Control)-\\(?:M|Meta)-[A-Za-z_]|\\(?:M|Meta)-\\(?:C|Control)-[A-Za-z_]|\\e|\\[\\\"\'abdfnrtv]|\\\d{1,3}|\\x\h{1,2}|./ class InvalidInputrc < RuntimeError attr_accessor :file, :lineno end VARIABLE_NAMES = %w{ completion-ignore-case convert-meta disable-completion history-size keyseq-timeout show-all-if-ambiguous show-mode-in-prompt vi-cmd-mode-string vi-ins-mode-string emacs-mode-string enable-bracketed-paste isearch-terminators } VARIABLE_NAME_SYMBOLS = VARIABLE_NAMES.map { |v| :"#{v.tr(?-, ?_)}" } VARIABLE_NAME_SYMBOLS.each do |v| attr_accessor v end attr_accessor :autocompletion def initialize reset_variables end def reset if editing_mode_is?(:vi_command) @editing_mode_label = :vi_insert end @oneshot_key_bindings.clear end def reset_variables @additional_key_bindings = { # from inputrc emacs: Reline::KeyActor::Base.new, vi_insert: Reline::KeyActor::Base.new, vi_command: Reline::KeyActor::Base.new } @oneshot_key_bindings = Reline::KeyActor::Base.new @editing_mode_label = :emacs @keymap_label = :emacs @keymap_prefix = [] @default_key_bindings = { emacs: Reline::KeyActor::Base.new(Reline::KeyActor::EMACS_MAPPING), vi_insert: Reline::KeyActor::Base.new(Reline::KeyActor::VI_INSERT_MAPPING), vi_command: Reline::KeyActor::Base.new(Reline::KeyActor::VI_COMMAND_MAPPING) } @vi_cmd_mode_string = '(cmd)' @vi_ins_mode_string = '(ins)' @emacs_mode_string = '@' # https://tiswww.case.edu/php/chet/readline/readline.html#IDX25 @history_size = -1 # unlimited @keyseq_timeout = 500 @test_mode = false @autocompletion = false @convert_meta = seven_bit_encoding?(Reline::IOGate.encoding) @loaded = false @enable_bracketed_paste = true @show_mode_in_prompt = false @default_inputrc_path = nil end def editing_mode @default_key_bindings[@editing_mode_label] end def editing_mode=(val) @editing_mode_label = val end def editing_mode_is?(*val) val.any?(@editing_mode_label) end def keymap @default_key_bindings[@keymap_label] end def loaded? @loaded end def inputrc_path case ENV['INPUTRC'] when nil, '' else return File.expand_path(ENV['INPUTRC']) end # In the XDG Specification, if ~/.config/readline/inputrc exists, then # ~/.inputrc should not be read, but for compatibility with GNU Readline, # if ~/.inputrc exists, then it is given priority. home_rc_path = File.expand_path('~/.inputrc') return home_rc_path if File.exist?(home_rc_path) case path = ENV['XDG_CONFIG_HOME'] when nil, '' else path = File.join(path, 'readline/inputrc') return path if File.exist?(path) and path == File.expand_path(path) end path = File.expand_path('~/.config/readline/inputrc') return path if File.exist?(path) return home_rc_path end private def default_inputrc_path @default_inputrc_path ||= inputrc_path end def read(file = nil) @loaded = true file ||= default_inputrc_path begin if file.respond_to?(:readlines) lines = file.readlines else lines = File.readlines(file) end rescue Errno::ENOENT return nil end read_lines(lines, file) self rescue InvalidInputrc => e warn e.message nil end def key_bindings # The key bindings for each editing mode will be overwritten by the user-defined ones. Reline::KeyActor::Composite.new([@oneshot_key_bindings, @additional_key_bindings[@editing_mode_label], @default_key_bindings[@editing_mode_label]]) end def add_oneshot_key_binding(keystroke, target) # IRB sets invalid keystroke [Reline::Key]. We should ignore it. return unless keystroke.all? { |c| c.is_a?(Integer) } @oneshot_key_bindings.add(keystroke, target) end def reset_oneshot_key_bindings @oneshot_key_bindings.clear end def add_default_key_binding_by_keymap(keymap, keystroke, target) @default_key_bindings[keymap].add(keystroke, target) end def add_default_key_binding(keystroke, target) add_default_key_binding_by_keymap(@keymap_label, keystroke, target) end def read_lines(lines, file = nil) if not lines.empty? and lines.first.encoding != Reline.encoding_system_needs begin lines = lines.map do |l| l.encode(Reline.encoding_system_needs) rescue Encoding::UndefinedConversionError mes = "The inputrc encoded in #{lines.first.encoding.name} can't be converted to the locale #{Reline.encoding_system_needs.name}." raise Reline::ConfigEncodingConversionError.new(mes) end end end if_stack = [] lines.each_with_index do |line, no| next if line.match(/\A\s*#/) no += 1 line = line.chomp.lstrip if line.start_with?('$') handle_directive(line[1..-1], file, no, if_stack) next end next if if_stack.any? { |_no, skip| skip } case line when /^set +([^ ]+) +(.+)/i # value ignores everything after a space, raw_value does not. var, value, raw_value = $1.downcase, $2.partition(' ').first, $2 bind_variable(var, value, raw_value) when /^\s*(?:M|Meta)-([a-zA-Z_])\s*:\s*(.*)\s*$/o bind_key("\"\\M-#$1\"", $2) when /^\s*(?:C|Control)-([a-zA-Z_])\s*:\s*(.*)\s*$/o bind_key("\"\\C-#$1\"", $2) when /^\s*(?:(?:C|Control)-(?:M|Meta)|(?:M|Meta)-(?:C|Control))-([a-zA-Z_])\s*:\s*(.*)\s*$/o bind_key("\"\\M-\\C-#$1\"", $2) when /^\s*("#{KEYSEQ_PATTERN}+")\s*:\s*(.*)\s*$/o bind_key($1, $2) end end unless if_stack.empty? raise InvalidInputrc, "#{file}:#{if_stack.last[0]}: unclosed if" end end def handle_directive(directive, file, no, if_stack) directive, args = directive.split(' ') case directive when 'if' condition = false case args when /^mode=(vi|emacs)$/i mode = $1.downcase # NOTE: mode=vi means vi-insert mode mode = 'vi_insert' if mode == 'vi' if @editing_mode_label == mode.to_sym condition = true end when 'term' when 'version' else # application name condition = true if args == 'Ruby' condition = true if args == 'Reline' end if_stack << [no, !condition] when 'else' if if_stack.empty? raise InvalidInputrc, "#{file}:#{no}: unmatched else" end if_stack.last[1] = !if_stack.last[1] when 'endif' if if_stack.empty? raise InvalidInputrc, "#{file}:#{no}: unmatched endif" end if_stack.pop when 'include' read(File.expand_path(args)) end end def bind_variable(name, value, raw_value) case name when 'history-size' begin @history_size = Integer(value) rescue ArgumentError @history_size = 500 end when 'isearch-terminators' @isearch_terminators = retrieve_string(raw_value) when 'editing-mode' case value when 'emacs' @editing_mode_label = :emacs @keymap_label = :emacs @keymap_prefix = [] when 'vi' @editing_mode_label = :vi_insert @keymap_label = :vi_insert @keymap_prefix = [] end when 'keymap' case value when 'emacs', 'emacs-standard' @keymap_label = :emacs @keymap_prefix = [] when 'emacs-ctlx' @keymap_label = :emacs @keymap_prefix = [?\C-x.ord] when 'emacs-meta' @keymap_label = :emacs @keymap_prefix = [?\e.ord] when 'vi', 'vi-move', 'vi-command' @keymap_label = :vi_command @keymap_prefix = [] when 'vi-insert' @keymap_label = :vi_insert @keymap_prefix = [] end when 'keyseq-timeout' @keyseq_timeout = value.to_i when 'show-mode-in-prompt' case value when 'off' @show_mode_in_prompt = false when 'on' @show_mode_in_prompt = true else @show_mode_in_prompt = false end when 'vi-cmd-mode-string' @vi_cmd_mode_string = retrieve_string(raw_value) when 'vi-ins-mode-string' @vi_ins_mode_string = retrieve_string(raw_value) when 'emacs-mode-string' @emacs_mode_string = retrieve_string(raw_value) when *VARIABLE_NAMES then variable_name = :"@#{name.tr(?-, ?_)}" instance_variable_set(variable_name, value.nil? || value == '1' || value == 'on') end end def retrieve_string(str) str = $1 if str =~ /\A"(.*)"\z/ parse_keyseq(str).map { |c| c.chr(Reline.encoding_system_needs) }.join end def bind_key(key, value) keystroke, func = parse_key_binding(key, value) @additional_key_bindings[@keymap_label].add(@keymap_prefix + keystroke, func) if keystroke end def parse_key_binding(key, func_name) if key =~ /\A"(.*)"\z/ keyseq = parse_keyseq($1) else keyseq = nil end if func_name =~ /"(.*)"/ func = parse_keyseq($1) else func = func_name.split.first.tr(?-, ?_).to_sym # It must be macro. end [keyseq, func] end def key_notation_to_code(notation) case notation when /(?:\\(?:C|Control)-\\(?:M|Meta)|\\(?:M|Meta)-\\(?:C|Control))-([A-Za-z_])/ [?\e.ord, $1.ord % 32] when /\\(?:C|Control)-([A-Za-z_])/ ($1.upcase.ord % 32) when /\\(?:M|Meta)-([0-9A-Za-z_])/ [?\e.ord, $1.ord] when /\\(\d{1,3})/ then $1.to_i(8) # octal when /\\x(\h{1,2})/ then $1.to_i(16) # hexadecimal when "\\e" then ?\e.ord when "\\\\" then ?\\.ord when "\\\"" then ?".ord when "\\'" then ?'.ord when "\\a" then ?\a.ord when "\\b" then ?\b.ord when "\\d" then ?\d.ord when "\\f" then ?\f.ord when "\\n" then ?\n.ord when "\\r" then ?\r.ord when "\\t" then ?\t.ord when "\\v" then ?\v.ord else notation.ord end end def parse_keyseq(str) str.scan(KEYSEQ_PATTERN).flat_map do |notation| key_notation_to_code(notation) end end def reload reset_variables read end private def seven_bit_encoding?(encoding) encoding == Encoding::US_ASCII end end PK!Lr io/dumb.rbnu[require 'io/wait' class Reline::Dumb < Reline::IO RESET_COLOR = '' # Do not send color reset sequence attr_writer :output def initialize(encoding: nil) @input = STDIN @output = STDOUT @buf = [] @pasting = false @encoding = encoding @screen_size = [24, 80] end def dumb? true end def encoding if @encoding @encoding elsif RUBY_PLATFORM =~ /mswin|mingw/ Encoding::UTF_8 else @input.external_encoding || Encoding.default_external end rescue IOError # STDIN.external_encoding raises IOError in Ruby <= 3.0 when STDIN is closed Encoding.default_external end def set_default_key_bindings(_) end def input=(val) @input = val end def with_raw_input yield end def write(string) @output.write(string) end def buffered_output yield end def getc(_timeout_second) unless @buf.empty? return @buf.shift end c = nil loop do Reline.core.line_editor.handle_signal result = @input.wait_readable(0.1) next if result.nil? c = @input.read(1) break end c&.ord end def ungetc(c) @buf.unshift(c) end def get_screen_size @screen_size end def cursor_pos Reline::CursorPos.new(0, 0) end def hide_cursor end def show_cursor end def move_cursor_column(val) end def move_cursor_up(val) end def move_cursor_down(val) end def erase_after_cursor end def scroll_down(val) end def clear_screen end def set_screen_size(rows, columns) @screen_size = [rows, columns] end def set_winch_handler(&handler) end def in_pasting? @pasting end def prep end def deprep(otio) end end PK!1nBB io/windows.rbnu[require 'fiddle/import' class Reline::Windows < Reline::IO attr_writer :output def initialize @input_buf = [] @output_buf = [] @output = STDOUT @hsg = nil @getwch = Win32API.new('msvcrt', '_getwch', [], 'I') @kbhit = Win32API.new('msvcrt', '_kbhit', [], 'I') @GetKeyState = Win32API.new('user32', 'GetKeyState', ['L'], 'L') @GetConsoleScreenBufferInfo = Win32API.new('kernel32', 'GetConsoleScreenBufferInfo', ['L', 'P'], 'L') @SetConsoleCursorPosition = Win32API.new('kernel32', 'SetConsoleCursorPosition', ['L', 'L'], 'L') @GetStdHandle = Win32API.new('kernel32', 'GetStdHandle', ['L'], 'L') @FillConsoleOutputCharacter = Win32API.new('kernel32', 'FillConsoleOutputCharacter', ['L', 'L', 'L', 'L', 'P'], 'L') @ScrollConsoleScreenBuffer = Win32API.new('kernel32', 'ScrollConsoleScreenBuffer', ['L', 'P', 'P', 'L', 'P'], 'L') @hConsoleHandle = @GetStdHandle.call(STD_OUTPUT_HANDLE) @hConsoleInputHandle = @GetStdHandle.call(STD_INPUT_HANDLE) @GetNumberOfConsoleInputEvents = Win32API.new('kernel32', 'GetNumberOfConsoleInputEvents', ['L', 'P'], 'L') @ReadConsoleInputW = Win32API.new('kernel32', 'ReadConsoleInputW', ['L', 'P', 'L', 'P'], 'L') @GetFileType = Win32API.new('kernel32', 'GetFileType', ['L'], 'L') @GetFileInformationByHandleEx = Win32API.new('kernel32', 'GetFileInformationByHandleEx', ['L', 'I', 'P', 'L'], 'I') @FillConsoleOutputAttribute = Win32API.new('kernel32', 'FillConsoleOutputAttribute', ['L', 'L', 'L', 'L', 'P'], 'L') @SetConsoleCursorInfo = Win32API.new('kernel32', 'SetConsoleCursorInfo', ['L', 'P'], 'L') @GetConsoleMode = Win32API.new('kernel32', 'GetConsoleMode', ['L', 'P'], 'L') @SetConsoleMode = Win32API.new('kernel32', 'SetConsoleMode', ['L', 'L'], 'L') @WaitForSingleObject = Win32API.new('kernel32', 'WaitForSingleObject', ['L', 'L'], 'L') @legacy_console = getconsolemode & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0 end def encoding Encoding::UTF_8 end def win? true end def win_legacy_console? @legacy_console end def set_default_key_bindings(config) { [224, 72] => :ed_prev_history, # ↑ [224, 80] => :ed_next_history, # ↓ [224, 77] => :ed_next_char, # → [224, 75] => :ed_prev_char, # ← [224, 83] => :key_delete, # Del [224, 71] => :ed_move_to_beg, # Home [224, 79] => :ed_move_to_end, # End [ 0, 72] => :ed_prev_history, # ↑ [ 0, 80] => :ed_next_history, # ↓ [ 0, 77] => :ed_next_char, # → [ 0, 75] => :ed_prev_char, # ← [ 0, 83] => :key_delete, # Del [ 0, 71] => :ed_move_to_beg, # Home [ 0, 79] => :ed_move_to_end # End }.each_pair do |key, func| config.add_default_key_binding_by_keymap(:emacs, key, func) config.add_default_key_binding_by_keymap(:vi_insert, key, func) config.add_default_key_binding_by_keymap(:vi_command, key, func) end { [27, 32] => :em_set_mark, # M- [24, 24] => :em_exchange_mark, # C-x C-x }.each_pair do |key, func| config.add_default_key_binding_by_keymap(:emacs, key, func) end # Emulate ANSI key sequence. { [27, 91, 90] => :completion_journey_up, # S-Tab }.each_pair do |key, func| config.add_default_key_binding_by_keymap(:emacs, key, func) config.add_default_key_binding_by_keymap(:vi_insert, key, func) end end if defined? JRUBY_VERSION require 'win32api' else class Win32API DLL = {} TYPEMAP = {"0" => Fiddle::TYPE_VOID, "S" => Fiddle::TYPE_VOIDP, "I" => Fiddle::TYPE_LONG} POINTER_TYPE = Fiddle::SIZEOF_VOIDP == Fiddle::SIZEOF_LONG_LONG ? 'q*' : 'l!*' WIN32_TYPES = "VPpNnLlIi" DL_TYPES = "0SSI" def initialize(dllname, func, import, export = "0", calltype = :stdcall) @proto = [import].join.tr(WIN32_TYPES, DL_TYPES).sub(/^(.)0*$/, '\1') import = @proto.chars.map {|win_type| TYPEMAP[win_type.tr(WIN32_TYPES, DL_TYPES)]} export = TYPEMAP[export.tr(WIN32_TYPES, DL_TYPES)] calltype = Fiddle::Importer.const_get(:CALL_TYPE_TO_ABI)[calltype] handle = DLL[dllname] ||= begin Fiddle.dlopen(dllname) rescue Fiddle::DLError raise unless File.extname(dllname).empty? Fiddle.dlopen(dllname + ".dll") end @func = Fiddle::Function.new(handle[func], import, export, calltype) rescue Fiddle::DLError => e raise LoadError, e.message, e.backtrace end def call(*args) import = @proto.split("") args.each_with_index do |x, i| args[i], = [x == 0 ? nil : +x].pack("p").unpack(POINTER_TYPE) if import[i] == "S" args[i], = [x].pack("I").unpack("i") if import[i] == "I" end ret, = @func.call(*args) return ret || 0 end end end VK_RETURN = 0x0D VK_MENU = 0x12 # ALT key VK_LMENU = 0xA4 VK_CONTROL = 0x11 VK_SHIFT = 0x10 VK_DIVIDE = 0x6F KEY_EVENT = 0x01 WINDOW_BUFFER_SIZE_EVENT = 0x04 CAPSLOCK_ON = 0x0080 ENHANCED_KEY = 0x0100 LEFT_ALT_PRESSED = 0x0002 LEFT_CTRL_PRESSED = 0x0008 NUMLOCK_ON = 0x0020 RIGHT_ALT_PRESSED = 0x0001 RIGHT_CTRL_PRESSED = 0x0004 SCROLLLOCK_ON = 0x0040 SHIFT_PRESSED = 0x0010 VK_TAB = 0x09 VK_END = 0x23 VK_HOME = 0x24 VK_LEFT = 0x25 VK_UP = 0x26 VK_RIGHT = 0x27 VK_DOWN = 0x28 VK_DELETE = 0x2E STD_INPUT_HANDLE = -10 STD_OUTPUT_HANDLE = -11 FILE_TYPE_PIPE = 0x0003 FILE_NAME_INFO = 2 ENABLE_WRAP_AT_EOL_OUTPUT = 2 ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4 # Calling Win32API with console handle is reported to fail after executing some external command. # We need to refresh console handle and retry the call again. private def call_with_console_handle(win32func, *args) val = win32func.call(@hConsoleHandle, *args) return val if val != 0 @hConsoleHandle = @GetStdHandle.call(STD_OUTPUT_HANDLE) win32func.call(@hConsoleHandle, *args) end private def getconsolemode mode = +"\0\0\0\0" call_with_console_handle(@GetConsoleMode, mode) mode.unpack1('L') end private def setconsolemode(mode) call_with_console_handle(@SetConsoleMode, mode) end #if @legacy_console # setconsolemode(getconsolemode() | ENABLE_VIRTUAL_TERMINAL_PROCESSING) # @legacy_console = (getconsolemode() & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0) #end def msys_tty?(io = @hConsoleInputHandle) # check if fd is a pipe if @GetFileType.call(io) != FILE_TYPE_PIPE return false end bufsize = 1024 p_buffer = "\0" * bufsize res = @GetFileInformationByHandleEx.call(io, FILE_NAME_INFO, p_buffer, bufsize - 2) return false if res == 0 # get pipe name: p_buffer layout is: # struct _FILE_NAME_INFO { # DWORD FileNameLength; # WCHAR FileName[1]; # } FILE_NAME_INFO len = p_buffer[0, 4].unpack1("L") name = p_buffer[4, len].encode(Encoding::UTF_8, Encoding::UTF_16LE, invalid: :replace) # Check if this could be a MSYS2 pty pipe ('\msys-XXXX-ptyN-XX') # or a cygwin pty pipe ('\cygwin-XXXX-ptyN-XX') name =~ /(msys-|cygwin-).*-pty/ ? true : false end KEY_MAP = [ # It's treated as Meta+Enter on Windows. [ { control_keys: :CTRL, virtual_key_code: 0x0D }, "\e\r".bytes ], [ { control_keys: :SHIFT, virtual_key_code: 0x0D }, "\e\r".bytes ], # It's treated as Meta+Space on Windows. [ { control_keys: :CTRL, char_code: 0x20 }, "\e ".bytes ], # Emulate getwch() key sequences. [ { control_keys: [], virtual_key_code: VK_UP }, [0, 72] ], [ { control_keys: [], virtual_key_code: VK_DOWN }, [0, 80] ], [ { control_keys: [], virtual_key_code: VK_RIGHT }, [0, 77] ], [ { control_keys: [], virtual_key_code: VK_LEFT }, [0, 75] ], [ { control_keys: [], virtual_key_code: VK_DELETE }, [0, 83] ], [ { control_keys: [], virtual_key_code: VK_HOME }, [0, 71] ], [ { control_keys: [], virtual_key_code: VK_END }, [0, 79] ], # Emulate ANSI key sequence. [ { control_keys: :SHIFT, virtual_key_code: VK_TAB }, [27, 91, 90] ], ] def process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state) # high-surrogate if 0xD800 <= char_code and char_code <= 0xDBFF @hsg = char_code return end # low-surrogate if 0xDC00 <= char_code and char_code <= 0xDFFF if @hsg char_code = 0x10000 + (@hsg - 0xD800) * 0x400 + char_code - 0xDC00 @hsg = nil else # no high-surrogate. ignored. return end else # ignore high-surrogate without low-surrogate if there @hsg = nil end key = KeyEventRecord.new(virtual_key_code, char_code, control_key_state) match = KEY_MAP.find { |args,| key.match?(**args) } unless match.nil? @output_buf.concat(match.last) return end # no char, only control keys return if key.char_code == 0 and key.control_keys.any? @output_buf.push("\e".ord) if key.control_keys.include?(:ALT) and !key.control_keys.include?(:CTRL) @output_buf.concat(key.char.bytes) end def check_input_event num_of_events = 0.chr * 8 while @output_buf.empty? Reline.core.line_editor.handle_signal if @WaitForSingleObject.(@hConsoleInputHandle, 100) != 0 # max 0.1 sec # prevent for background consolemode change @legacy_console = getconsolemode & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0 next end next if @GetNumberOfConsoleInputEvents.(@hConsoleInputHandle, num_of_events) == 0 or num_of_events.unpack1('L') == 0 input_records = 0.chr * 20 * 80 read_event = 0.chr * 4 if @ReadConsoleInputW.(@hConsoleInputHandle, input_records, 80, read_event) != 0 read_events = read_event.unpack1('L') 0.upto(read_events) do |idx| input_record = input_records[idx * 20, 20] event = input_record[0, 2].unpack1('s*') case event when WINDOW_BUFFER_SIZE_EVENT @winch_handler.() when KEY_EVENT key_down = input_record[4, 4].unpack1('l*') repeat_count = input_record[8, 2].unpack1('s*') virtual_key_code = input_record[10, 2].unpack1('s*') virtual_scan_code = input_record[12, 2].unpack1('s*') char_code = input_record[14, 2].unpack1('S*') control_key_state = input_record[16, 2].unpack1('S*') is_key_down = key_down.zero? ? false : true if is_key_down process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state) end end end end end end def with_raw_input yield end def write(string) @output.write(string) end def buffered_output yield end def getc(_timeout_second) check_input_event @output_buf.shift end def ungetc(c) @output_buf.unshift(c) end def in_pasting? not empty_buffer? end def empty_buffer? if not @output_buf.empty? false elsif @kbhit.call == 0 true else false end end def get_console_screen_buffer_info # CONSOLE_SCREEN_BUFFER_INFO # [ 0,2] dwSize.X # [ 2,2] dwSize.Y # [ 4,2] dwCursorPositions.X # [ 6,2] dwCursorPositions.Y # [ 8,2] wAttributes # [10,2] srWindow.Left # [12,2] srWindow.Top # [14,2] srWindow.Right # [16,2] srWindow.Bottom # [18,2] dwMaximumWindowSize.X # [20,2] dwMaximumWindowSize.Y csbi = 0.chr * 22 if call_with_console_handle(@GetConsoleScreenBufferInfo, csbi) != 0 # returns [width, height, x, y, attributes, left, top, right, bottom] csbi.unpack("s9") else return nil end end ALTERNATIVE_CSBI = [80, 24, 0, 0, 7, 0, 0, 79, 23].freeze def get_screen_size width, _, _, _, _, _, top, _, bottom = get_console_screen_buffer_info || ALTERNATIVE_CSBI [bottom - top + 1, width] end def cursor_pos _, _, x, y, _, _, top, = get_console_screen_buffer_info || ALTERNATIVE_CSBI Reline::CursorPos.new(x, y - top) end def move_cursor_column(val) _, _, _, y, = get_console_screen_buffer_info call_with_console_handle(@SetConsoleCursorPosition, y * 65536 + val) if y end def move_cursor_up(val) if val > 0 _, _, x, y, _, _, top, = get_console_screen_buffer_info return unless y y = (y - top) - val y = 0 if y < 0 call_with_console_handle(@SetConsoleCursorPosition, (y + top) * 65536 + x) elsif val < 0 move_cursor_down(-val) end end def move_cursor_down(val) if val > 0 _, _, x, y, _, _, top, _, bottom = get_console_screen_buffer_info return unless y screen_height = bottom - top y = (y - top) + val y = screen_height if y > screen_height call_with_console_handle(@SetConsoleCursorPosition, (y + top) * 65536 + x) elsif val < 0 move_cursor_up(-val) end end def erase_after_cursor width, _, x, y, attributes, = get_console_screen_buffer_info return unless x written = 0.chr * 4 call_with_console_handle(@FillConsoleOutputCharacter, 0x20, width - x, y * 65536 + x, written) call_with_console_handle(@FillConsoleOutputAttribute, attributes, width - x, y * 65536 + x, written) end # This only works when the cursor is at the bottom of the scroll range # For more details, see https://github.com/ruby/reline/pull/577#issuecomment-1646679623 def scroll_down(x) return if x.zero? # We use `\n` instead of CSI + S because CSI + S would cause https://github.com/ruby/reline/issues/576 @output.write "\n" * x end def clear_screen if @legacy_console width, _, _, _, attributes, _, top, _, bottom = get_console_screen_buffer_info return unless width fill_length = width * (bottom - top + 1) screen_topleft = top * 65536 written = 0.chr * 4 call_with_console_handle(@FillConsoleOutputCharacter, 0x20, fill_length, screen_topleft, written) call_with_console_handle(@FillConsoleOutputAttribute, attributes, fill_length, screen_topleft, written) call_with_console_handle(@SetConsoleCursorPosition, screen_topleft) else @output.write "\e[2J" "\e[H" end end def set_screen_size(rows, columns) raise NotImplementedError end def hide_cursor size = 100 visible = 0 # 0 means false cursor_info = [size, visible].pack('Li') call_with_console_handle(@SetConsoleCursorInfo, cursor_info) end def show_cursor size = 100 visible = 1 # 1 means true cursor_info = [size, visible].pack('Li') call_with_console_handle(@SetConsoleCursorInfo, cursor_info) end def set_winch_handler(&handler) @winch_handler = handler end def prep # do nothing nil end def deprep(otio) # do nothing end def disable_auto_linewrap(setting = true, &block) mode = getconsolemode if 0 == (mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) if block begin setconsolemode(mode & ~ENABLE_WRAP_AT_EOL_OUTPUT) block.call ensure setconsolemode(mode | ENABLE_WRAP_AT_EOL_OUTPUT) end else if setting setconsolemode(mode & ~ENABLE_WRAP_AT_EOL_OUTPUT) else setconsolemode(mode | ENABLE_WRAP_AT_EOL_OUTPUT) end end else block.call if block end end class KeyEventRecord attr_reader :virtual_key_code, :char_code, :control_key_state, :control_keys def initialize(virtual_key_code, char_code, control_key_state) @virtual_key_code = virtual_key_code @char_code = char_code @control_key_state = control_key_state @enhanced = control_key_state & ENHANCED_KEY != 0 (@control_keys = []).tap do |control_keys| # symbols must be sorted to make comparison is easier later on control_keys << :ALT if control_key_state & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED) != 0 control_keys << :CTRL if control_key_state & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) != 0 control_keys << :SHIFT if control_key_state & SHIFT_PRESSED != 0 end.freeze end def char @char_code.chr(Encoding::UTF_8) end def enhanced? @enhanced end # Verifies if the arguments match with this key event. # Nil arguments are ignored, but at least one must be passed as non-nil. # To verify that no control keys were pressed, pass an empty array: `control_keys: []`. def match?(control_keys: nil, virtual_key_code: nil, char_code: nil) raise ArgumentError, 'No argument was passed to match key event' if control_keys.nil? && virtual_key_code.nil? && char_code.nil? (control_keys.nil? || [*control_keys].sort == @control_keys) && (virtual_key_code.nil? || @virtual_key_code == virtual_key_code) && (char_code.nil? || char_code == @char_code) end end end PK!!! io/ansi.rbnu[require 'io/console' require 'io/wait' class Reline::ANSI < Reline::IO CAPNAME_KEY_BINDINGS = { 'khome' => :ed_move_to_beg, 'kend' => :ed_move_to_end, 'kdch1' => :key_delete, 'kpp' => :ed_search_prev_history, 'knp' => :ed_search_next_history, 'kcuu1' => :ed_prev_history, 'kcud1' => :ed_next_history, 'kcuf1' => :ed_next_char, 'kcub1' => :ed_prev_char, } ANSI_CURSOR_KEY_BINDINGS = { # Up 'A' => [:ed_prev_history, {}], # Down 'B' => [:ed_next_history, {}], # Right 'C' => [:ed_next_char, { ctrl: :em_next_word, meta: :em_next_word }], # Left 'D' => [:ed_prev_char, { ctrl: :ed_prev_word, meta: :ed_prev_word }], # End 'F' => [:ed_move_to_end, {}], # Home 'H' => [:ed_move_to_beg, {}], } attr_writer :input, :output def initialize @input = STDIN @output = STDOUT @buf = [] @output_buffer = nil @old_winch_handler = nil end def encoding @input.external_encoding || Encoding.default_external rescue IOError # STDIN.external_encoding raises IOError in Ruby <= 3.0 when STDIN is closed Encoding.default_external end def set_default_key_bindings(config) set_bracketed_paste_key_bindings(config) set_default_key_bindings_ansi_cursor(config) set_default_key_bindings_comprehensive_list(config) { [27, 91, 90] => :completion_journey_up, # S-Tab }.each_pair do |key, func| config.add_default_key_binding_by_keymap(:emacs, key, func) config.add_default_key_binding_by_keymap(:vi_insert, key, func) end { # default bindings [27, 32] => :em_set_mark, # M- [24, 24] => :em_exchange_mark, # C-x C-x }.each_pair do |key, func| config.add_default_key_binding_by_keymap(:emacs, key, func) end end def set_bracketed_paste_key_bindings(config) [:emacs, :vi_insert, :vi_command].each do |keymap| config.add_default_key_binding_by_keymap(keymap, START_BRACKETED_PASTE.bytes, :bracketed_paste_start) end end def set_default_key_bindings_ansi_cursor(config) ANSI_CURSOR_KEY_BINDINGS.each do |char, (default_func, modifiers)| bindings = [ ["\e[#{char}", default_func], # CSI + char ["\eO#{char}", default_func] # SS3 + char, application cursor key mode ] if modifiers[:ctrl] # CSI + ctrl_key_modifier + char bindings << ["\e[1;5#{char}", modifiers[:ctrl]] end if modifiers[:meta] # CSI + meta_key_modifier + char bindings << ["\e[1;3#{char}", modifiers[:meta]] # Meta(ESC) + CSI + char bindings << ["\e\e[#{char}", modifiers[:meta]] end bindings.each do |sequence, func| key = sequence.bytes config.add_default_key_binding_by_keymap(:emacs, key, func) config.add_default_key_binding_by_keymap(:vi_insert, key, func) config.add_default_key_binding_by_keymap(:vi_command, key, func) end end end def set_default_key_bindings_comprehensive_list(config) { # xterm [27, 91, 51, 126] => :key_delete, # kdch1 [27, 91, 53, 126] => :ed_search_prev_history, # kpp [27, 91, 54, 126] => :ed_search_next_history, # knp # Console (80x25) [27, 91, 49, 126] => :ed_move_to_beg, # Home [27, 91, 52, 126] => :ed_move_to_end, # End # urxvt / exoterm [27, 91, 55, 126] => :ed_move_to_beg, # Home [27, 91, 56, 126] => :ed_move_to_end, # End }.each_pair do |key, func| config.add_default_key_binding_by_keymap(:emacs, key, func) config.add_default_key_binding_by_keymap(:vi_insert, key, func) config.add_default_key_binding_by_keymap(:vi_command, key, func) end end def with_raw_input if @input.tty? @input.raw(intr: true) { yield } else yield end end def inner_getc(timeout_second) unless @buf.empty? return @buf.shift end until @input.wait_readable(0.01) timeout_second -= 0.01 return nil if timeout_second <= 0 Reline.core.line_editor.handle_signal end c = @input.getbyte (c == 0x16 && @input.tty? && @input.raw(min: 0, time: 0, &:getbyte)) || c rescue Errno::EIO # Maybe the I/O has been closed. nil end START_BRACKETED_PASTE = String.new("\e[200~", encoding: Encoding::ASCII_8BIT) END_BRACKETED_PASTE = String.new("\e[201~", encoding: Encoding::ASCII_8BIT) def read_bracketed_paste buffer = String.new(encoding: Encoding::ASCII_8BIT) until buffer.end_with?(END_BRACKETED_PASTE) c = inner_getc(Float::INFINITY) break unless c buffer << c end string = buffer.delete_suffix(END_BRACKETED_PASTE).force_encoding(encoding) string.valid_encoding? ? string : '' end # if the usage expects to wait indefinitely, use Float::INFINITY for timeout_second def getc(timeout_second) inner_getc(timeout_second) end def in_pasting? not empty_buffer? end def empty_buffer? unless @buf.empty? return false end !@input.wait_readable(0) end def ungetc(c) @buf.unshift(c) end def retrieve_keybuffer begin return unless @input.wait_readable(0.001) str = @input.read_nonblock(1024) str.bytes.each do |c| @buf.push(c) end rescue EOFError end end def get_screen_size s = @input.winsize return s if s[0] > 0 && s[1] > 0 s = [ENV["LINES"].to_i, ENV["COLUMNS"].to_i] return s if s[0] > 0 && s[1] > 0 [24, 80] rescue Errno::ENOTTY, Errno::ENODEV [24, 80] end def set_screen_size(rows, columns) @input.winsize = [rows, columns] self rescue Errno::ENOTTY, Errno::ENODEV self end private def cursor_pos_internal(timeout:) match = nil @input.raw do |stdin| @output << "\e[6n" @output.flush timeout_at = Time.now + timeout buf = +'' while (wait = timeout_at - Time.now) > 0 && stdin.wait_readable(wait) buf << stdin.readpartial(1024) if (match = buf.match(/\e\[(?\d+);(?\d+)R/)) buf = match.pre_match + match.post_match break end end buf.chars.reverse_each do |ch| stdin.ungetc ch end end [match[:column].to_i - 1, match[:row].to_i - 1] if match end def cursor_pos col, row = cursor_pos_internal(timeout: 0.5) if both_tty? Reline::CursorPos.new(col || 0, row || 0) end def both_tty? @input.tty? && @output.tty? end def write(string) if @output_buffer @output_buffer << string else @output.write(string) end end def buffered_output @output_buffer = +'' yield @output.write(@output_buffer) ensure @output_buffer = nil end def move_cursor_column(x) write "\e[#{x + 1}G" end def move_cursor_up(x) if x > 0 write "\e[#{x}A" elsif x < 0 move_cursor_down(-x) end end def move_cursor_down(x) if x > 0 write "\e[#{x}B" elsif x < 0 move_cursor_up(-x) end end def hide_cursor write "\e[?25l" end def show_cursor write "\e[?25h" end def erase_after_cursor write "\e[K" end # This only works when the cursor is at the bottom of the scroll range # For more details, see https://github.com/ruby/reline/pull/577#issuecomment-1646679623 def scroll_down(x) return if x.zero? # We use `\n` instead of CSI + S because CSI + S would cause https://github.com/ruby/reline/issues/576 write "\n" * x end def clear_screen write "\e[2J" write "\e[1;1H" end def set_winch_handler(&handler) @old_winch_handler = Signal.trap('WINCH') do |arg| handler.call @old_winch_handler.call(arg) if @old_winch_handler.respond_to?(:call) end @old_cont_handler = Signal.trap('CONT') do |arg| @input.raw!(intr: true) if @input.tty? # Rerender the screen. Note that screen size might be changed while suspended. handler.call @old_cont_handler.call(arg) if @old_cont_handler.respond_to?(:call) end rescue ArgumentError # Signal.trap may raise an ArgumentError if the platform doesn't support the signal. end def prep # Enable bracketed paste write "\e[?2004h" if Reline.core.config.enable_bracketed_paste && both_tty? retrieve_keybuffer nil end def deprep(otio) # Disable bracketed paste write "\e[?2004l" if Reline.core.config.enable_bracketed_paste && both_tty? Signal.trap('WINCH', @old_winch_handler) if @old_winch_handler Signal.trap('CONT', @old_cont_handler) if @old_cont_handler end end PK!B iiio.rbnu[ module Reline class IO RESET_COLOR = "\e[0m" def self.decide_io_gate if ENV['TERM'] == 'dumb' Reline::Dumb.new else require 'reline/io/ansi' case RbConfig::CONFIG['host_os'] when /mswin|msys|mingw|cygwin|bccwin|wince|emc/ require 'reline/io/windows' io = Reline::Windows.new if io.msys_tty? Reline::ANSI.new else io end else Reline::ANSI.new end end end def dumb? false end def win? false end def reset_color_sequence self.class::RESET_COLOR end # Read a single encoding valid character from the input. def read_single_char(keyseq_timeout) buffer = String.new(encoding: Encoding::ASCII_8BIT) loop do timeout = buffer.empty? ? Float::INFINITY : keyseq_timeout c = getc(timeout) return unless c buffer << c encoded = buffer.dup.force_encoding(encoding) return encoded if encoded.valid_encoding? end end end end require 'reline/io/dumb' PK!|@&& version.rbnu[PK!n  `key_stroke.rbnu[PK!+U+Uy unicode/east_asian_width.rbnu[PK! M$$bface.rbnu[PK!! Jvhistory.rbnu[PK!lM'b33~line_editor.rbnu[PK!= ձkey_actor.rbnu[PK!`X߲key_actor/base.rbnu[PK!key_actor/emacs.rbnu[PK!yxDD2key_actor/composite.rbnu[PK!baA2key_actor/vi_command.rbnu[PK!Sggkey_actor/vi_insert.rbnu[PK!8/44 unicode.rbnu[PK!'g} } <kill_ring.rbnu[PK!E(( Fconfig.rbnu[PK!Lr toio/dumb.rbnu[PK!1nBB tvio/windows.rbnu[PK!!! io/ansi.rbnu[PK!B iiwio.rbnu[PK