reline 0.2.5 → 0.2.8.pre.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1da0f8ec80f16c720589413fbc14c02ac199dcfc0b0344270a61edbad4aec966
4
- data.tar.gz: d214e02ad3500323e98d97d57677b7bbc94ea4c09df0b529d5b21ea66374a6a8
3
+ metadata.gz: 45220c00c821168746654f6c7f5ad3cc88ff8b4e3d3b6e5aefccc83b34392041
4
+ data.tar.gz: a3bbf71304ce777dbae4d5b1cc91f5a2b297b811194947e5e8e8a27b229667a2
5
5
  SHA512:
6
- metadata.gz: 0d218a92032ad8b02218e44bda7fe7d7404845f6cc9cceb9aade01d3c49bc33a0396b02857de417ecee59b5d65c0cb787eecec8dea60180ae3b5457df6c89ebd
7
- data.tar.gz: 12c4b8d00608e1f9f16a1c5e530ae86c7530f39ddb1c0f6100b1d4ae87a6dbd53def15d9f387941062aafdb57360fd499dc5850a5bd60d07776daf60e057acfe
6
+ metadata.gz: a9d8e4a15c1eff337dc476b8b4f2d603b64d35faf255a9984f119b2f14a76bcd93f145d01d1e0698e2cb0a3d80c28c8b3baa343866e3cb7b8b28ab4642e77ebf
7
+ data.tar.gz: fcc70f912f7472acb0d7eeee8f221a60d06e3f08cd079b37a988392eca35723753e3b02638a4bcbb41876dc59cfcb46349b16bfff88e336ecea9bd2a4cf93231
data/README.md CHANGED
@@ -8,6 +8,52 @@ This is a screen capture of *IRB improved by Reline*.
8
8
 
9
9
  Reline is compatible with the API of Ruby's stdlib 'readline', GNU Readline and Editline by pure Ruby implementation.
10
10
 
11
+ ## Usage
12
+
13
+ ### Single line editing mode
14
+
15
+ It's compatible with the readline standard library.
16
+
17
+ See [the document of readline stdlib](https://ruby-doc.org/stdlib/libdoc/readline/rdoc/Readline.html) or [bin/example](https://github.com/ruby/reline/blob/master/bin/example).
18
+
19
+ ### Multi-line editing mode
20
+
21
+ ```ruby
22
+ require "reline"
23
+
24
+ prompt = 'prompt> '
25
+ use_history = true
26
+
27
+ begin
28
+ while true
29
+ text = Reline.readmultiline(prompt, use_history) do |multiline_input|
30
+ # Accept the input until `end` is entered
31
+ multiline_input.split.last == "end"
32
+ end
33
+
34
+ puts 'You entered:'
35
+ puts text
36
+ end
37
+ # If you want to exit, type Ctrl-C
38
+ rescue Interrupt
39
+ puts '^C'
40
+ exit 0
41
+ end
42
+ ```
43
+
44
+ ```bash
45
+ $ ruby example.rb
46
+ prompt> aaa
47
+ prompt> bbb
48
+ prompt> end
49
+ You entered:
50
+ aaa
51
+ bbb
52
+ end
53
+ ```
54
+
55
+ See also: [test/reline/yamatanooroti/multiline_repl](https://github.com/ruby/reline/blob/master/test/reline/yamatanooroti/multiline_repl)
56
+
11
57
  ## License
12
58
 
13
59
  The gem is available as open source under the terms of the [Ruby License](https://www.ruby-lang.org/en/about/license.txt).
data/lib/reline/ansi.rb CHANGED
@@ -1,7 +1,13 @@
1
1
  require 'io/console'
2
+ require 'io/wait'
2
3
  require 'timeout'
4
+ require_relative 'terminfo'
3
5
 
4
6
  class Reline::ANSI
7
+ if Reline::Terminfo.enabled?
8
+ Reline::Terminfo.setupterm(0, 2)
9
+ end
10
+
5
11
  def self.encoding
6
12
  Encoding.default_external
7
13
  end
@@ -10,52 +16,100 @@ class Reline::ANSI
10
16
  false
11
17
  end
12
18
 
13
- RAW_KEYSTROKE_CONFIG = {
14
- # Console (80x25)
15
- [27, 91, 49, 126] => :ed_move_to_beg, # Home
16
- [27, 91, 52, 126] => :ed_move_to_end, # End
17
- [27, 91, 51, 126] => :key_delete, # Del
18
- [27, 91, 65] => :ed_prev_history, # ↑
19
- [27, 91, 66] => :ed_next_history, # ↓
20
- [27, 91, 67] => :ed_next_char, # →
21
- [27, 91, 68] => :ed_prev_char, #
22
-
23
- # KDE
24
- [27, 91, 72] => :ed_move_to_beg, # Home
25
- [27, 91, 70] => :ed_move_to_end, # End
26
- # Del is 0x08
27
- [27, 71, 65] => :ed_prev_history, # ↑
28
- [27, 71, 66] => :ed_next_history, # ↓
29
- [27, 71, 67] => :ed_next_char, # →
30
- [27, 71, 68] => :ed_prev_char, # ←
31
-
32
- # urxvt / exoterm
33
- [27, 91, 55, 126] => :ed_move_to_beg, # Home
34
- [27, 91, 56, 126] => :ed_move_to_end, # End
35
-
36
- # GNOME
37
- [27, 79, 72] => :ed_move_to_beg, # Home
38
- [27, 79, 70] => :ed_move_to_end, # End
39
- # Del is 0x08
40
- # Arrow keys are the same of KDE
41
-
42
- # iTerm2
43
- [27, 27, 91, 67] => :em_next_word, # Option+→
44
- [27, 27, 91, 68] => :ed_prev_word, # Option+←
45
- [195, 166] => :em_next_word, # Option+f
46
- [195, 162] => :ed_prev_word, # Option+b
47
-
48
- # others
49
- [27, 32] => :em_set_mark, # M-<space>
50
- [24, 24] => :em_exchange_mark, # C-x C-x TODO also add Windows
51
- [27, 91, 49, 59, 53, 67] => :em_next_word, # Ctrl+→
52
- [27, 91, 49, 59, 53, 68] => :ed_prev_word, # Ctrl+←
53
-
54
- [27, 79, 65] => :ed_prev_history, #
55
- [27, 79, 66] => :ed_next_history, # ↓
56
- [27, 79, 67] => :ed_next_char, # →
57
- [27, 79, 68] => :ed_prev_char, # ←
58
- }
19
+ def self.set_default_key_bindings(config)
20
+ if Reline::Terminfo.enabled?
21
+ set_default_key_bindings_terminfo(config)
22
+ else
23
+ set_default_key_bindings_comprehensive_list(config)
24
+ end
25
+ {
26
+ # extended entries of terminfo
27
+ [27, 91, 49, 59, 53, 67] => :em_next_word, # Ctrl+→, extended entry
28
+ [27, 91, 49, 59, 53, 68] => :ed_prev_word, # Ctrl+←, extended entry
29
+ [27, 91, 49, 59, 51, 67] => :em_next_word, # Meta+→, extended entry
30
+ [27, 91, 49, 59, 51, 68] => :ed_prev_word, # Meta+←, extended entry
31
+ }.each_pair do |key, func|
32
+ config.add_default_key_binding_by_keymap(:emacs, key, func)
33
+ config.add_default_key_binding_by_keymap(:vi_insert, key, func)
34
+ config.add_default_key_binding_by_keymap(:vi_command, key, func)
35
+ end
36
+ {
37
+ # default bindings
38
+ [27, 32] => :em_set_mark, # M-<space>
39
+ [24, 24] => :em_exchange_mark, # C-x C-x
40
+ [27, 91, 90] => :completion_journey_up, # S-Tab
41
+ }.each_pair do |key, func|
42
+ config.add_default_key_binding_by_keymap(:emacs, key, func)
43
+ end
44
+ end
45
+
46
+ def self.set_default_key_bindings_terminfo(config)
47
+ {
48
+ Reline::Terminfo.tigetstr('khome').bytes => :ed_move_to_beg,
49
+ Reline::Terminfo.tigetstr('kend').bytes => :ed_move_to_end,
50
+ Reline::Terminfo.tigetstr('kcuu1').bytes => :ed_prev_history,
51
+ Reline::Terminfo.tigetstr('kcud1').bytes => :ed_next_history,
52
+ Reline::Terminfo.tigetstr('kcuf1').bytes => :ed_next_char,
53
+ Reline::Terminfo.tigetstr('kcub1').bytes => :ed_prev_char,
54
+ # Escape sequences that omit the move distance and are set to defaults
55
+ # value 1 may be sometimes sent by pressing the arrow-key.
56
+ Reline::Terminfo.tigetstr('cuu').sub(/%p1%d/, '').bytes => :ed_prev_history,
57
+ Reline::Terminfo.tigetstr('cud').sub(/%p1%d/, '').bytes => :ed_next_history,
58
+ Reline::Terminfo.tigetstr('cuf').sub(/%p1%d/, '').bytes => :ed_next_char,
59
+ Reline::Terminfo.tigetstr('cub').sub(/%p1%d/, '').bytes => :ed_prev_char,
60
+ }.each_pair do |key, func|
61
+ config.add_default_key_binding_by_keymap(:emacs, key, func)
62
+ config.add_default_key_binding_by_keymap(:vi_insert, key, func)
63
+ config.add_default_key_binding_by_keymap(:vi_command, key, func)
64
+ end
65
+ end
66
+
67
+ def self.set_default_key_bindings_comprehensive_list(config)
68
+ {
69
+ # Console (80x25)
70
+ [27, 91, 49, 126] => :ed_move_to_beg, # Home
71
+ [27, 91, 52, 126] => :ed_move_to_end, # End
72
+ [27, 91, 51, 126] => :key_delete, # Del
73
+ [27, 91, 65] => :ed_prev_history, # ↑
74
+ [27, 91, 66] => :ed_next_history, # ↓
75
+ [27, 91, 67] => :ed_next_char, # →
76
+ [27, 91, 68] => :ed_prev_char, # ←
77
+
78
+ # KDE
79
+ [27, 91, 72] => :ed_move_to_beg, # Home
80
+ [27, 91, 70] => :ed_move_to_end, # End
81
+ # Del is 0x08
82
+ [27, 71, 65] => :ed_prev_history, # ↑
83
+ [27, 71, 66] => :ed_next_history, # ↓
84
+ [27, 71, 67] => :ed_next_char, # →
85
+ [27, 71, 68] => :ed_prev_char, # ←
86
+
87
+ # urxvt / exoterm
88
+ [27, 91, 55, 126] => :ed_move_to_beg, # Home
89
+ [27, 91, 56, 126] => :ed_move_to_end, # End
90
+
91
+ # GNOME
92
+ [27, 79, 72] => :ed_move_to_beg, # Home
93
+ [27, 79, 70] => :ed_move_to_end, # End
94
+ # Del is 0x08
95
+ # Arrow keys are the same of KDE
96
+
97
+ # iTerm2
98
+ [27, 27, 91, 67] => :em_next_word, # Option+→, extended entry
99
+ [27, 27, 91, 68] => :ed_prev_word, # Option+←, extended entry
100
+ [195, 166] => :em_next_word, # Option+f
101
+ [195, 162] => :ed_prev_word, # Option+b
102
+
103
+ [27, 79, 65] => :ed_prev_history, # ↑
104
+ [27, 79, 66] => :ed_next_history, # ↓
105
+ [27, 79, 67] => :ed_next_char, # →
106
+ [27, 79, 68] => :ed_prev_char, # ←
107
+ }.each_pair do |key, func|
108
+ config.add_default_key_binding_by_keymap(:emacs, key, func)
109
+ config.add_default_key_binding_by_keymap(:vi_insert, key, func)
110
+ config.add_default_key_binding_by_keymap(:vi_command, key, func)
111
+ end
112
+ end
59
113
 
60
114
  @@input = STDIN
61
115
  def self.input=(val)
@@ -79,6 +133,8 @@ class Reline::ANSI
79
133
  rescue Errno::EIO
80
134
  # Maybe the I/O has been closed.
81
135
  nil
136
+ rescue Errno::ENOTTY
137
+ nil
82
138
  end
83
139
 
84
140
  @@in_bracketed_paste_mode = false
@@ -129,12 +185,7 @@ class Reline::ANSI
129
185
  unless @@buf.empty?
130
186
  return false
131
187
  end
132
- rs, = IO.select([@@input], [], [], 0.00001)
133
- if rs and rs[0]
134
- false
135
- else
136
- true
137
- end
188
+ !@@input.wait_readable(0)
138
189
  end
139
190
 
140
191
  def self.ungetc(c)
@@ -143,8 +194,7 @@ class Reline::ANSI
143
194
 
144
195
  def self.retrieve_keybuffer
145
196
  begin
146
- result = select([@@input], [], [], 0.001)
147
- return if result.nil?
197
+ return unless @@input.wait_readable(0.001)
148
198
  str = @@input.read_nonblock(1024)
149
199
  str.bytes.each do |c|
150
200
  @@buf.push(c)
@@ -225,6 +275,22 @@ class Reline::ANSI
225
275
  end
226
276
  end
227
277
 
278
+ def self.hide_cursor
279
+ if Reline::Terminfo.enabled?
280
+ @@output.write Reline::Terminfo.tigetstr('civis')
281
+ else
282
+ # ignored
283
+ end
284
+ end
285
+
286
+ def self.show_cursor
287
+ if Reline::Terminfo.enabled?
288
+ @@output.write Reline::Terminfo.tigetstr('cnorm')
289
+ else
290
+ # ignored
291
+ end
292
+ end
293
+
228
294
  def self.erase_after_cursor
229
295
  @@output.write "\e[K"
230
296
  end
@@ -246,8 +312,6 @@ class Reline::ANSI
246
312
 
247
313
  def self.prep
248
314
  retrieve_keybuffer
249
- int_handle = Signal.trap('INT', 'IGNORE')
250
- Signal.trap('INT', int_handle)
251
315
  nil
252
316
  end
253
317
 
data/lib/reline/config.rb CHANGED
@@ -47,7 +47,9 @@ class Reline::Config
47
47
 
48
48
  def initialize
49
49
  @additional_key_bindings = {} # from inputrc
50
- @default_key_bindings = {} # environment-dependent
50
+ @additional_key_bindings[:emacs] = {}
51
+ @additional_key_bindings[:vi_insert] = {}
52
+ @additional_key_bindings[:vi_command] = {}
51
53
  @skip_section = nil
52
54
  @if_stack = nil
53
55
  @editing_mode_label = :emacs
@@ -63,14 +65,17 @@ class Reline::Config
63
65
  @history_size = -1 # unlimited
64
66
  @keyseq_timeout = 500
65
67
  @test_mode = false
68
+ @autocompletion = false
66
69
  end
67
70
 
68
71
  def reset
69
72
  if editing_mode_is?(:vi_command)
70
73
  @editing_mode_label = :vi_insert
71
74
  end
72
- @additional_key_bindings = {}
73
- @default_key_bindings = {}
75
+ @additional_key_bindings.keys.each do |key|
76
+ @additional_key_bindings[key].clear
77
+ end
78
+ reset_default_key_bindings
74
79
  end
75
80
 
76
81
  def editing_mode
@@ -85,6 +90,14 @@ class Reline::Config
85
90
  (val.respond_to?(:any?) ? val : [val]).any?(@editing_mode_label)
86
91
  end
87
92
 
93
+ def autocompletion=(val)
94
+ @autocompletion = val
95
+ end
96
+
97
+ def autocompletion
98
+ @autocompletion
99
+ end
100
+
88
101
  def keymap
89
102
  @key_actors[@keymap_label]
90
103
  end
@@ -135,19 +148,35 @@ class Reline::Config
135
148
  end
136
149
 
137
150
  def key_bindings
138
- # override @default_key_bindings with @additional_key_bindings
139
- @default_key_bindings.merge(@additional_key_bindings)
151
+ # override @key_actors[@editing_mode_label].default_key_bindings with @additional_key_bindings[@editing_mode_label]
152
+ @key_actors[@editing_mode_label].default_key_bindings.merge(@additional_key_bindings[@editing_mode_label])
153
+ end
154
+
155
+ def add_default_key_binding_by_keymap(keymap, keystroke, target)
156
+ @key_actors[keymap].default_key_bindings[keystroke] = target
140
157
  end
141
158
 
142
159
  def add_default_key_binding(keystroke, target)
143
- @default_key_bindings[keystroke] = target
160
+ @key_actors[@keymap_label].default_key_bindings[keystroke] = target
144
161
  end
145
162
 
146
163
  def reset_default_key_bindings
147
- @default_key_bindings = {}
164
+ @key_actors.values.each do |ka|
165
+ ka.reset_default_key_bindings
166
+ end
148
167
  end
149
168
 
150
169
  def read_lines(lines, file = nil)
170
+ if not lines.empty? and lines.first.encoding != Reline.encoding_system_needs
171
+ begin
172
+ lines = lines.map do |l|
173
+ l.encode(Reline.encoding_system_needs)
174
+ rescue Encoding::UndefinedConversionError
175
+ mes = "The inputrc encoded in #{lines.first.encoding.name} can't be converted to the locale #{Reline.encoding_system_needs.name}."
176
+ raise Reline::ConfigEncodingConversionError.new(mes)
177
+ end
178
+ end
179
+ end
151
180
  conditions = [@skip_section, @if_stack]
152
181
  @skip_section = nil
153
182
  @if_stack = []
@@ -174,7 +203,7 @@ class Reline::Config
174
203
  key, func_name = $1, $2
175
204
  keystroke, func = bind_key(key, func_name)
176
205
  next unless keystroke
177
- @additional_key_bindings[keystroke] = func
206
+ @additional_key_bindings[@keymap_label][keystroke] = func
178
207
  end
179
208
  end
180
209
  unless @if_stack.empty?
@@ -282,11 +311,8 @@ class Reline::Config
282
311
  end
283
312
 
284
313
  def retrieve_string(str)
285
- if str =~ /\A"(.*)"\z/
286
- parse_keyseq($1).map(&:chr).join
287
- else
288
- parse_keyseq(str).map(&:chr).join
289
- end
314
+ str = $1 if str =~ /\A"(.*)"\z/
315
+ parse_keyseq(str).map { |c| c.chr(Reline.encoding_system_needs) }.join
290
316
  end
291
317
 
292
318
  def bind_key(key, func_name)
@@ -1,19 +1,27 @@
1
1
  require 'timeout'
2
2
 
3
3
  class Reline::GeneralIO
4
- def self.reset
4
+ def self.reset(encoding: nil)
5
5
  @@pasting = false
6
+ @@encoding = encoding
6
7
  end
7
8
 
8
9
  def self.encoding
9
- RUBY_PLATFORM =~ /mswin|mingw/ ? Encoding::UTF_8 : Encoding::default_external
10
+ if defined?(@@encoding)
11
+ @@encoding
12
+ elsif RUBY_PLATFORM =~ /mswin|mingw/
13
+ Encoding::UTF_8
14
+ else
15
+ Encoding::default_external
16
+ end
10
17
  end
11
18
 
12
19
  def self.win?
13
20
  false
14
21
  end
15
22
 
16
- RAW_KEYSTROKE_CONFIG = {}
23
+ def self.set_default_key_bindings(_)
24
+ end
17
25
 
18
26
  @@buf = []
19
27
 
@@ -4,4 +4,16 @@ class Reline::KeyActor::Base
4
4
  def get_method(key)
5
5
  self.class::MAPPING[key]
6
6
  end
7
+
8
+ def initialize
9
+ @default_key_bindings = {}
10
+ end
11
+
12
+ def default_key_bindings
13
+ @default_key_bindings
14
+ end
15
+
16
+ def reset_default_key_bindings
17
+ @default_key_bindings.clear
18
+ end
7
19
  end
@@ -150,7 +150,8 @@ class Reline::LineEditor
150
150
  @screen_size = Reline::IOGate.get_screen_size
151
151
  @screen_height = @screen_size.first
152
152
  reset_variables(prompt, encoding: encoding)
153
- @old_trap = Signal.trap('SIGINT') {
153
+ @old_trap = Signal.trap(:INT) {
154
+ clear_dialog
154
155
  if @scroll_partial_screen
155
156
  move_cursor_down(@screen_height - (@line_index - @scroll_partial_screen) - 1)
156
157
  else
@@ -158,8 +159,16 @@ class Reline::LineEditor
158
159
  end
159
160
  Reline::IOGate.move_cursor_column(0)
160
161
  scroll_down(1)
161
- @old_trap.call if @old_trap.respond_to?(:call) # can also be string, ex: "DEFAULT"
162
- raise Interrupt
162
+ case @old_trap
163
+ when 'DEFAULT', 'SYSTEM_DEFAULT'
164
+ raise Interrupt
165
+ when 'IGNORE'
166
+ # Do nothing
167
+ when 'EXIT'
168
+ exit
169
+ else
170
+ @old_trap.call
171
+ end
163
172
  }
164
173
  Reline::IOGate.set_winch_handler do
165
174
  @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
@@ -241,6 +250,7 @@ class Reline::LineEditor
241
250
  @drop_terminate_spaces = false
242
251
  @in_pasting = false
243
252
  @auto_indent_proc = nil
253
+ @dialogs = []
244
254
  reset_line
245
255
  end
246
256
 
@@ -406,10 +416,10 @@ class Reline::LineEditor
406
416
  Reline::IOGate.erase_after_cursor
407
417
  end
408
418
  @output.flush
419
+ clear_dialog
409
420
  return
410
421
  end
411
422
  new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
412
- # FIXME: end of logical line sometimes breaks
413
423
  rendered = false
414
424
  if @add_newline_to_end_of_buffer
415
425
  rerender_added_newline(prompt, prompt_width)
@@ -417,6 +427,7 @@ class Reline::LineEditor
417
427
  else
418
428
  if @just_cursor_moving and not @rerender_all
419
429
  rendered = just_move_cursor
430
+ render_dialog((prompt_width + @cursor) % @screen_size.last)
420
431
  @just_cursor_moving = false
421
432
  return
422
433
  elsif @previous_line_index or new_highest_in_this != @highest_in_this
@@ -439,18 +450,20 @@ class Reline::LineEditor
439
450
  new_lines = whole_lines
440
451
  end
441
452
  line = modify_lines(new_lines)[@line_index]
453
+ clear_dialog
442
454
  prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
443
455
  render_partial(prompt, prompt_width, line, @first_line_started_from)
444
456
  move_cursor_down(@highest_in_all - (@first_line_started_from + @highest_in_this - 1) - 1)
445
457
  scroll_down(1)
446
458
  Reline::IOGate.move_cursor_column(0)
447
459
  Reline::IOGate.erase_after_cursor
448
- elsif not rendered
449
- unless @in_pasting
460
+ else
461
+ if not rendered and not @in_pasting
450
462
  line = modify_lines(whole_lines)[@line_index]
451
463
  prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
452
464
  render_partial(prompt, prompt_width, line, @first_line_started_from)
453
465
  end
466
+ render_dialog((prompt_width + @cursor) % @screen_size.last)
454
467
  end
455
468
  @buffer_of_lines[@line_index] = @line
456
469
  @rest_height = 0 if @scroll_partial_screen
@@ -465,6 +478,294 @@ class Reline::LineEditor
465
478
  end
466
479
  end
467
480
 
481
+ class DialogProcScope
482
+ def initialize(line_editor, config, proc_to_exec, context)
483
+ @line_editor = line_editor
484
+ @config = config
485
+ @proc_to_exec = proc_to_exec
486
+ @context = context
487
+ @cursor_pos = Reline::CursorPos.new
488
+ end
489
+
490
+ def context
491
+ @context
492
+ end
493
+
494
+ def retrieve_completion_block(set_completion_quote_character = false)
495
+ @line_editor.retrieve_completion_block(set_completion_quote_character)
496
+ end
497
+
498
+ def call_completion_proc_with_checking_args(pre, target, post)
499
+ @line_editor.call_completion_proc_with_checking_args(pre, target, post)
500
+ end
501
+
502
+ def set_cursor_pos(col, row)
503
+ @cursor_pos.x = col
504
+ @cursor_pos.y = row
505
+ end
506
+
507
+ def cursor_pos
508
+ @cursor_pos
509
+ end
510
+
511
+ def just_cursor_moving
512
+ @line_editor.instance_variable_get(:@just_cursor_moving)
513
+ end
514
+
515
+ def screen_width
516
+ @line_editor.instance_variable_get(:@screen_size).last
517
+ end
518
+
519
+ def completion_journey_data
520
+ @line_editor.instance_variable_get(:@completion_journey_data)
521
+ end
522
+
523
+ def config
524
+ @config
525
+ end
526
+
527
+ def call
528
+ instance_exec(&@proc_to_exec)
529
+ end
530
+ end
531
+
532
+ class Dialog
533
+ attr_reader :name
534
+ attr_accessor :column, :vertical_offset, :contents, :lines_backup
535
+
536
+ def initialize(name, proc_scope)
537
+ @name = name
538
+ @proc_scope = proc_scope
539
+ end
540
+
541
+ def set_cursor_pos(col, row)
542
+ @proc_scope.set_cursor_pos(col, row)
543
+ end
544
+
545
+ def call
546
+ @proc_scope.call
547
+ end
548
+ end
549
+
550
+ def add_dialog_proc(name, p, context = nil)
551
+ return if @dialogs.any? { |d| d.name == name }
552
+ @dialogs << Dialog.new(name, DialogProcScope.new(self, @config, p, context))
553
+ end
554
+
555
+ DIALOG_HEIGHT = 20
556
+ DIALOG_WIDTH = 40
557
+ private def render_dialog(cursor_column)
558
+ @dialogs.each do |dialog|
559
+ render_each_dialog(dialog, cursor_column)
560
+ end
561
+ end
562
+
563
+ private def render_each_dialog(dialog, cursor_column)
564
+ if @in_pasting
565
+ dialog.contents = nil
566
+ return
567
+ end
568
+ dialog.set_cursor_pos(cursor_column, @first_line_started_from + @started_from)
569
+ pos, result, pointer, bg = dialog.call
570
+ old_dialog_contents = dialog.contents
571
+ old_dialog_column = dialog.column
572
+ old_dialog_vertical_offset = dialog.vertical_offset
573
+ if result and not result.empty?
574
+ dialog.contents = result
575
+ dialog.contents = dialog.contents[0...DIALOG_HEIGHT] if dialog.contents.size > DIALOG_HEIGHT
576
+ else
577
+ dialog.lines_backup = {
578
+ lines: modify_lines(whole_lines),
579
+ line_index: @line_index,
580
+ first_line_started_from: @first_line_started_from,
581
+ started_from: @started_from,
582
+ byte_pointer: @byte_pointer
583
+ }
584
+ clear_each_dialog(dialog)
585
+ dialog.contents = nil
586
+ return
587
+ end
588
+ upper_space = @first_line_started_from - @started_from
589
+ lower_space = @highest_in_all - @first_line_started_from - @started_from - 1
590
+ dialog.column = pos.x
591
+ diff = (dialog.column + DIALOG_WIDTH) - (@screen_size.last - 1)
592
+ if diff > 0
593
+ dialog.column -= diff
594
+ end
595
+ if (lower_space + @rest_height) >= DIALOG_HEIGHT
596
+ dialog.vertical_offset = pos.y + 1
597
+ elsif upper_space >= DIALOG_HEIGHT
598
+ dialog.vertical_offset = pos.y + -(DIALOG_HEIGHT + 1)
599
+ else
600
+ if (lower_space + @rest_height) < DIALOG_HEIGHT
601
+ scroll_down(DIALOG_HEIGHT)
602
+ move_cursor_up(DIALOG_HEIGHT)
603
+ end
604
+ dialog.vertical_offset = pos.y + 1
605
+ end
606
+ Reline::IOGate.hide_cursor
607
+ reset_dialog(dialog, old_dialog_contents, old_dialog_column, old_dialog_vertical_offset)
608
+ move_cursor_down(dialog.vertical_offset)
609
+ Reline::IOGate.move_cursor_column(dialog.column)
610
+ dialog.contents.each_with_index do |item, i|
611
+ if i == pointer
612
+ bg_color = '45'
613
+ else
614
+ if bg
615
+ bg_color = bg
616
+ else
617
+ bg_color = '46'
618
+ end
619
+ end
620
+ @output.write "\e[#{bg_color}m%-#{DIALOG_WIDTH}s\e[49m" % item.slice(0, DIALOG_WIDTH)
621
+ Reline::IOGate.move_cursor_column(dialog.column)
622
+ move_cursor_down(1) if i < (dialog.contents.size - 1)
623
+ end
624
+ Reline::IOGate.move_cursor_column(cursor_column)
625
+ move_cursor_up(dialog.vertical_offset + dialog.contents.size - 1)
626
+ Reline::IOGate.show_cursor
627
+ dialog.lines_backup = {
628
+ lines: modify_lines(whole_lines),
629
+ line_index: @line_index,
630
+ first_line_started_from: @first_line_started_from,
631
+ started_from: @started_from,
632
+ byte_pointer: @byte_pointer
633
+ }
634
+ end
635
+
636
+ private def reset_dialog(dialog, old_dialog_contents, old_dialog_column, old_dialog_vertical_offset)
637
+ return if dialog.lines_backup.nil? or old_dialog_contents.nil?
638
+ prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines], prompt)
639
+ visual_lines = []
640
+ visual_start = nil
641
+ dialog.lines_backup[:lines].each_with_index { |l, i|
642
+ pr = prompt_list ? prompt_list[i] : prompt
643
+ vl, _ = split_by_width(pr + l, @screen_size.last)
644
+ vl.compact!
645
+ if i == dialog.lines_backup[:line_index]
646
+ visual_start = visual_lines.size + dialog.lines_backup[:started_from]
647
+ end
648
+ visual_lines.concat(vl)
649
+ }
650
+ old_y = dialog.lines_backup[:first_line_started_from] + dialog.lines_backup[:started_from]
651
+ y = @first_line_started_from + @started_from
652
+ y_diff = y - old_y
653
+ if (old_y + old_dialog_vertical_offset) < (y + dialog.vertical_offset)
654
+ # rerender top
655
+ move_cursor_down(old_dialog_vertical_offset - y_diff)
656
+ start = visual_start + old_dialog_vertical_offset
657
+ line_num = dialog.vertical_offset - old_dialog_vertical_offset
658
+ line_num.times do |i|
659
+ Reline::IOGate.move_cursor_column(old_dialog_column)
660
+ if visual_lines[start + i].nil?
661
+ s = ' ' * DIALOG_WIDTH
662
+ else
663
+ s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog_column, DIALOG_WIDTH)
664
+ end
665
+ @output.write "\e[39m\e[49m%-#{DIALOG_WIDTH}s\e[39m\e[49m" % s
666
+ move_cursor_down(1) if i < (line_num - 1)
667
+ end
668
+ move_cursor_up(old_dialog_vertical_offset + line_num - 1 - y_diff)
669
+ end
670
+ if (old_y + old_dialog_vertical_offset + old_dialog_contents.size) > (y + dialog.vertical_offset + dialog.contents.size)
671
+ # rerender bottom
672
+ move_cursor_down(dialog.vertical_offset + dialog.contents.size - y_diff)
673
+ start = visual_start + dialog.vertical_offset + dialog.contents.size
674
+ line_num = (old_dialog_vertical_offset + old_dialog_contents.size) - (dialog.vertical_offset + dialog.contents.size)
675
+ line_num.times do |i|
676
+ Reline::IOGate.move_cursor_column(old_dialog_column)
677
+ if visual_lines[start + i].nil?
678
+ s = ' ' * DIALOG_WIDTH
679
+ else
680
+ s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog_column, DIALOG_WIDTH)
681
+ end
682
+ @output.write "\e[39m\e[49m%-#{DIALOG_WIDTH}s\e[39m\e[49m" % s
683
+ move_cursor_down(1) if i < (line_num - 1)
684
+ end
685
+ move_cursor_up(dialog.vertical_offset + dialog.contents.size + line_num - 1 - y_diff)
686
+ end
687
+ if old_dialog_column < dialog.column
688
+ # rerender left
689
+ move_cursor_down(old_dialog_vertical_offset - y_diff)
690
+ width = dialog.column - old_dialog_column
691
+ start = visual_start + old_dialog_vertical_offset
692
+ line_num = old_dialog_contents.size
693
+ line_num.times do |i|
694
+ Reline::IOGate.move_cursor_column(old_dialog_column)
695
+ if visual_lines[start + i].nil?
696
+ s = ' ' * width
697
+ else
698
+ s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog_column, width)
699
+ end
700
+ @output.write "\e[39m\e[49m%-#{width}s\e[39m\e[49m" % s
701
+ move_cursor_down(1) if i < (line_num - 1)
702
+ end
703
+ move_cursor_up(old_dialog_vertical_offset + line_num - 1 - y_diff)
704
+ end
705
+ if (old_dialog_column + DIALOG_WIDTH) > (dialog.column + DIALOG_WIDTH)
706
+ # rerender right
707
+ move_cursor_down(old_dialog_vertical_offset + y_diff)
708
+ width = (old_dialog_column + DIALOG_WIDTH) - (dialog.column + DIALOG_WIDTH)
709
+ start = visual_start + old_dialog_vertical_offset
710
+ line_num = old_dialog_contents.size
711
+ line_num.times do |i|
712
+ Reline::IOGate.move_cursor_column(old_dialog_column + DIALOG_WIDTH)
713
+ if visual_lines[start + i].nil?
714
+ s = ' ' * width
715
+ else
716
+ s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog_column + DIALOG_WIDTH, width)
717
+ end
718
+ Reline::IOGate.move_cursor_column(dialog.column + DIALOG_WIDTH)
719
+ @output.write "\e[39m\e[49m%-#{width}s\e[39m\e[49m" % s
720
+ move_cursor_down(1) if i < (line_num - 1)
721
+ end
722
+ move_cursor_up(old_dialog_vertical_offset + line_num - 1 + y_diff)
723
+ end
724
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
725
+ end
726
+
727
+ private def clear_dialog
728
+ @dialogs.each do |dialog|
729
+ clear_each_dialog(dialog)
730
+ end
731
+ end
732
+
733
+ private def clear_each_dialog(dialog)
734
+ return unless dialog.contents
735
+ prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines], prompt)
736
+ visual_lines = []
737
+ visual_lines_under_dialog = []
738
+ visual_start = nil
739
+ dialog.lines_backup[:lines].each_with_index { |l, i|
740
+ pr = prompt_list ? prompt_list[i] : prompt
741
+ vl, _ = split_by_width(pr + l, @screen_size.last)
742
+ vl.compact!
743
+ if i == dialog.lines_backup[:line_index]
744
+ visual_start = visual_lines.size + dialog.lines_backup[:started_from] + dialog.vertical_offset
745
+ end
746
+ visual_lines.concat(vl)
747
+ }
748
+ visual_lines_under_dialog = visual_lines[visual_start, dialog.contents.size]
749
+ visual_lines_under_dialog = [] if visual_lines_under_dialog.nil?
750
+ Reline::IOGate.hide_cursor
751
+ move_cursor_down(dialog.vertical_offset)
752
+ dialog_vertical_size = dialog.contents.size
753
+ dialog_vertical_size.times do |i|
754
+ if i < visual_lines_under_dialog.size
755
+ Reline::IOGate.move_cursor_column(0)
756
+ @output.write "\e[39m\e[49m%-#{DIALOG_WIDTH}s\e[39m\e[49m" % visual_lines_under_dialog[i]
757
+ else
758
+ Reline::IOGate.move_cursor_column(dialog.column)
759
+ @output.write "\e[39m\e[49m#{' ' * DIALOG_WIDTH}\e[39m\e[49m"
760
+ end
761
+ Reline::IOGate.erase_after_cursor
762
+ move_cursor_down(1) if i < (dialog_vertical_size - 1)
763
+ end
764
+ move_cursor_up(dialog_vertical_size - 1 + dialog.vertical_offset)
765
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
766
+ Reline::IOGate.show_cursor
767
+ end
768
+
468
769
  private def calculate_scroll_partial_screen(highest_in_all, cursor_y)
469
770
  if @screen_height < highest_in_all
470
771
  old_scroll_partial_screen = @scroll_partial_screen
@@ -678,7 +979,6 @@ class Reline::LineEditor
678
979
  private def render_partial(prompt, prompt_width, line_to_render, this_started_from, with_control: true)
679
980
  visual_lines, height = split_by_width(line_to_render.nil? ? prompt : prompt + line_to_render, @screen_size.last)
680
981
  cursor_up_from_last_line = 0
681
- # TODO: This logic would be sometimes buggy if this logical line isn't the current @line_index.
682
982
  if @scroll_partial_screen
683
983
  last_visual_line = this_started_from + (height - 1)
684
984
  last_screen_line = @scroll_partial_screen + (@screen_height - 1)
@@ -926,6 +1226,16 @@ class Reline::LineEditor
926
1226
  @completion_journey_data = CompletionJourneyData.new(
927
1227
  preposing, postposing,
928
1228
  [target] + list.select{ |item| item.start_with?(target) }, 0)
1229
+ if @completion_journey_data.list.size == 1
1230
+ @completion_journey_data.pointer = 0
1231
+ else
1232
+ case direction
1233
+ when :up
1234
+ @completion_journey_data.pointer = @completion_journey_data.list.size - 1
1235
+ when :down
1236
+ @completion_journey_data.pointer = 1
1237
+ end
1238
+ end
929
1239
  @completion_state = CompletionState::JOURNEY
930
1240
  else
931
1241
  case direction
@@ -940,13 +1250,13 @@ class Reline::LineEditor
940
1250
  @completion_journey_data.pointer = 0
941
1251
  end
942
1252
  end
943
- completed = @completion_journey_data.list[@completion_journey_data.pointer]
944
- @line = @completion_journey_data.preposing + completed + @completion_journey_data.postposing
945
- line_to_pointer = @completion_journey_data.preposing + completed
946
- @cursor_max = calculate_width(@line)
947
- @cursor = calculate_width(line_to_pointer)
948
- @byte_pointer = line_to_pointer.bytesize
949
1253
  end
1254
+ completed = @completion_journey_data.list[@completion_journey_data.pointer]
1255
+ @line = @completion_journey_data.preposing + completed + @completion_journey_data.postposing
1256
+ line_to_pointer = @completion_journey_data.preposing + completed
1257
+ @cursor_max = calculate_width(@line)
1258
+ @cursor = calculate_width(line_to_pointer)
1259
+ @byte_pointer = line_to_pointer.bytesize
950
1260
  end
951
1261
 
952
1262
  private def run_for_operators(key, method_symbol, &block)
@@ -1121,7 +1431,20 @@ class Reline::LineEditor
1121
1431
  if result.is_a?(Array)
1122
1432
  completion_occurs = true
1123
1433
  process_insert
1124
- complete(result)
1434
+ if @config.autocompletion
1435
+ move_completed_list(result, :down)
1436
+ else
1437
+ complete(result)
1438
+ end
1439
+ end
1440
+ end
1441
+ elsif @config.editing_mode_is?(:emacs, :vi_insert) and key.char == :completion_journey_up
1442
+ if not @config.disable_completion and @config.autocompletion
1443
+ result = call_completion_proc
1444
+ if result.is_a?(Array)
1445
+ completion_occurs = true
1446
+ process_insert
1447
+ move_completed_list(result, :up)
1125
1448
  end
1126
1449
  end
1127
1450
  elsif not @config.disable_completion and @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key.char)
@@ -1140,6 +1463,7 @@ class Reline::LineEditor
1140
1463
  end
1141
1464
  unless completion_occurs
1142
1465
  @completion_state = CompletionState::NORMAL
1466
+ @completion_journey_data = nil
1143
1467
  end
1144
1468
  if not @in_pasting and @just_cursor_moving.nil?
1145
1469
  if @previous_line_index and @buffer_of_lines[@previous_line_index] == @line
@@ -1159,7 +1483,13 @@ class Reline::LineEditor
1159
1483
 
1160
1484
  def call_completion_proc
1161
1485
  result = retrieve_completion_block(true)
1162
- preposing, target, postposing = result
1486
+ pre, target, post = result
1487
+ result = call_completion_proc_with_checking_args(pre, target, post)
1488
+ Reline.core.instance_variable_set(:@completion_quote_character, nil)
1489
+ result
1490
+ end
1491
+
1492
+ def call_completion_proc_with_checking_args(pre, target, post)
1163
1493
  if @completion_proc and target
1164
1494
  argnum = @completion_proc.parameters.inject(0) { |result, item|
1165
1495
  case item.first
@@ -1173,12 +1503,11 @@ class Reline::LineEditor
1173
1503
  when 1
1174
1504
  result = @completion_proc.(target)
1175
1505
  when 2
1176
- result = @completion_proc.(target, preposing)
1506
+ result = @completion_proc.(target, pre)
1177
1507
  when 3..Float::INFINITY
1178
- result = @completion_proc.(target, preposing, postposing)
1508
+ result = @completion_proc.(target, pre, post)
1179
1509
  end
1180
1510
  end
1181
- Reline.core.instance_variable_set(:@completion_quote_character, nil)
1182
1511
  result
1183
1512
  end
1184
1513