reline 0.2.5 → 0.2.8.pre.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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