reline 0.5.7 → 0.5.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,138 +7,99 @@ class Reline::KeyStroke
7
7
  @config = config
8
8
  end
9
9
 
10
- def compress_meta_key(ary)
11
- return ary unless @config.convert_meta
12
- ary.inject([]) { |result, key|
13
- if result.size > 0 and result.last == "\e".ord
14
- result[result.size - 1] = Reline::Key.new(key, key | 0b10000000, true)
15
- else
16
- result << key
17
- end
18
- result
19
- }
20
- end
10
+ # Input exactly matches to a key sequence
11
+ MATCHING = :matching
12
+ # Input partially matches to a key sequence
13
+ MATCHED = :matched
14
+ # Input matches to a key sequence and the key sequence is a prefix of another key sequence
15
+ MATCHING_MATCHED = :matching_matched
16
+ # Input does not match to any key sequence
17
+ UNMATCHED = :unmatched
21
18
 
22
- def start_with?(me, other)
23
- compressed_me = compress_meta_key(me)
24
- compressed_other = compress_meta_key(other)
25
- i = 0
26
- loop do
27
- my_c = compressed_me[i]
28
- other_c = compressed_other[i]
29
- other_is_last = (i + 1) == compressed_other.size
30
- me_is_last = (i + 1) == compressed_me.size
31
- if my_c != other_c
32
- if other_c == "\e".ord and other_is_last and my_c.is_a?(Reline::Key) and my_c.with_meta
33
- return true
34
- else
35
- return false
36
- end
37
- elsif other_is_last
38
- return true
39
- elsif me_is_last
40
- return false
41
- end
42
- i += 1
43
- end
44
- end
19
+ def match_status(input)
20
+ matching = key_mapping.matching?(input)
21
+ matched = key_mapping.get(input)
45
22
 
46
- def equal?(me, other)
47
- case me
48
- when Array
49
- compressed_me = compress_meta_key(me)
50
- compressed_other = compress_meta_key(other)
51
- compressed_me.size == compressed_other.size and [compressed_me, compressed_other].transpose.all?{ |i| equal?(i[0], i[1]) }
52
- when Integer
53
- if other.is_a?(Reline::Key)
54
- if other.combined_char == "\e".ord
55
- false
56
- else
57
- other.combined_char == me
58
- end
59
- else
60
- me == other
61
- end
62
- when Reline::Key
63
- if other.is_a?(Integer)
64
- me.combined_char == other
65
- else
66
- me == other
67
- end
68
- end
69
- end
23
+ # FIXME: Workaround for single byte. remove this after MAPPING is merged into KeyActor.
24
+ matched ||= input.size == 1
25
+ matching ||= input == [ESC_BYTE]
70
26
 
71
- def match_status(input)
72
- key_mapping.keys.select { |lhs|
73
- start_with?(lhs, input)
74
- }.tap { |it|
75
- return :matched if it.size == 1 && equal?(it[0], input)
76
- return :matching if it.size == 1 && !equal?(it[0], input)
77
- return :matched if it.max_by(&:size)&.size&.< input.size
78
- return :matching if it.size > 1
79
- }
80
- if key_mapping.keys.any? { |lhs| start_with?(input, lhs) }
81
- :matched
27
+ if matching && matched
28
+ MATCHING_MATCHED
29
+ elsif matching
30
+ MATCHING
31
+ elsif matched
32
+ MATCHED
33
+ elsif input[0] == ESC_BYTE
34
+ match_unknown_escape_sequence(input, vi_mode: @config.editing_mode_is?(:vi_insert, :vi_command))
35
+ elsif input.size == 1
36
+ MATCHED
82
37
  else
83
- match_unknown_escape_sequence(input).first
38
+ UNMATCHED
84
39
  end
85
40
  end
86
41
 
87
42
  def expand(input)
88
- lhs = key_mapping.keys.select { |item| start_with?(input, item) }.sort_by(&:size).last
89
- unless lhs
90
- status, size = match_unknown_escape_sequence(input)
91
- case status
92
- when :matched
93
- return [:ed_unassigned] + expand(input.drop(size))
94
- when :matching
95
- return [:ed_unassigned]
96
- else
97
- return input
98
- end
43
+ matched_bytes = nil
44
+ (1..input.size).each do |i|
45
+ bytes = input.take(i)
46
+ status = match_status(bytes)
47
+ matched_bytes = bytes if status == MATCHED || status == MATCHING_MATCHED
99
48
  end
100
- rhs = key_mapping[lhs]
49
+ return [[], []] unless matched_bytes
101
50
 
102
- case rhs
103
- when String
104
- rhs_bytes = rhs.bytes
105
- expand(expand(rhs_bytes) + expand(input.drop(lhs.size)))
106
- when Symbol
107
- [rhs] + expand(input.drop(lhs.size))
108
- when Array
109
- rhs
51
+ func = key_mapping.get(matched_bytes)
52
+ if func.is_a?(Array)
53
+ keys = func.map { |c| Reline::Key.new(c, c, false) }
54
+ elsif func
55
+ keys = [Reline::Key.new(func, func, false)]
56
+ elsif matched_bytes.size == 1
57
+ keys = [Reline::Key.new(matched_bytes.first, matched_bytes.first, false)]
58
+ elsif matched_bytes.size == 2 && matched_bytes[0] == ESC_BYTE
59
+ keys = [Reline::Key.new(matched_bytes[1], matched_bytes[1] | 0b10000000, true)]
60
+ else
61
+ keys = []
110
62
  end
63
+
64
+ [keys, input.drop(matched_bytes.size)]
111
65
  end
112
66
 
113
67
  private
114
68
 
115
69
  # returns match status of CSI/SS3 sequence and matched length
116
- def match_unknown_escape_sequence(input)
70
+ def match_unknown_escape_sequence(input, vi_mode: false)
117
71
  idx = 0
118
- return [:unmatched, nil] unless input[idx] == ESC_BYTE
72
+ return UNMATCHED unless input[idx] == ESC_BYTE
119
73
  idx += 1
120
74
  idx += 1 if input[idx] == ESC_BYTE
121
75
 
122
76
  case input[idx]
123
77
  when nil
124
- return [:matching, nil]
78
+ if idx == 1 # `ESC`
79
+ return MATCHING_MATCHED
80
+ else # `ESC ESC`
81
+ return MATCHING
82
+ end
125
83
  when 91 # == '['.ord
126
- # CSI sequence
84
+ # CSI sequence `ESC [ ... char`
127
85
  idx += 1
128
86
  idx += 1 while idx < input.size && CSI_PARAMETER_BYTES_RANGE.cover?(input[idx])
129
87
  idx += 1 while idx < input.size && CSI_INTERMEDIATE_BYTES_RANGE.cover?(input[idx])
130
- input[idx] ? [:matched, idx + 1] : [:matching, nil]
131
88
  when 79 # == 'O'.ord
132
- # SS3 sequence
133
- input[idx + 1] ? [:matched, idx + 2] : [:matching, nil]
89
+ # SS3 sequence `ESC O char`
90
+ idx += 1
134
91
  else
135
- if idx == 1
136
- # `ESC char`, make it :unmatched so that it will be handled correctly in `read_2nd_character_of_key_sequence`
137
- [:unmatched, nil]
138
- else
139
- # `ESC ESC char`
140
- [:matched, idx + 1]
141
- end
92
+ # `ESC char` or `ESC ESC char`
93
+ return UNMATCHED if vi_mode
94
+ end
95
+
96
+ case input.size
97
+ when idx
98
+ MATCHING
99
+ when idx + 1
100
+ MATCHED
101
+ else
102
+ UNMATCHED
142
103
  end
143
104
  end
144
105
 
@@ -45,6 +45,7 @@ class Reline::LineEditor
45
45
  RenderedScreen = Struct.new(:base_y, :lines, :cursor_y, keyword_init: true)
46
46
 
47
47
  CompletionJourneyState = Struct.new(:line_index, :pre, :target, :post, :list, :pointer)
48
+ NullActionState = [nil, nil].freeze
48
49
 
49
50
  class MenuInfo
50
51
  attr_reader :list
@@ -237,7 +238,6 @@ class Reline::LineEditor
237
238
  @perfect_matched = nil
238
239
  @menu_info = nil
239
240
  @searching_prompt = nil
240
- @first_char = true
241
241
  @just_cursor_moving = false
242
242
  @eof = false
243
243
  @continuous_insertion_buffer = String.new(encoding: @encoding)
@@ -250,8 +250,11 @@ class Reline::LineEditor
250
250
  @resized = false
251
251
  @cache = {}
252
252
  @rendered_screen = RenderedScreen.new(base_y: 0, lines: [], cursor_y: 0)
253
- @past_lines = []
253
+ @input_lines = [[[""], 0, 0]]
254
+ @input_lines_position = 0
254
255
  @undoing = false
256
+ @prev_action_state = NullActionState
257
+ @next_action_state = NullActionState
255
258
  reset_line
256
259
  end
257
260
 
@@ -411,7 +414,7 @@ class Reline::LineEditor
411
414
  # do nothing
412
415
  elsif level == :blank
413
416
  Reline::IOGate.move_cursor_column base_x
414
- @output.write "#{Reline::IOGate::RESET_COLOR}#{' ' * width}"
417
+ @output.write "#{Reline::IOGate.reset_color_sequence}#{' ' * width}"
415
418
  else
416
419
  x, w, content = new_items[level]
417
420
  cover_begin = base_x != 0 && new_levels[base_x - 1] == level
@@ -421,7 +424,7 @@ class Reline::LineEditor
421
424
  content, pos = Reline::Unicode.take_mbchar_range(content, base_x - x, width, cover_begin: cover_begin, cover_end: cover_end, padding: true)
422
425
  end
423
426
  Reline::IOGate.move_cursor_column x + pos
424
- @output.write "#{Reline::IOGate::RESET_COLOR}#{content}#{Reline::IOGate::RESET_COLOR}"
427
+ @output.write "#{Reline::IOGate.reset_color_sequence}#{content}#{Reline::IOGate.reset_color_sequence}"
425
428
  end
426
429
  base_x += width
427
430
  end
@@ -683,10 +686,8 @@ class Reline::LineEditor
683
686
  @trap_key.each do |t|
684
687
  @config.add_oneshot_key_binding(t, @name)
685
688
  end
686
- elsif @trap_key.is_a?(Array)
689
+ else
687
690
  @config.add_oneshot_key_binding(@trap_key, @name)
688
- elsif @trap_key.is_a?(Integer) or @trap_key.is_a?(Reline::Key)
689
- @config.add_oneshot_key_binding([@trap_key], @name)
690
691
  end
691
692
  end
692
693
  dialog_render_info
@@ -1079,17 +1080,7 @@ class Reline::LineEditor
1079
1080
  else # single byte
1080
1081
  return if key.char >= 128 # maybe, first byte of multi byte
1081
1082
  method_symbol = @config.editing_mode.get_method(key.combined_char)
1082
- if key.with_meta and method_symbol == :ed_unassigned
1083
- if @config.editing_mode_is?(:vi_command, :vi_insert)
1084
- # split ESC + key in vi mode
1085
- method_symbol = @config.editing_mode.get_method("\e".ord)
1086
- process_key("\e".ord, method_symbol)
1087
- method_symbol = @config.editing_mode.get_method(key.char)
1088
- process_key(key.char, method_symbol)
1089
- end
1090
- else
1091
- process_key(key.combined_char, method_symbol)
1092
- end
1083
+ process_key(key.combined_char, method_symbol)
1093
1084
  @multibyte_buffer.clear
1094
1085
  end
1095
1086
  if @config.editing_mode_is?(:vi_command) and @byte_pointer > 0 and @byte_pointer == current_line.bytesize
@@ -1118,13 +1109,10 @@ class Reline::LineEditor
1118
1109
  end
1119
1110
  if key.char.nil?
1120
1111
  process_insert(force: true)
1121
- if @first_char
1122
- @eof = true
1123
- end
1112
+ @eof = buffer_empty?
1124
1113
  finish
1125
1114
  return
1126
1115
  end
1127
- @first_char = false
1128
1116
  @completion_occurs = false
1129
1117
 
1130
1118
  if key.char.is_a?(Symbol)
@@ -1132,12 +1120,15 @@ class Reline::LineEditor
1132
1120
  else
1133
1121
  normal_char(key)
1134
1122
  end
1123
+
1124
+ @prev_action_state, @next_action_state = @next_action_state, NullActionState
1125
+
1135
1126
  unless @completion_occurs
1136
1127
  @completion_state = CompletionState::NORMAL
1137
1128
  @completion_journey_state = nil
1138
1129
  end
1139
1130
 
1140
- push_past_lines unless @undoing
1131
+ push_input_lines unless @undoing
1141
1132
  @undoing = false
1142
1133
 
1143
1134
  if @in_pasting
@@ -1156,21 +1147,24 @@ class Reline::LineEditor
1156
1147
 
1157
1148
  def save_old_buffer
1158
1149
  @old_buffer_of_lines = @buffer_of_lines.dup
1159
- @old_byte_pointer = @byte_pointer.dup
1160
- @old_line_index = @line_index.dup
1161
1150
  end
1162
1151
 
1163
- def push_past_lines
1164
- if @old_buffer_of_lines != @buffer_of_lines
1165
- @past_lines.push([@old_buffer_of_lines, @old_byte_pointer, @old_line_index])
1152
+ def push_input_lines
1153
+ if @old_buffer_of_lines == @buffer_of_lines
1154
+ @input_lines[@input_lines_position] = [@buffer_of_lines.dup, @byte_pointer, @line_index]
1155
+ else
1156
+ @input_lines = @input_lines[0..@input_lines_position]
1157
+ @input_lines_position += 1
1158
+ @input_lines.push([@buffer_of_lines.dup, @byte_pointer, @line_index])
1166
1159
  end
1167
- trim_past_lines
1160
+ trim_input_lines
1168
1161
  end
1169
1162
 
1170
- MAX_PAST_LINES = 100
1171
- def trim_past_lines
1172
- if @past_lines.size > MAX_PAST_LINES
1173
- @past_lines.shift
1163
+ MAX_INPUT_LINES = 100
1164
+ def trim_input_lines
1165
+ if @input_lines.size > MAX_INPUT_LINES
1166
+ @input_lines.shift
1167
+ @input_lines_position -= 1
1174
1168
  end
1175
1169
  end
1176
1170
 
@@ -1352,7 +1346,7 @@ class Reline::LineEditor
1352
1346
  @buffer_of_lines[@line_index, 1] = lines
1353
1347
  @line_index += lines.size - 1
1354
1348
  @byte_pointer = @buffer_of_lines[@line_index].bytesize - post.bytesize
1355
- push_past_lines
1349
+ push_input_lines
1356
1350
  end
1357
1351
 
1358
1352
  def insert_text(text)
@@ -1411,6 +1405,10 @@ class Reline::LineEditor
1411
1405
  whole_lines.join("\n")
1412
1406
  end
1413
1407
 
1408
+ private def buffer_empty?
1409
+ current_line.empty? and @buffer_of_lines.size == 1
1410
+ end
1411
+
1414
1412
  def finished?
1415
1413
  @finished
1416
1414
  end
@@ -1759,29 +1757,31 @@ class Reline::LineEditor
1759
1757
  end
1760
1758
 
1761
1759
  private def ed_search_prev_history(key, arg: 1)
1762
- substr = current_line.byteslice(0, @byte_pointer)
1760
+ substr = prev_action_state_value(:search_history) == :empty ? '' : current_line.byteslice(0, @byte_pointer)
1763
1761
  return if @history_pointer == 0
1764
1762
  return if @history_pointer.nil? && substr.empty? && !current_line.empty?
1765
1763
 
1766
1764
  history_range = 0...(@history_pointer || Reline::HISTORY.size)
1767
1765
  h_pointer, line_index = search_history(substr, history_range.reverse_each)
1768
1766
  return unless h_pointer
1769
- move_history(h_pointer, line: line_index || :start, cursor: @byte_pointer)
1767
+ move_history(h_pointer, line: line_index || :start, cursor: substr.empty? ? :end : @byte_pointer)
1770
1768
  arg -= 1
1769
+ set_next_action_state(:search_history, :empty) if substr.empty?
1771
1770
  ed_search_prev_history(key, arg: arg) if arg > 0
1772
1771
  end
1773
1772
  alias_method :history_search_backward, :ed_search_prev_history
1774
1773
 
1775
1774
  private def ed_search_next_history(key, arg: 1)
1776
- substr = current_line.byteslice(0, @byte_pointer)
1775
+ substr = prev_action_state_value(:search_history) == :empty ? '' : current_line.byteslice(0, @byte_pointer)
1777
1776
  return if @history_pointer.nil?
1778
1777
 
1779
1778
  history_range = @history_pointer + 1...Reline::HISTORY.size
1780
1779
  h_pointer, line_index = search_history(substr, history_range)
1781
1780
  return if h_pointer.nil? and not substr.empty?
1782
1781
 
1783
- move_history(h_pointer, line: line_index || :start, cursor: @byte_pointer)
1782
+ move_history(h_pointer, line: line_index || :start, cursor: substr.empty? ? :end : @byte_pointer)
1784
1783
  arg -= 1
1784
+ set_next_action_state(:search_history, :empty) if substr.empty?
1785
1785
  ed_search_next_history(key, arg: arg) if arg > 0
1786
1786
  end
1787
1787
  alias_method :history_search_forward, :ed_search_next_history
@@ -1937,7 +1937,7 @@ class Reline::LineEditor
1937
1937
  alias_method :kill_whole_line, :em_kill_line
1938
1938
 
1939
1939
  private def em_delete(key)
1940
- if current_line.empty? and @buffer_of_lines.size == 1 and key == "\C-d".ord
1940
+ if buffer_empty? and key == "\C-d".ord
1941
1941
  @eof = true
1942
1942
  finish
1943
1943
  elsif @byte_pointer < current_line.bytesize
@@ -2285,8 +2285,7 @@ class Reline::LineEditor
2285
2285
  end
2286
2286
 
2287
2287
  private def vi_list_or_eof(key)
2288
- if current_line.empty? and @buffer_of_lines.size == 1
2289
- set_current_line('', 0)
2288
+ if buffer_empty?
2290
2289
  @eof = true
2291
2290
  finish
2292
2291
  else
@@ -2529,13 +2528,30 @@ class Reline::LineEditor
2529
2528
  end
2530
2529
 
2531
2530
  private def undo(_key)
2532
- return if @past_lines.empty?
2531
+ @undoing = true
2532
+
2533
+ return if @input_lines_position <= 0
2534
+
2535
+ @input_lines_position -= 1
2536
+ target_lines, target_cursor_x, target_cursor_y = @input_lines[@input_lines_position]
2537
+ set_current_lines(target_lines.dup, target_cursor_x, target_cursor_y)
2538
+ end
2533
2539
 
2540
+ private def redo(_key)
2534
2541
  @undoing = true
2535
2542
 
2536
- target_lines, target_cursor_x, target_cursor_y = @past_lines.last
2537
- set_current_lines(target_lines, target_cursor_x, target_cursor_y)
2543
+ return if @input_lines_position >= @input_lines.size - 1
2544
+
2545
+ @input_lines_position += 1
2546
+ target_lines, target_cursor_x, target_cursor_y = @input_lines[@input_lines_position]
2547
+ set_current_lines(target_lines.dup, target_cursor_x, target_cursor_y)
2548
+ end
2549
+
2550
+ private def prev_action_state_value(type)
2551
+ @prev_action_state[0] == type ? @prev_action_state[1] : nil
2552
+ end
2538
2553
 
2539
- @past_lines.pop
2554
+ private def set_next_action_state(type, value)
2555
+ @next_action_state = [type, value]
2540
2556
  end
2541
2557
  end
@@ -1,4 +1,7 @@
1
1
  begin
2
+ # Ignore warning `Add fiddle to your Gemfile or gemspec` in Ruby 3.4.
3
+ # terminfo.rb and ansi.rb supports fiddle unavailable environment.
4
+ verbose, $VERBOSE = $VERBOSE, nil
2
5
  require 'fiddle'
3
6
  require 'fiddle/import'
4
7
  rescue LoadError
@@ -7,6 +10,8 @@ rescue LoadError
7
10
  false
8
11
  end
9
12
  end
13
+ ensure
14
+ $VERBOSE = verbose
10
15
  end
11
16
 
12
17
  module Reline::Terminfo
@@ -1,3 +1,3 @@
1
1
  module Reline
2
- VERSION = '0.5.7'
2
+ VERSION = '0.5.9'
3
3
  end