reline 0.5.11 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -13,7 +13,6 @@ class Reline::LineEditor
13
13
  attr_accessor :prompt_proc
14
14
  attr_accessor :auto_indent_proc
15
15
  attr_accessor :dig_perfect_match_proc
16
- attr_writer :output
17
16
 
18
17
  VI_MOTIONS = %i{
19
18
  ed_prev_char
@@ -36,7 +35,6 @@ class Reline::LineEditor
36
35
 
37
36
  module CompletionState
38
37
  NORMAL = :normal
39
- COMPLETION = :completion
40
38
  MENU = :menu
41
39
  MENU_WITH_PERFECT_MATCH = :menu_with_perfect_match
42
40
  PERFECT_MATCH = :perfect_match
@@ -254,7 +252,7 @@ class Reline::LineEditor
254
252
  @rendered_screen = RenderedScreen.new(base_y: 0, lines: [], cursor_y: 0)
255
253
  @input_lines = [[[""], 0, 0]]
256
254
  @input_lines_position = 0
257
- @undoing = false
255
+ @restoring = false
258
256
  @prev_action_state = NullActionState
259
257
  @next_action_state = NullActionState
260
258
  reset_line
@@ -266,7 +264,6 @@ class Reline::LineEditor
266
264
  @line_index = 0
267
265
  @cache.clear
268
266
  @line_backup_in_history = nil
269
- @multibyte_buffer = String.new(encoding: 'ASCII-8BIT')
270
267
  end
271
268
 
272
269
  def multiline_on
@@ -300,8 +297,8 @@ class Reline::LineEditor
300
297
  end
301
298
  end
302
299
 
303
- private def split_by_width(str, max_width, offset: 0)
304
- Reline::Unicode.split_by_width(str, max_width, encoding, offset: offset)
300
+ private def split_line_by_width(str, max_width, offset: 0)
301
+ Reline::Unicode.split_line_by_width(str, max_width, encoding, offset: offset)
305
302
  end
306
303
 
307
304
  def current_byte_pointer_cursor
@@ -391,8 +388,8 @@ class Reline::LineEditor
391
388
  if (cached = cached_wraps[[prompt, line]])
392
389
  next cached
393
390
  end
394
- *wrapped_prompts, code_line_prompt = split_by_width(prompt, width).first.compact
395
- wrapped_lines = split_by_width(line, width, offset: calculate_width(code_line_prompt, true)).first.compact
391
+ *wrapped_prompts, code_line_prompt = split_line_by_width(prompt, width)
392
+ wrapped_lines = split_line_by_width(line, width, offset: calculate_width(code_line_prompt, true))
396
393
  wrapped_prompts.map { |p| [p, ''] } + [[code_line_prompt, wrapped_lines.first]] + wrapped_lines.drop(1).map { |c| ['', c] }
397
394
  end
398
395
  end
@@ -416,7 +413,7 @@ class Reline::LineEditor
416
413
  # do nothing
417
414
  elsif level == :blank
418
415
  Reline::IOGate.move_cursor_column base_x
419
- @output.write "#{Reline::IOGate.reset_color_sequence}#{' ' * width}"
416
+ Reline::IOGate.write "#{Reline::IOGate.reset_color_sequence}#{' ' * width}"
420
417
  else
421
418
  x, w, content = new_items[level]
422
419
  cover_begin = base_x != 0 && new_levels[base_x - 1] == level
@@ -426,7 +423,7 @@ class Reline::LineEditor
426
423
  content, pos = Reline::Unicode.take_mbchar_range(content, base_x - x, width, cover_begin: cover_begin, cover_end: cover_end, padding: true)
427
424
  end
428
425
  Reline::IOGate.move_cursor_column x + pos
429
- @output.write "#{Reline::IOGate.reset_color_sequence}#{content}#{Reline::IOGate.reset_color_sequence}"
426
+ Reline::IOGate.write "#{Reline::IOGate.reset_color_sequence}#{content}#{Reline::IOGate.reset_color_sequence}"
430
427
  end
431
428
  base_x += width
432
429
  end
@@ -439,8 +436,8 @@ class Reline::LineEditor
439
436
  # Calculate cursor position in word wrapped content.
440
437
  def wrapped_cursor_position
441
438
  prompt_width = calculate_width(prompt_list[@line_index], true)
442
- line_before_cursor = whole_lines[@line_index].byteslice(0, @byte_pointer)
443
- wrapped_line_before_cursor = split_by_width(' ' * prompt_width + line_before_cursor, screen_width).first.compact
439
+ line_before_cursor = Reline::Unicode.escape_for_print(whole_lines[@line_index].byteslice(0, @byte_pointer))
440
+ wrapped_line_before_cursor = split_line_by_width(' ' * prompt_width + line_before_cursor, screen_width)
444
441
  wrapped_cursor_y = wrapped_prompt_and_input_lines[0...@line_index].sum(&:size) + wrapped_line_before_cursor.size - 1
445
442
  wrapped_cursor_x = calculate_width(wrapped_line_before_cursor.last)
446
443
  [wrapped_cursor_x, wrapped_cursor_y]
@@ -462,19 +459,21 @@ class Reline::LineEditor
462
459
  end
463
460
 
464
461
  def render_finished
465
- render_differential([], 0, 0)
466
- lines = @buffer_of_lines.size.times.map do |i|
467
- line = Reline::Unicode.strip_non_printing_start_end(prompt_list[i]) + modified_lines[i]
468
- wrapped_lines, = split_by_width(line, screen_width)
469
- wrapped_lines.last.empty? ? "#{line} " : line
462
+ Reline::IOGate.buffered_output do
463
+ render_differential([], 0, 0)
464
+ lines = @buffer_of_lines.size.times.map do |i|
465
+ line = Reline::Unicode.strip_non_printing_start_end(prompt_list[i]) + modified_lines[i]
466
+ wrapped_lines = split_line_by_width(line, screen_width)
467
+ wrapped_lines.last.empty? ? "#{line} " : line
468
+ end
469
+ Reline::IOGate.write lines.map { |l| "#{l}\r\n" }.join
470
470
  end
471
- @output.puts lines.map { |l| "#{l}\r\n" }.join
472
471
  end
473
472
 
474
473
  def print_nomultiline_prompt
475
474
  Reline::IOGate.disable_auto_linewrap(true) if Reline::IOGate.win?
476
475
  # Readline's test `TestRelineAsReadline#test_readline` requires first output to be prompt, not cursor reset escape sequence.
477
- @output.write Reline::Unicode.strip_non_printing_start_end(@prompt) if @prompt && !@is_multiline
476
+ Reline::IOGate.write Reline::Unicode.strip_non_printing_start_end(@prompt) if @prompt && !@is_multiline
478
477
  ensure
479
478
  Reline::IOGate.disable_auto_linewrap(false) if Reline::IOGate.win?
480
479
  end
@@ -505,7 +504,9 @@ class Reline::LineEditor
505
504
  end
506
505
  end
507
506
 
508
- render_differential new_lines, wrapped_cursor_x, wrapped_cursor_y - screen_scroll_top
507
+ Reline::IOGate.buffered_output do
508
+ render_differential new_lines, wrapped_cursor_x, wrapped_cursor_y - screen_scroll_top
509
+ end
509
510
  end
510
511
 
511
512
  # Reflects lines to be rendered and new cursor position to the screen
@@ -579,8 +580,9 @@ class Reline::LineEditor
579
580
  @context
580
581
  end
581
582
 
582
- def retrieve_completion_block(set_completion_quote_character = false)
583
- @line_editor.retrieve_completion_block(set_completion_quote_character)
583
+ def retrieve_completion_block(_unused = false)
584
+ preposing, target, postposing, _quote = @line_editor.retrieve_completion_block
585
+ [preposing, target, postposing]
584
586
  end
585
587
 
586
588
  def call_completion_proc_with_checking_args(pre, target, post)
@@ -800,105 +802,73 @@ class Reline::LineEditor
800
802
  @config.editing_mode
801
803
  end
802
804
 
803
- private def menu(_target, list)
805
+ private def menu(list)
804
806
  @menu_info = MenuInfo.new(list)
805
807
  end
806
808
 
807
- private def complete_internal_proc(list, is_menu)
808
- preposing, target, postposing = retrieve_completion_block
809
- candidates = list.select { |i|
810
- if i and not Encoding.compatible?(target.encoding, i.encoding)
811
- raise Encoding::CompatibilityError, "#{target.encoding.name} is not compatible with #{i.encoding.name}"
809
+ private def filter_normalize_candidates(target, list)
810
+ target = target.downcase if @config.completion_ignore_case
811
+ list.select do |item|
812
+ next unless item
813
+ unless Encoding.compatible?(target.encoding, item.encoding)
814
+ # Workaround for Readline test
815
+ if defined?(::Readline) && ::Readline == ::Reline
816
+ raise Encoding::CompatibilityError, "incompatible character encodings: #{target.encoding} and #{item.encoding}"
817
+ end
812
818
  end
819
+
813
820
  if @config.completion_ignore_case
814
- i&.downcase&.start_with?(target.downcase)
821
+ item.downcase.start_with?(target)
815
822
  else
816
- i&.start_with?(target)
817
- end
818
- }.uniq
819
- if is_menu
820
- menu(target, candidates)
821
- return nil
822
- end
823
- completed = candidates.inject { |memo, item|
824
- begin
825
- memo_mbchars = memo.unicode_normalize.grapheme_clusters
826
- item_mbchars = item.unicode_normalize.grapheme_clusters
827
- rescue Encoding::CompatibilityError
828
- memo_mbchars = memo.grapheme_clusters
829
- item_mbchars = item.grapheme_clusters
830
- end
831
- size = [memo_mbchars.size, item_mbchars.size].min
832
- result = +''
833
- size.times do |i|
834
- if @config.completion_ignore_case
835
- if memo_mbchars[i].casecmp?(item_mbchars[i])
836
- result << memo_mbchars[i]
837
- else
838
- break
839
- end
840
- else
841
- if memo_mbchars[i] == item_mbchars[i]
842
- result << memo_mbchars[i]
843
- else
844
- break
845
- end
846
- end
823
+ item.start_with?(target)
847
824
  end
848
- result
849
- }
850
-
851
- [target, preposing, completed, postposing, candidates]
825
+ end.map do |item|
826
+ item.unicode_normalize
827
+ rescue Encoding::CompatibilityError
828
+ item
829
+ end.uniq
852
830
  end
853
831
 
854
- private def perform_completion(list, just_show_list)
832
+ private def perform_completion(preposing, target, postposing, quote, list)
833
+ candidates = filter_normalize_candidates(target, list)
834
+
855
835
  case @completion_state
856
- when CompletionState::NORMAL
857
- @completion_state = CompletionState::COMPLETION
858
836
  when CompletionState::PERFECT_MATCH
859
837
  if @dig_perfect_match_proc
860
- @dig_perfect_match_proc.(@perfect_matched)
861
- else
862
- @completion_state = CompletionState::COMPLETION
838
+ @dig_perfect_match_proc.call(@perfect_matched)
839
+ return
863
840
  end
864
- end
865
- if just_show_list
866
- is_menu = true
867
- elsif @completion_state == CompletionState::MENU
868
- is_menu = true
869
- elsif @completion_state == CompletionState::MENU_WITH_PERFECT_MATCH
870
- is_menu = true
871
- else
872
- is_menu = false
873
- end
874
- result = complete_internal_proc(list, is_menu)
875
- if @completion_state == CompletionState::MENU_WITH_PERFECT_MATCH
841
+ when CompletionState::MENU
842
+ menu(candidates)
843
+ return
844
+ when CompletionState::MENU_WITH_PERFECT_MATCH
845
+ menu(candidates)
876
846
  @completion_state = CompletionState::PERFECT_MATCH
847
+ return
877
848
  end
878
- return if result.nil?
879
- target, preposing, completed, postposing, candidates = result
880
- return if completed.nil?
881
- if target <= completed and (@completion_state == CompletionState::COMPLETION)
882
- append_character = ''
883
- if candidates.include?(completed)
884
- if candidates.one?
885
- append_character = completion_append_character.to_s
886
- @completion_state = CompletionState::PERFECT_MATCH
887
- else
888
- @completion_state = CompletionState::MENU_WITH_PERFECT_MATCH
889
- perform_completion(candidates, true) if @config.show_all_if_ambiguous
890
- end
891
- @perfect_matched = completed
849
+
850
+ completed = Reline::Unicode.common_prefix(candidates, ignore_case: @config.completion_ignore_case)
851
+ return if completed.empty?
852
+
853
+ append_character = ''
854
+ if candidates.include?(completed)
855
+ if candidates.one?
856
+ append_character = quote || completion_append_character.to_s
857
+ @completion_state = CompletionState::PERFECT_MATCH
858
+ elsif @config.show_all_if_ambiguous
859
+ menu(candidates)
860
+ @completion_state = CompletionState::PERFECT_MATCH
892
861
  else
893
- @completion_state = CompletionState::MENU
894
- perform_completion(candidates, true) if @config.show_all_if_ambiguous
895
- end
896
- unless just_show_list
897
- @buffer_of_lines[@line_index] = (preposing + completed + append_character + postposing).split("\n")[@line_index] || String.new(encoding: encoding)
898
- line_to_pointer = (preposing + completed + append_character).split("\n")[@line_index] || String.new(encoding: encoding)
899
- @byte_pointer = line_to_pointer.bytesize
862
+ @completion_state = CompletionState::MENU_WITH_PERFECT_MATCH
900
863
  end
864
+ @perfect_matched = completed
865
+ else
866
+ @completion_state = CompletionState::MENU
867
+ menu(candidates) if @config.show_all_if_ambiguous
901
868
  end
869
+ @buffer_of_lines[@line_index] = (preposing + completed + append_character + postposing).split("\n")[@line_index] || String.new(encoding: encoding)
870
+ line_to_pointer = (preposing + completed + append_character).split("\n")[@line_index] || String.new(encoding: encoding)
871
+ @byte_pointer = line_to_pointer.bytesize
902
872
  end
903
873
 
904
874
  def dialog_proc_scope_completion_journey_data
@@ -927,8 +897,8 @@ class Reline::LineEditor
927
897
  end
928
898
 
929
899
  private def retrieve_completion_journey_state
930
- preposing, target, postposing = retrieve_completion_block
931
- list = call_completion_proc
900
+ preposing, target, postposing, quote = retrieve_completion_block
901
+ list = call_completion_proc(preposing, target, postposing, quote)
932
902
  return unless list.is_a?(Array)
933
903
 
934
904
  candidates = list.select{ |item| item.start_with?(target) }
@@ -1003,10 +973,18 @@ class Reline::LineEditor
1003
973
  @drop_terminate_spaces = false
1004
974
  end
1005
975
 
976
+ ARGUMENT_DIGIT_METHODS = %i[ed_digit vi_zero ed_argument_digit]
977
+ VI_WAITING_ACCEPT_METHODS = %i[vi_change_meta vi_delete_meta vi_yank ed_insert ed_argument_digit]
978
+
1006
979
  private def process_key(key, method_symbol)
1007
- if key.is_a?(Symbol)
1008
- cleanup_waiting
1009
- elsif @waiting_proc
980
+ if @waiting_proc
981
+ cleanup_waiting unless key.size == 1
982
+ end
983
+ if @vi_waiting_operator
984
+ cleanup_waiting unless VI_WAITING_ACCEPT_METHODS.include?(method_symbol) || VI_MOTIONS.include?(method_symbol)
985
+ end
986
+
987
+ if @waiting_proc
1010
988
  old_byte_pointer = @byte_pointer
1011
989
  @waiting_proc.call(key)
1012
990
  if @vi_waiting_operator
@@ -1020,23 +998,14 @@ class Reline::LineEditor
1020
998
  return
1021
999
  end
1022
1000
 
1001
+ # Reject multibyte input (converted to ed_insert) in vi_command mode
1002
+ return if method_symbol == :ed_insert && @config.editing_mode_is?(:vi_command)
1003
+
1023
1004
  if method_symbol and respond_to?(method_symbol, true)
1024
1005
  method_obj = method(method_symbol)
1025
1006
  end
1026
- if method_symbol and key.is_a?(Symbol)
1027
- if @vi_arg and argumentable?(method_obj)
1028
- run_for_operators(key, method_symbol) do |with_operator|
1029
- wrap_method_call(method_symbol, method_obj, key, with_operator)
1030
- end
1031
- else
1032
- wrap_method_call(method_symbol, method_obj, key) if method_obj
1033
- end
1034
- @kill_ring.process
1035
- if @vi_arg
1036
- @vi_arg = nil
1037
- end
1038
- elsif @vi_arg
1039
- if key.chr =~ /[0-9]/
1007
+ if @vi_arg
1008
+ if ARGUMENT_DIGIT_METHODS.include?(method_symbol)
1040
1009
  ed_argument_digit(key)
1041
1010
  else
1042
1011
  if argumentable?(method_obj)
@@ -1045,13 +1014,9 @@ class Reline::LineEditor
1045
1014
  end
1046
1015
  elsif method_obj
1047
1016
  wrap_method_call(method_symbol, method_obj, key)
1048
- else
1049
- ed_insert(key) unless @config.editing_mode_is?(:vi_command)
1050
1017
  end
1051
1018
  @kill_ring.process
1052
- if @vi_arg
1053
- @vi_arg = nil
1054
- end
1019
+ @vi_arg = nil
1055
1020
  end
1056
1021
  elsif method_obj
1057
1022
  if method_symbol == :ed_argument_digit
@@ -1062,30 +1027,6 @@ class Reline::LineEditor
1062
1027
  end
1063
1028
  end
1064
1029
  @kill_ring.process
1065
- else
1066
- ed_insert(key) unless @config.editing_mode_is?(:vi_command)
1067
- end
1068
- end
1069
-
1070
- private def normal_char(key)
1071
- @multibyte_buffer << key.combined_char
1072
- if @multibyte_buffer.size > 1
1073
- if @multibyte_buffer.dup.force_encoding(encoding).valid_encoding?
1074
- process_key(@multibyte_buffer.dup.force_encoding(encoding), nil)
1075
- @multibyte_buffer.clear
1076
- else
1077
- # invalid
1078
- return
1079
- end
1080
- else # single byte
1081
- return if key.char >= 128 # maybe, first byte of multi byte
1082
- method_symbol = @config.editing_mode.get_method(key.combined_char)
1083
- process_key(key.combined_char, method_symbol)
1084
- @multibyte_buffer.clear
1085
- end
1086
- if @config.editing_mode_is?(:vi_command) and @byte_pointer > 0 and @byte_pointer == current_line.bytesize
1087
- byte_size = Reline::Unicode.get_prev_mbchar_size(@buffer_of_lines[@line_index], @byte_pointer)
1088
- @byte_pointer -= byte_size
1089
1030
  end
1090
1031
  end
1091
1032
 
@@ -1102,23 +1043,23 @@ class Reline::LineEditor
1102
1043
  def input_key(key)
1103
1044
  save_old_buffer
1104
1045
  @config.reset_oneshot_key_bindings
1105
- @dialogs.each do |dialog|
1106
- if key.char.instance_of?(Symbol) and key.char == dialog.name
1107
- return
1108
- end
1109
- end
1110
1046
  if key.char.nil?
1111
1047
  process_insert(force: true)
1112
1048
  @eof = buffer_empty?
1113
1049
  finish
1114
1050
  return
1115
1051
  end
1052
+ @dialogs.each do |dialog|
1053
+ if key.method_symbol == dialog.name
1054
+ return
1055
+ end
1056
+ end
1116
1057
  @completion_occurs = false
1117
1058
 
1118
- if key.char.is_a?(Symbol)
1119
- process_key(key.char, key.char)
1120
- else
1121
- normal_char(key)
1059
+ process_key(key.char, key.method_symbol)
1060
+ if @config.editing_mode_is?(:vi_command) and @byte_pointer > 0 and @byte_pointer == current_line.bytesize
1061
+ byte_size = Reline::Unicode.get_prev_mbchar_size(@buffer_of_lines[@line_index], @byte_pointer)
1062
+ @byte_pointer -= byte_size
1122
1063
  end
1123
1064
 
1124
1065
  @prev_action_state, @next_action_state = @next_action_state, NullActionState
@@ -1128,8 +1069,8 @@ class Reline::LineEditor
1128
1069
  @completion_journey_state = nil
1129
1070
  end
1130
1071
 
1131
- push_input_lines unless @undoing
1132
- @undoing = false
1072
+ push_input_lines unless @restoring
1073
+ @restoring = false
1133
1074
 
1134
1075
  if @in_pasting
1135
1076
  clear_dialogs
@@ -1178,9 +1119,8 @@ class Reline::LineEditor
1178
1119
  end
1179
1120
  end
1180
1121
 
1181
- def call_completion_proc
1182
- result = retrieve_completion_block(true)
1183
- pre, target, post = result
1122
+ def call_completion_proc(pre, target, post, quote)
1123
+ Reline.core.instance_variable_set(:@completion_quote_character, quote)
1184
1124
  result = call_completion_proc_with_checking_args(pre, target, post)
1185
1125
  Reline.core.instance_variable_set(:@completion_quote_character, nil)
1186
1126
  result
@@ -1244,84 +1184,32 @@ class Reline::LineEditor
1244
1184
  process_auto_indent
1245
1185
  end
1246
1186
 
1247
- def set_current_lines(lines, byte_pointer = nil, line_index = 0)
1248
- cursor = current_byte_pointer_cursor
1249
- @buffer_of_lines = lines
1250
- @line_index = line_index
1251
- if byte_pointer
1252
- @byte_pointer = byte_pointer
1253
- else
1254
- calculate_nearest_cursor(cursor)
1255
- end
1256
- process_auto_indent
1257
- end
1258
-
1259
- def retrieve_completion_block(set_completion_quote_character = false)
1260
- if Reline.completer_word_break_characters.empty?
1261
- word_break_regexp = nil
1262
- else
1263
- word_break_regexp = /\A[#{Regexp.escape(Reline.completer_word_break_characters)}]/
1264
- end
1265
- if Reline.completer_quote_characters.empty?
1266
- quote_characters_regexp = nil
1267
- else
1268
- quote_characters_regexp = /\A[#{Regexp.escape(Reline.completer_quote_characters)}]/
1269
- end
1270
- before = current_line.byteslice(0, @byte_pointer)
1271
- rest = nil
1272
- break_pointer = nil
1187
+ def retrieve_completion_block
1188
+ quote_characters = Reline.completer_quote_characters
1189
+ before = current_line.byteslice(0, @byte_pointer).grapheme_clusters
1273
1190
  quote = nil
1274
- closing_quote = nil
1275
- escaped_quote = nil
1276
- i = 0
1277
- while i < @byte_pointer do
1278
- slice = current_line.byteslice(i, @byte_pointer - i)
1279
- unless slice.valid_encoding?
1280
- i += 1
1281
- next
1282
- end
1283
- if quote and slice.start_with?(closing_quote)
1284
- quote = nil
1285
- i += 1
1286
- rest = nil
1287
- elsif quote and slice.start_with?(escaped_quote)
1288
- # skip
1289
- i += 2
1290
- elsif quote_characters_regexp and slice =~ quote_characters_regexp # find new "
1291
- rest = $'
1292
- quote = $&
1293
- closing_quote = /(?!\\)#{Regexp.escape(quote)}/
1294
- escaped_quote = /\\#{Regexp.escape(quote)}/
1295
- i += 1
1296
- break_pointer = i - 1
1297
- elsif word_break_regexp and not quote and slice =~ word_break_regexp
1298
- rest = $'
1299
- i += 1
1300
- before = current_line.byteslice(i, @byte_pointer - i)
1301
- break_pointer = i
1302
- else
1303
- i += 1
1304
- end
1305
- end
1306
- postposing = current_line.byteslice(@byte_pointer, current_line.bytesize - @byte_pointer)
1307
- if rest
1308
- preposing = current_line.byteslice(0, break_pointer)
1309
- target = rest
1310
- if set_completion_quote_character and quote
1311
- Reline.core.instance_variable_set(:@completion_quote_character, quote)
1312
- if postposing !~ /(?!\\)#{Regexp.escape(quote)}/ # closing quote
1313
- insert_text(quote)
1191
+ # Calcualte closing quote when cursor is at the end of the line
1192
+ if current_line.bytesize == @byte_pointer && !quote_characters.empty?
1193
+ escaped = false
1194
+ before.each do |c|
1195
+ if escaped
1196
+ escaped = false
1197
+ next
1198
+ elsif c == '\\'
1199
+ escaped = true
1200
+ elsif quote
1201
+ quote = nil if c == quote
1202
+ elsif quote_characters.include?(c)
1203
+ quote = c
1314
1204
  end
1315
1205
  end
1316
- else
1317
- preposing = ''
1318
- if break_pointer
1319
- preposing = current_line.byteslice(0, break_pointer)
1320
- else
1321
- preposing = ''
1322
- end
1323
- target = before
1324
1206
  end
1207
+
1208
+ word_break_characters = quote_characters + Reline.completer_word_break_characters
1209
+ break_index = before.rindex { |c| word_break_characters.include?(c) || quote_characters.include?(c) } || -1
1210
+ preposing = before.take(break_index + 1).join
1211
+ target = before.drop(break_index + 1).join
1212
+ postposing = current_line.byteslice(@byte_pointer, current_line.bytesize - @byte_pointer)
1325
1213
  lines = whole_lines
1326
1214
  if @line_index > 0
1327
1215
  preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing
@@ -1329,7 +1217,7 @@ class Reline::LineEditor
1329
1217
  if (lines.size - 1) > @line_index
1330
1218
  postposing = postposing + "\n" + lines[(@line_index + 1)..-1].join("\n")
1331
1219
  end
1332
- [preposing.encode(encoding), target.encode(encoding), postposing.encode(encoding)]
1220
+ [preposing.encode(encoding), target.encode(encoding), postposing.encode(encoding), quote&.encode(encoding)]
1333
1221
  end
1334
1222
 
1335
1223
  def confirm_multiline_termination
@@ -1338,7 +1226,6 @@ class Reline::LineEditor
1338
1226
  end
1339
1227
 
1340
1228
  def insert_multiline_text(text)
1341
- save_old_buffer
1342
1229
  pre = @buffer_of_lines[@line_index].byteslice(0, @byte_pointer)
1343
1230
  post = @buffer_of_lines[@line_index].byteslice(@byte_pointer..)
1344
1231
  lines = (pre + Reline::Unicode.safe_encode(text, encoding).gsub(/\r\n?/, "\n") + post).split("\n", -1)
@@ -1346,7 +1233,6 @@ class Reline::LineEditor
1346
1233
  @buffer_of_lines[@line_index, 1] = lines
1347
1234
  @line_index += lines.size - 1
1348
1235
  @byte_pointer = @buffer_of_lines[@line_index].bytesize - post.bytesize
1349
- push_input_lines
1350
1236
  end
1351
1237
 
1352
1238
  def insert_text(text)
@@ -1460,10 +1346,11 @@ class Reline::LineEditor
1460
1346
  @completion_occurs = move_completed_list(:down)
1461
1347
  else
1462
1348
  @completion_journey_state = nil
1463
- result = call_completion_proc
1349
+ pre, target, post, quote = retrieve_completion_block
1350
+ result = call_completion_proc(pre, target, post, quote)
1464
1351
  if result.is_a?(Array)
1465
1352
  @completion_occurs = true
1466
- perform_completion(result, false)
1353
+ perform_completion(pre, target, post, quote, result)
1467
1354
  end
1468
1355
  end
1469
1356
  end
@@ -1511,21 +1398,11 @@ class Reline::LineEditor
1511
1398
  # digit or if the existing argument is already greater than a
1512
1399
  # million.
1513
1400
  # GNU Readline:: +self-insert+ (a, b, A, 1, !, …) Insert yourself.
1514
- private def ed_insert(key)
1515
- if key.instance_of?(String)
1516
- begin
1517
- key.encode(Encoding::UTF_8)
1518
- rescue Encoding::UndefinedConversionError
1519
- return
1520
- end
1521
- str = key
1522
- else
1523
- begin
1524
- key.chr.encode(Encoding::UTF_8)
1525
- rescue Encoding::UndefinedConversionError
1526
- return
1527
- end
1528
- str = key.chr
1401
+ private def ed_insert(str)
1402
+ begin
1403
+ str.encode(Encoding::UTF_8)
1404
+ rescue Encoding::UndefinedConversionError
1405
+ return
1529
1406
  end
1530
1407
  if @in_pasting
1531
1408
  @continuous_insertion_buffer << str
@@ -1539,21 +1416,16 @@ class Reline::LineEditor
1539
1416
  alias_method :ed_digit, :ed_insert
1540
1417
  alias_method :self_insert, :ed_insert
1541
1418
 
1542
- private def ed_quoted_insert(str, arg: 1)
1543
- @waiting_proc = proc { |key|
1544
- arg.times do
1545
- if key == "\C-j".ord or key == "\C-m".ord
1546
- key_newline(key)
1547
- elsif key == 0
1548
- # Ignore NUL.
1549
- else
1550
- ed_insert(key)
1551
- end
1419
+ private def insert_raw_char(str, arg: 1)
1420
+ arg.times do
1421
+ if str == "\C-j" or str == "\C-m"
1422
+ key_newline(str)
1423
+ elsif str != "\0"
1424
+ # Ignore NUL.
1425
+ ed_insert(str)
1552
1426
  end
1553
- @waiting_proc = nil
1554
- }
1427
+ end
1555
1428
  end
1556
- alias_method :quoted_insert, :ed_quoted_insert
1557
1429
 
1558
1430
  private def ed_next_char(key, arg: 1)
1559
1431
  byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
@@ -1582,7 +1454,7 @@ class Reline::LineEditor
1582
1454
  alias_method :backward_char, :ed_prev_char
1583
1455
 
1584
1456
  private def vi_first_print(key)
1585
- @byte_pointer, = Reline::Unicode.vi_first_print(current_line)
1457
+ @byte_pointer = Reline::Unicode.vi_first_print(current_line)
1586
1458
  end
1587
1459
 
1588
1460
  private def ed_move_to_beg(key)
@@ -1598,26 +1470,21 @@ class Reline::LineEditor
1598
1470
 
1599
1471
  private def generate_searcher(search_key)
1600
1472
  search_word = String.new(encoding: encoding)
1601
- multibyte_buf = String.new(encoding: 'ASCII-8BIT')
1602
1473
  hit_pointer = nil
1603
1474
  lambda do |key|
1604
1475
  search_again = false
1605
1476
  case key
1606
- when "\C-h".ord, "\C-?".ord
1477
+ when "\C-h", "\C-?"
1607
1478
  grapheme_clusters = search_word.grapheme_clusters
1608
1479
  if grapheme_clusters.size > 0
1609
1480
  grapheme_clusters.pop
1610
1481
  search_word = grapheme_clusters.join
1611
1482
  end
1612
- when "\C-r".ord, "\C-s".ord
1483
+ when "\C-r", "\C-s"
1613
1484
  search_again = true if search_key == key
1614
1485
  search_key = key
1615
1486
  else
1616
- multibyte_buf << key
1617
- if multibyte_buf.dup.force_encoding(encoding).valid_encoding?
1618
- search_word << multibyte_buf.dup.force_encoding(encoding)
1619
- multibyte_buf.clear
1620
- end
1487
+ search_word << key
1621
1488
  end
1622
1489
  hit = nil
1623
1490
  if not search_word.empty? and @line_backup_in_history&.include?(search_word)
@@ -1630,10 +1497,10 @@ class Reline::LineEditor
1630
1497
  end
1631
1498
  if @history_pointer
1632
1499
  case search_key
1633
- when "\C-r".ord
1500
+ when "\C-r"
1634
1501
  history_pointer_base = 0
1635
1502
  history = Reline::HISTORY[0..(@history_pointer - 1)]
1636
- when "\C-s".ord
1503
+ when "\C-s"
1637
1504
  history_pointer_base = @history_pointer + 1
1638
1505
  history = Reline::HISTORY[(@history_pointer + 1)..-1]
1639
1506
  end
@@ -1643,10 +1510,10 @@ class Reline::LineEditor
1643
1510
  end
1644
1511
  elsif @history_pointer
1645
1512
  case search_key
1646
- when "\C-r".ord
1513
+ when "\C-r"
1647
1514
  history_pointer_base = 0
1648
1515
  history = Reline::HISTORY[0..@history_pointer]
1649
- when "\C-s".ord
1516
+ when "\C-s"
1650
1517
  history_pointer_base = @history_pointer
1651
1518
  history = Reline::HISTORY[@history_pointer..-1]
1652
1519
  end
@@ -1655,11 +1522,11 @@ class Reline::LineEditor
1655
1522
  history = Reline::HISTORY
1656
1523
  end
1657
1524
  case search_key
1658
- when "\C-r".ord
1525
+ when "\C-r"
1659
1526
  hit_index = history.rindex { |item|
1660
1527
  item.include?(search_word)
1661
1528
  }
1662
- when "\C-s".ord
1529
+ when "\C-s"
1663
1530
  hit_index = history.index { |item|
1664
1531
  item.include?(search_word)
1665
1532
  }
@@ -1670,9 +1537,9 @@ class Reline::LineEditor
1670
1537
  end
1671
1538
  end
1672
1539
  case search_key
1673
- when "\C-r".ord
1540
+ when "\C-r"
1674
1541
  prompt_name = 'reverse-i-search'
1675
- when "\C-s".ord
1542
+ when "\C-s"
1676
1543
  prompt_name = 'i-search'
1677
1544
  end
1678
1545
  prompt_name = "failed #{prompt_name}" unless hit
@@ -1684,16 +1551,15 @@ class Reline::LineEditor
1684
1551
  backup = @buffer_of_lines.dup, @line_index, @byte_pointer, @history_pointer, @line_backup_in_history
1685
1552
  searcher = generate_searcher(key)
1686
1553
  @searching_prompt = "(reverse-i-search)`': "
1687
- termination_keys = ["\C-j".ord]
1688
- termination_keys.concat(@config.isearch_terminators.chars.map(&:ord)) if @config.isearch_terminators
1554
+ termination_keys = ["\C-j"]
1555
+ termination_keys.concat(@config.isearch_terminators.chars) if @config.isearch_terminators
1689
1556
  @waiting_proc = ->(k) {
1690
- chr = k.is_a?(String) ? k : k.chr(Encoding::ASCII_8BIT)
1691
- if k == "\C-g".ord
1557
+ if k == "\C-g"
1692
1558
  # cancel search and restore buffer
1693
1559
  @buffer_of_lines, @line_index, @byte_pointer, @history_pointer, @line_backup_in_history = backup
1694
1560
  @searching_prompt = nil
1695
1561
  @waiting_proc = nil
1696
- elsif !termination_keys.include?(k) && (chr.match?(/[[:print:]]/) || k == "\C-h".ord || k == "\C-?".ord || k == "\C-r".ord || k == "\C-s".ord)
1562
+ elsif !termination_keys.include?(k) && (k.match?(/[[:print:]]/) || k == "\C-h" || k == "\C-?" || k == "\C-r" || k == "\C-s")
1697
1563
  search_word, prompt_name, hit_pointer = searcher.call(k)
1698
1564
  Reline.last_incremental_search = search_word
1699
1565
  @searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
@@ -1909,7 +1775,7 @@ class Reline::LineEditor
1909
1775
  alias_method :kill_whole_line, :em_kill_line
1910
1776
 
1911
1777
  private def em_delete(key)
1912
- if buffer_empty? and key == "\C-d".ord
1778
+ if buffer_empty? and key == "\C-d"
1913
1779
  @eof = true
1914
1780
  finish
1915
1781
  elsif @byte_pointer < current_line.bytesize
@@ -1927,9 +1793,11 @@ class Reline::LineEditor
1927
1793
  if current_line.empty? or @byte_pointer < current_line.bytesize
1928
1794
  em_delete(key)
1929
1795
  elsif !@config.autocompletion # show completed list
1930
- result = call_completion_proc
1796
+ pre, target, post, quote = retrieve_completion_block
1797
+ result = call_completion_proc(pre, target, post, quote)
1931
1798
  if result.is_a?(Array)
1932
- perform_completion(result, true)
1799
+ candidates = filter_normalize_candidates(target, result)
1800
+ menu(candidates)
1933
1801
  end
1934
1802
  end
1935
1803
  end
@@ -1961,7 +1829,7 @@ class Reline::LineEditor
1961
1829
 
1962
1830
  private def em_next_word(key)
1963
1831
  if current_line.bytesize > @byte_pointer
1964
- byte_size, _ = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
1832
+ byte_size = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
1965
1833
  @byte_pointer += byte_size
1966
1834
  end
1967
1835
  end
@@ -1969,7 +1837,7 @@ class Reline::LineEditor
1969
1837
 
1970
1838
  private def ed_prev_word(key)
1971
1839
  if @byte_pointer > 0
1972
- byte_size, _ = Reline::Unicode.em_backward_word(current_line, @byte_pointer)
1840
+ byte_size = Reline::Unicode.em_backward_word(current_line, @byte_pointer)
1973
1841
  @byte_pointer -= byte_size
1974
1842
  end
1975
1843
  end
@@ -1977,7 +1845,7 @@ class Reline::LineEditor
1977
1845
 
1978
1846
  private def em_delete_next_word(key)
1979
1847
  if current_line.bytesize > @byte_pointer
1980
- byte_size, _ = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
1848
+ byte_size = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
1981
1849
  line, word = byteslice!(current_line, @byte_pointer, byte_size)
1982
1850
  set_current_line(line)
1983
1851
  @kill_ring.append(word)
@@ -1987,7 +1855,7 @@ class Reline::LineEditor
1987
1855
 
1988
1856
  private def ed_delete_prev_word(key)
1989
1857
  if @byte_pointer > 0
1990
- byte_size, _ = Reline::Unicode.em_backward_word(current_line, @byte_pointer)
1858
+ byte_size = Reline::Unicode.em_backward_word(current_line, @byte_pointer)
1991
1859
  line, word = byteslice!(current_line, @byte_pointer - byte_size, byte_size)
1992
1860
  set_current_line(line, @byte_pointer - byte_size)
1993
1861
  @kill_ring.append(word, true)
@@ -2027,7 +1895,7 @@ class Reline::LineEditor
2027
1895
 
2028
1896
  private def em_capitol_case(key)
2029
1897
  if current_line.bytesize > @byte_pointer
2030
- byte_size, _, new_str = Reline::Unicode.em_forward_word_with_capitalization(current_line, @byte_pointer)
1898
+ byte_size, new_str = Reline::Unicode.em_forward_word_with_capitalization(current_line, @byte_pointer)
2031
1899
  before = current_line.byteslice(0, @byte_pointer)
2032
1900
  after = current_line.byteslice((@byte_pointer + byte_size)..-1)
2033
1901
  set_current_line(before + new_str + after, @byte_pointer + new_str.bytesize)
@@ -2037,7 +1905,7 @@ class Reline::LineEditor
2037
1905
 
2038
1906
  private def em_lower_case(key)
2039
1907
  if current_line.bytesize > @byte_pointer
2040
- byte_size, = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
1908
+ byte_size = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
2041
1909
  part = current_line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
2042
1910
  mbchar =~ /[A-Z]/ ? mbchar.downcase : mbchar
2043
1911
  }.join
@@ -2050,7 +1918,7 @@ class Reline::LineEditor
2050
1918
 
2051
1919
  private def em_upper_case(key)
2052
1920
  if current_line.bytesize > @byte_pointer
2053
- byte_size, = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
1921
+ byte_size = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
2054
1922
  part = current_line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
2055
1923
  mbchar =~ /[a-z]/ ? mbchar.upcase : mbchar
2056
1924
  }.join
@@ -2063,7 +1931,7 @@ class Reline::LineEditor
2063
1931
 
2064
1932
  private def em_kill_region(key)
2065
1933
  if @byte_pointer > 0
2066
- byte_size, _ = Reline::Unicode.em_big_backward_word(current_line, @byte_pointer)
1934
+ byte_size = Reline::Unicode.em_big_backward_word(current_line, @byte_pointer)
2067
1935
  line, deleted = byteslice!(current_line, @byte_pointer - byte_size, byte_size)
2068
1936
  set_current_line(line, @byte_pointer - byte_size)
2069
1937
  @kill_ring.append(deleted, true)
@@ -2094,7 +1962,7 @@ class Reline::LineEditor
2094
1962
 
2095
1963
  private def vi_next_word(key, arg: 1)
2096
1964
  if current_line.bytesize > @byte_pointer
2097
- byte_size, _ = Reline::Unicode.vi_forward_word(current_line, @byte_pointer, @drop_terminate_spaces)
1965
+ byte_size = Reline::Unicode.vi_forward_word(current_line, @byte_pointer, @drop_terminate_spaces)
2098
1966
  @byte_pointer += byte_size
2099
1967
  end
2100
1968
  arg -= 1
@@ -2103,7 +1971,7 @@ class Reline::LineEditor
2103
1971
 
2104
1972
  private def vi_prev_word(key, arg: 1)
2105
1973
  if @byte_pointer > 0
2106
- byte_size, _ = Reline::Unicode.vi_backward_word(current_line, @byte_pointer)
1974
+ byte_size = Reline::Unicode.vi_backward_word(current_line, @byte_pointer)
2107
1975
  @byte_pointer -= byte_size
2108
1976
  end
2109
1977
  arg -= 1
@@ -2112,7 +1980,7 @@ class Reline::LineEditor
2112
1980
 
2113
1981
  private def vi_end_word(key, arg: 1, inclusive: false)
2114
1982
  if current_line.bytesize > @byte_pointer
2115
- byte_size, _ = Reline::Unicode.vi_forward_end_word(current_line, @byte_pointer)
1983
+ byte_size = Reline::Unicode.vi_forward_end_word(current_line, @byte_pointer)
2116
1984
  @byte_pointer += byte_size
2117
1985
  end
2118
1986
  arg -= 1
@@ -2127,7 +1995,7 @@ class Reline::LineEditor
2127
1995
 
2128
1996
  private def vi_next_big_word(key, arg: 1)
2129
1997
  if current_line.bytesize > @byte_pointer
2130
- byte_size, _ = Reline::Unicode.vi_big_forward_word(current_line, @byte_pointer)
1998
+ byte_size = Reline::Unicode.vi_big_forward_word(current_line, @byte_pointer)
2131
1999
  @byte_pointer += byte_size
2132
2000
  end
2133
2001
  arg -= 1
@@ -2136,7 +2004,7 @@ class Reline::LineEditor
2136
2004
 
2137
2005
  private def vi_prev_big_word(key, arg: 1)
2138
2006
  if @byte_pointer > 0
2139
- byte_size, _ = Reline::Unicode.vi_big_backward_word(current_line, @byte_pointer)
2007
+ byte_size = Reline::Unicode.vi_big_backward_word(current_line, @byte_pointer)
2140
2008
  @byte_pointer -= byte_size
2141
2009
  end
2142
2010
  arg -= 1
@@ -2145,7 +2013,7 @@ class Reline::LineEditor
2145
2013
 
2146
2014
  private def vi_end_big_word(key, arg: 1, inclusive: false)
2147
2015
  if current_line.bytesize > @byte_pointer
2148
- byte_size, _ = Reline::Unicode.vi_big_forward_end_word(current_line, @byte_pointer)
2016
+ byte_size = Reline::Unicode.vi_big_forward_end_word(current_line, @byte_pointer)
2149
2017
  @byte_pointer += byte_size
2150
2018
  end
2151
2019
  arg -= 1
@@ -2325,20 +2193,9 @@ class Reline::LineEditor
2325
2193
  end
2326
2194
 
2327
2195
  private def ed_argument_digit(key)
2328
- if @vi_arg.nil?
2329
- if key.chr.to_i.zero?
2330
- if key.anybits?(0b10000000)
2331
- unescaped_key = key ^ 0b10000000
2332
- unless unescaped_key.chr.to_i.zero?
2333
- @vi_arg = unescaped_key.chr.to_i
2334
- end
2335
- end
2336
- else
2337
- @vi_arg = key.chr.to_i
2338
- end
2339
- else
2340
- @vi_arg = @vi_arg * 10 + key.chr.to_i
2341
- end
2196
+ # key is expected to be `ESC digit` or `digit`
2197
+ num = key[/\d/].to_i
2198
+ @vi_arg = (@vi_arg || 0) * 10 + num
2342
2199
  end
2343
2200
 
2344
2201
  private def vi_to_column(key, arg: 0)
@@ -2357,7 +2214,7 @@ class Reline::LineEditor
2357
2214
  before = current_line.byteslice(0, @byte_pointer)
2358
2215
  remaining_point = @byte_pointer + byte_size
2359
2216
  after = current_line.byteslice(remaining_point, current_line.bytesize - remaining_point)
2360
- set_current_line(before + k.chr + after)
2217
+ set_current_line(before + k + after)
2361
2218
  @waiting_proc = nil
2362
2219
  elsif arg > 1
2363
2220
  byte_size = 0
@@ -2367,7 +2224,7 @@ class Reline::LineEditor
2367
2224
  before = current_line.byteslice(0, @byte_pointer)
2368
2225
  remaining_point = @byte_pointer + byte_size
2369
2226
  after = current_line.byteslice(remaining_point, current_line.bytesize - remaining_point)
2370
- replaced = k.chr * arg
2227
+ replaced = k * arg
2371
2228
  set_current_line(before + replaced + after, @byte_pointer + replaced.bytesize)
2372
2229
  @waiting_proc = nil
2373
2230
  end
@@ -2383,11 +2240,6 @@ class Reline::LineEditor
2383
2240
  end
2384
2241
 
2385
2242
  private def search_next_char(key, arg, need_prev_char: false, inclusive: false)
2386
- if key.instance_of?(String)
2387
- inputted_char = key
2388
- else
2389
- inputted_char = key.chr
2390
- end
2391
2243
  prev_total = nil
2392
2244
  total = nil
2393
2245
  found = false
@@ -2398,7 +2250,7 @@ class Reline::LineEditor
2398
2250
  width = Reline::Unicode.get_mbchar_width(mbchar)
2399
2251
  total = [mbchar.bytesize, width]
2400
2252
  else
2401
- if inputted_char == mbchar
2253
+ if key == mbchar
2402
2254
  arg -= 1
2403
2255
  if arg.zero?
2404
2256
  found = true
@@ -2435,11 +2287,6 @@ class Reline::LineEditor
2435
2287
  end
2436
2288
 
2437
2289
  private def search_prev_char(key, arg, need_next_char = false)
2438
- if key.instance_of?(String)
2439
- inputted_char = key
2440
- else
2441
- inputted_char = key.chr
2442
- end
2443
2290
  prev_total = nil
2444
2291
  total = nil
2445
2292
  found = false
@@ -2450,7 +2297,7 @@ class Reline::LineEditor
2450
2297
  width = Reline::Unicode.get_mbchar_width(mbchar)
2451
2298
  total = [mbchar.bytesize, width]
2452
2299
  else
2453
- if inputted_char == mbchar
2300
+ if key == mbchar
2454
2301
  arg -= 1
2455
2302
  if arg.zero?
2456
2303
  found = true
@@ -2502,24 +2349,23 @@ class Reline::LineEditor
2502
2349
  @config.editing_mode = :vi_insert
2503
2350
  end
2504
2351
 
2505
- private def undo(_key)
2506
- @undoing = true
2352
+ private def move_undo_redo(direction)
2353
+ @restoring = true
2354
+ return unless (0..@input_lines.size - 1).cover?(@input_lines_position + direction)
2507
2355
 
2508
- return if @input_lines_position <= 0
2356
+ @input_lines_position += direction
2357
+ buffer_of_lines, byte_pointer, line_index = @input_lines[@input_lines_position]
2358
+ @buffer_of_lines = buffer_of_lines.dup
2359
+ @line_index = line_index
2360
+ @byte_pointer = byte_pointer
2361
+ end
2509
2362
 
2510
- @input_lines_position -= 1
2511
- target_lines, target_cursor_x, target_cursor_y = @input_lines[@input_lines_position]
2512
- set_current_lines(target_lines.dup, target_cursor_x, target_cursor_y)
2363
+ private def undo(_key)
2364
+ move_undo_redo(-1)
2513
2365
  end
2514
2366
 
2515
2367
  private def redo(_key)
2516
- @undoing = true
2517
-
2518
- return if @input_lines_position >= @input_lines.size - 1
2519
-
2520
- @input_lines_position += 1
2521
- target_lines, target_cursor_x, target_cursor_y = @input_lines[@input_lines_position]
2522
- set_current_lines(target_lines.dup, target_cursor_x, target_cursor_y)
2368
+ move_undo_redo(+1)
2523
2369
  end
2524
2370
 
2525
2371
  private def prev_action_state_value(type)