reline 0.2.1 → 0.2.6

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: 9ad1ae315ec2e7717822c6dd2cde886639d5becb7ccf552a64921e12b8452107
4
- data.tar.gz: 191aa399008fb54a8ee3048f5ef96b260d1937fba8203977e15321f45a3fd962
3
+ metadata.gz: fc0f6fb7c331a37907554b292249a75b218646aa14298586790cc09d6c174894
4
+ data.tar.gz: 79fd7b502a7ab601f6642964bb0987dae5d0a62bc8cfda016d8806c8b76bb723
5
5
  SHA512:
6
- metadata.gz: ea45bb1e825b2c17a9d0965a5bfc33e0a42c9c6eb8a29ae3f0500b6de197dc124ba6a99c8e41b6692fa04d3086f7ba1dbb359d9ff0c4a53520246a598719d2f2
7
- data.tar.gz: 6e8780a82c7e59aa9c1f0d6283b7c5f09ea5bb66dda6209aa85cef3bec3ccf1079d4a3cc113b33403e6ee274403f1f0c021f892c871123a255985965b017d798
6
+ metadata.gz: 2b4300db4d7bef4ab3ddc8f45db07e20e37b371d89e7a21f343dd3c7096209d0c19028def435d584c7bc1f0a9f18d0d6c783b47d28967eb3d34e4d0eaa613490
7
+ data.tar.gz: '081a683b1980b9c8d27822e62dcdc776024cc408e3f93b0c999f8da2c6b2fef92c4eded142f7bee8f95fcd7a18fc8431a87f2cd19d1c1064fa677f7de606dcf0'
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.rb CHANGED
@@ -231,9 +231,7 @@ module Reline
231
231
  unless config.test_mode
232
232
  config.read
233
233
  config.reset_default_key_bindings
234
- Reline::IOGate::RAW_KEYSTROKE_CONFIG.each_pair do |key, func|
235
- config.add_default_key_binding(key, func)
236
- end
234
+ Reline::IOGate.set_default_key_bindings(config)
237
235
  end
238
236
 
239
237
  line_editor.rerender
@@ -243,6 +241,7 @@ module Reline
243
241
  loop do
244
242
  prev_pasting_state = Reline::IOGate.in_pasting?
245
243
  read_io(config.keyseq_timeout) { |inputs|
244
+ line_editor.set_pasting_state(Reline::IOGate.in_pasting?)
246
245
  inputs.each { |c|
247
246
  line_editor.input_key(c)
248
247
  line_editor.rerender
@@ -253,6 +252,7 @@ module Reline
253
252
  end
254
253
  }
255
254
  if prev_pasting_state == true and not Reline::IOGate.in_pasting? and not line_editor.finished?
255
+ line_editor.set_pasting_state(false)
256
256
  prev_pasting_state = false
257
257
  line_editor.rerender_all
258
258
  end
@@ -271,11 +271,12 @@ module Reline
271
271
  Reline::IOGate.deprep(otio)
272
272
  end
273
273
 
274
- # Keystrokes of GNU Readline will timeout it with the specification of
275
- # "keyseq-timeout" when waiting for the 2nd character after the 1st one.
276
- # If the 2nd character comes after 1st ESC without timeout it has a
277
- # meta-property of meta-key to discriminate modified key with meta-key
278
- # from multibyte characters that come with 8th bit on.
274
+ # GNU Readline waits for "keyseq-timeout" milliseconds to see if the ESC
275
+ # is followed by a character, and times out and treats it as a standalone
276
+ # ESC if the second character does not arrive. If the second character
277
+ # comes before timed out, it is treated as a modifier key with the
278
+ # meta-property of meta-key, so that it can be distinguished from
279
+ # multibyte characters with the 8th bit turned on.
279
280
  #
280
281
  # GNU Readline will wait for the 2nd character with "keyseq-timeout"
281
282
  # milli-seconds but wait forever after 3rd characters.
@@ -444,22 +445,34 @@ module Reline
444
445
  }
445
446
  end
446
447
 
448
+ def self.ungetc(c)
449
+ Reline::IOGate.ungetc(c)
450
+ end
451
+
447
452
  def self.line_editor
448
453
  core.line_editor
449
454
  end
450
455
  end
451
456
 
457
+ require 'reline/general_io'
452
458
  if RbConfig::CONFIG['host_os'] =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/
453
459
  require 'reline/windows'
454
460
  if Reline::Windows.msys_tty?
455
- require 'reline/ansi'
456
- Reline::IOGate = Reline::ANSI
461
+ Reline::IOGate = if ENV['TERM'] == 'dumb'
462
+ Reline::GeneralIO
463
+ else
464
+ require 'reline/ansi'
465
+ Reline::ANSI
466
+ end
457
467
  else
458
468
  Reline::IOGate = Reline::Windows
459
469
  end
460
470
  else
461
- require 'reline/ansi'
462
- Reline::IOGate = Reline::ANSI
471
+ Reline::IOGate = if $stdout.isatty
472
+ require 'reline/ansi'
473
+ Reline::ANSI
474
+ else
475
+ Reline::GeneralIO
476
+ end
463
477
  end
464
478
  Reline::HISTORY = Reline::History.new(Reline.core.config)
465
- require 'reline/general_io'
data/lib/reline/ansi.rb CHANGED
@@ -1,7 +1,12 @@
1
1
  require 'io/console'
2
2
  require 'timeout'
3
+ require_relative 'terminfo'
3
4
 
4
5
  class Reline::ANSI
6
+ if Reline::Terminfo.enabled?
7
+ Reline::Terminfo.setupterm(0, 2)
8
+ end
9
+
5
10
  def self.encoding
6
11
  Encoding.default_external
7
12
  end
@@ -10,52 +15,99 @@ class Reline::ANSI
10
15
  false
11
16
  end
12
17
 
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
- }
18
+ def self.set_default_key_bindings(config)
19
+ if Reline::Terminfo.enabled?
20
+ set_default_key_bindings_terminfo(config)
21
+ else
22
+ set_default_key_bindings_comprehensive_list(config)
23
+ end
24
+ {
25
+ # extended entries of terminfo
26
+ [27, 91, 49, 59, 53, 67] => :em_next_word, # Ctrl+→, extended entry
27
+ [27, 91, 49, 59, 53, 68] => :ed_prev_word, # Ctrl+←, extended entry
28
+ [27, 91, 49, 59, 51, 67] => :em_next_word, # Meta+→, extended entry
29
+ [27, 91, 49, 59, 51, 68] => :ed_prev_word, # Meta+←, extended entry
30
+ }.each_pair do |key, func|
31
+ config.add_default_key_binding_by_keymap(:emacs, key, func)
32
+ config.add_default_key_binding_by_keymap(:vi_insert, key, func)
33
+ config.add_default_key_binding_by_keymap(:vi_command, key, func)
34
+ end
35
+ {
36
+ # default bindings
37
+ [27, 32] => :em_set_mark, # M-<space>
38
+ [24, 24] => :em_exchange_mark, # C-x C-x
39
+ }.each_pair do |key, func|
40
+ config.add_default_key_binding_by_keymap(:emacs, key, func)
41
+ end
42
+ end
43
+
44
+ def self.set_default_key_bindings_terminfo(config)
45
+ {
46
+ Reline::Terminfo.tigetstr('khome').bytes => :ed_move_to_beg,
47
+ Reline::Terminfo.tigetstr('kend').bytes => :ed_move_to_end,
48
+ Reline::Terminfo.tigetstr('kcuu1').bytes => :ed_prev_history,
49
+ Reline::Terminfo.tigetstr('kcud1').bytes => :ed_next_history,
50
+ Reline::Terminfo.tigetstr('kcuf1').bytes => :ed_next_char,
51
+ Reline::Terminfo.tigetstr('kcub1').bytes => :ed_prev_char,
52
+ # Escape sequences that omit the move distance and are set to defaults
53
+ # value 1 may be sometimes sent by pressing the arrow-key.
54
+ Reline::Terminfo.tigetstr('cuu').sub(/%p1%d/, '').bytes => :ed_prev_history,
55
+ Reline::Terminfo.tigetstr('cud').sub(/%p1%d/, '').bytes => :ed_next_history,
56
+ Reline::Terminfo.tigetstr('cuf').sub(/%p1%d/, '').bytes => :ed_next_char,
57
+ Reline::Terminfo.tigetstr('cub').sub(/%p1%d/, '').bytes => :ed_prev_char,
58
+ }.each_pair do |key, func|
59
+ config.add_default_key_binding_by_keymap(:emacs, key, func)
60
+ config.add_default_key_binding_by_keymap(:vi_insert, key, func)
61
+ config.add_default_key_binding_by_keymap(:vi_command, key, func)
62
+ end
63
+ end
64
+
65
+ def self.set_default_key_bindings_comprehensive_list(config)
66
+ {
67
+ # Console (80x25)
68
+ [27, 91, 49, 126] => :ed_move_to_beg, # Home
69
+ [27, 91, 52, 126] => :ed_move_to_end, # End
70
+ [27, 91, 51, 126] => :key_delete, # Del
71
+ [27, 91, 65] => :ed_prev_history, # ↑
72
+ [27, 91, 66] => :ed_next_history, # ↓
73
+ [27, 91, 67] => :ed_next_char, # →
74
+ [27, 91, 68] => :ed_prev_char, # ←
75
+
76
+ # KDE
77
+ [27, 91, 72] => :ed_move_to_beg, # Home
78
+ [27, 91, 70] => :ed_move_to_end, # End
79
+ # Del is 0x08
80
+ [27, 71, 65] => :ed_prev_history, # ↑
81
+ [27, 71, 66] => :ed_next_history, # ↓
82
+ [27, 71, 67] => :ed_next_char, # →
83
+ [27, 71, 68] => :ed_prev_char, # ←
84
+
85
+ # urxvt / exoterm
86
+ [27, 91, 55, 126] => :ed_move_to_beg, # Home
87
+ [27, 91, 56, 126] => :ed_move_to_end, # End
88
+
89
+ # GNOME
90
+ [27, 79, 72] => :ed_move_to_beg, # Home
91
+ [27, 79, 70] => :ed_move_to_end, # End
92
+ # Del is 0x08
93
+ # Arrow keys are the same of KDE
94
+
95
+ # iTerm2
96
+ [27, 27, 91, 67] => :em_next_word, # Option+→, extended entry
97
+ [27, 27, 91, 68] => :ed_prev_word, # Option+←, extended entry
98
+ [195, 166] => :em_next_word, # Option+f
99
+ [195, 162] => :ed_prev_word, # Option+b
100
+
101
+ [27, 79, 65] => :ed_prev_history, # ↑
102
+ [27, 79, 66] => :ed_next_history, # ↓
103
+ [27, 79, 67] => :ed_next_char, # →
104
+ [27, 79, 68] => :ed_prev_char, # ←
105
+ }.each_pair do |key, func|
106
+ config.add_default_key_binding_by_keymap(:emacs, key, func)
107
+ config.add_default_key_binding_by_keymap(:vi_insert, key, func)
108
+ config.add_default_key_binding_by_keymap(:vi_command, key, func)
109
+ end
110
+ end
59
111
 
60
112
  @@input = STDIN
61
113
  def self.input=(val)
@@ -79,6 +131,8 @@ class Reline::ANSI
79
131
  rescue Errno::EIO
80
132
  # Maybe the I/O has been closed.
81
133
  nil
134
+ rescue Errno::ENOTTY
135
+ nil
82
136
  end
83
137
 
84
138
  @@in_bracketed_paste_mode = false
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
@@ -69,8 +71,9 @@ class Reline::Config
69
71
  if editing_mode_is?(:vi_command)
70
72
  @editing_mode_label = :vi_insert
71
73
  end
72
- @additional_key_bindings = {}
73
- @default_key_bindings = {}
74
+ @additional_key_bindings.keys.each do |key|
75
+ @additional_key_bindings[key].clear
76
+ end
74
77
  end
75
78
 
76
79
  def editing_mode
@@ -135,19 +138,28 @@ class Reline::Config
135
138
  end
136
139
 
137
140
  def key_bindings
138
- # override @default_key_bindings with @additional_key_bindings
139
- @default_key_bindings.merge(@additional_key_bindings)
141
+ # override @key_actors[@editing_mode_label].default_key_bindings with @additional_key_bindings[@editing_mode_label]
142
+ @key_actors[@editing_mode_label].default_key_bindings.merge(@additional_key_bindings[@editing_mode_label])
143
+ end
144
+
145
+ def add_default_key_binding_by_keymap(keymap, keystroke, target)
146
+ @key_actors[keymap].default_key_bindings[keystroke] = target
140
147
  end
141
148
 
142
149
  def add_default_key_binding(keystroke, target)
143
- @default_key_bindings[keystroke] = target
150
+ @key_actors[@keymap_label].default_key_bindings[keystroke] = target
144
151
  end
145
152
 
146
153
  def reset_default_key_bindings
147
- @default_key_bindings = {}
154
+ @key_actors.values.each do |ka|
155
+ ka.reset_default_key_bindings
156
+ end
148
157
  end
149
158
 
150
159
  def read_lines(lines, file = nil)
160
+ if lines.first.encoding != Reline.encoding_system_needs
161
+ lines = lines.map { |l| l.encode(Reline.encoding_system_needs) }
162
+ end
151
163
  conditions = [@skip_section, @if_stack]
152
164
  @skip_section = nil
153
165
  @if_stack = []
@@ -174,7 +186,7 @@ class Reline::Config
174
186
  key, func_name = $1, $2
175
187
  keystroke, func = bind_key(key, func_name)
176
188
  next unless keystroke
177
- @additional_key_bindings[keystroke] = func
189
+ @additional_key_bindings[@keymap_label][keystroke] = func
178
190
  end
179
191
  end
180
192
  unless @if_stack.empty?
@@ -282,11 +294,8 @@ class Reline::Config
282
294
  end
283
295
 
284
296
  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
297
+ str = $1 if str =~ /\A"(.*)"\z/
298
+ parse_keyseq(str).map { |c| c.chr(Reline.encoding_system_needs) }.join
290
299
  end
291
300
 
292
301
  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
@@ -58,13 +58,17 @@ class Reline::LineEditor
58
58
  reset_variables(encoding: encoding)
59
59
  end
60
60
 
61
+ def set_pasting_state(in_pasting)
62
+ @in_pasting = in_pasting
63
+ end
64
+
61
65
  def simplified_rendering?
62
66
  if finished?
63
67
  false
64
68
  elsif @just_cursor_moving and not @rerender_all
65
69
  true
66
70
  else
67
- not @rerender_all and not finished? and Reline::IOGate.in_pasting?
71
+ not @rerender_all and not finished? and @in_pasting
68
72
  end
69
73
  end
70
74
 
@@ -120,6 +124,7 @@ class Reline::LineEditor
120
124
  @prompt_cache_time = Time.now.to_f
121
125
  end
122
126
  prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
127
+ prompt_list = [prompt] if prompt_list.empty?
123
128
  mode_string = check_mode_string
124
129
  prompt_list = prompt_list.map{ |pr| mode_string + pr } if mode_string
125
130
  prompt = prompt_list[@line_index]
@@ -146,6 +151,13 @@ class Reline::LineEditor
146
151
  @screen_height = @screen_size.first
147
152
  reset_variables(prompt, encoding: encoding)
148
153
  @old_trap = Signal.trap('SIGINT') {
154
+ if @scroll_partial_screen
155
+ move_cursor_down(@screen_height - (@line_index - @scroll_partial_screen) - 1)
156
+ else
157
+ move_cursor_down(@highest_in_all - @line_index - 1)
158
+ end
159
+ Reline::IOGate.move_cursor_column(0)
160
+ scroll_down(1)
149
161
  @old_trap.call if @old_trap.respond_to?(:call) # can also be string, ex: "DEFAULT"
150
162
  raise Interrupt
151
163
  }
@@ -227,6 +239,8 @@ class Reline::LineEditor
227
239
  @scroll_partial_screen = nil
228
240
  @prev_mode_string = nil
229
241
  @drop_terminate_spaces = false
242
+ @in_pasting = false
243
+ @auto_indent_proc = nil
230
244
  reset_line
231
245
  end
232
246
 
@@ -330,8 +344,9 @@ class Reline::LineEditor
330
344
  else
331
345
  end_of_line_cursor = new_cursor_max
332
346
  end
333
- line_to_calc.encode(Encoding::UTF_8).grapheme_clusters.each do |gc|
334
- mbchar_width = Reline::Unicode.get_mbchar_width(gc)
347
+ line_to_calc.grapheme_clusters.each do |gc|
348
+ mbchar = gc.encode(Encoding::UTF_8)
349
+ mbchar_width = Reline::Unicode.get_mbchar_width(mbchar)
335
350
  now = new_cursor + mbchar_width
336
351
  if now > end_of_line_cursor or now > cursor
337
352
  break
@@ -375,10 +390,28 @@ class Reline::LineEditor
375
390
  @cleared = false
376
391
  return
377
392
  end
393
+ if @is_multiline and finished? and @scroll_partial_screen
394
+ # Re-output all code higher than the screen when finished.
395
+ Reline::IOGate.move_cursor_up(@first_line_started_from + @started_from - @scroll_partial_screen)
396
+ Reline::IOGate.move_cursor_column(0)
397
+ @scroll_partial_screen = nil
398
+ prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
399
+ if @previous_line_index
400
+ new_lines = whole_lines(index: @previous_line_index, line: @line)
401
+ else
402
+ new_lines = whole_lines
403
+ end
404
+ modify_lines(new_lines).each_with_index do |line, index|
405
+ @output.write "#{prompt_list ? prompt_list[index] : prompt}#{line}\n"
406
+ Reline::IOGate.erase_after_cursor
407
+ end
408
+ @output.flush
409
+ return
410
+ end
378
411
  new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
379
- # FIXME: end of logical line sometimes breaks
412
+ rendered = false
380
413
  if @add_newline_to_end_of_buffer
381
- rerender_added_newline
414
+ rerender_added_newline(prompt, prompt_width)
382
415
  @add_newline_to_end_of_buffer = false
383
416
  else
384
417
  if @just_cursor_moving and not @rerender_all
@@ -396,20 +429,32 @@ class Reline::LineEditor
396
429
  else
397
430
  end
398
431
  end
399
- line = modify_lines(whole_lines)[@line_index]
400
432
  if @is_multiline
401
- prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
402
433
  if finished?
403
434
  # Always rerender on finish because output_modifier_proc may return a different output.
435
+ if @previous_line_index
436
+ new_lines = whole_lines(index: @previous_line_index, line: @line)
437
+ else
438
+ new_lines = whole_lines
439
+ end
440
+ line = modify_lines(new_lines)[@line_index]
441
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
404
442
  render_partial(prompt, prompt_width, line, @first_line_started_from)
443
+ move_cursor_down(@highest_in_all - (@first_line_started_from + @highest_in_this - 1) - 1)
405
444
  scroll_down(1)
406
445
  Reline::IOGate.move_cursor_column(0)
407
446
  Reline::IOGate.erase_after_cursor
408
447
  elsif not rendered
409
- render_partial(prompt, prompt_width, line, @first_line_started_from)
448
+ unless @in_pasting
449
+ line = modify_lines(whole_lines)[@line_index]
450
+ prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
451
+ render_partial(prompt, prompt_width, line, @first_line_started_from)
452
+ end
410
453
  end
411
454
  @buffer_of_lines[@line_index] = @line
455
+ @rest_height = 0 if @scroll_partial_screen
412
456
  else
457
+ line = modify_lines(whole_lines)[@line_index]
413
458
  render_partial(prompt, prompt_width, line, 0)
414
459
  if finished?
415
460
  scroll_down(1)
@@ -452,13 +497,13 @@ class Reline::LineEditor
452
497
  end
453
498
  end
454
499
 
455
- private def rerender_added_newline
500
+ private def rerender_added_newline(prompt, prompt_width)
456
501
  scroll_down(1)
457
- new_lines = whole_lines(index: @previous_line_index, line: @line)
458
- prompt, prompt_width, = check_multiline_prompt(new_lines, prompt)
459
502
  @buffer_of_lines[@previous_line_index] = @line
460
503
  @line = @buffer_of_lines[@line_index]
461
- render_partial(prompt, prompt_width, @line, @first_line_started_from + @started_from + 1, with_control: false)
504
+ unless @in_pasting
505
+ render_partial(prompt, prompt_width, @line, @first_line_started_from + @started_from + 1, with_control: false)
506
+ end
462
507
  @cursor = @cursor_max = calculate_width(@line)
463
508
  @byte_pointer = @line.bytesize
464
509
  @highest_in_all += @highest_in_this
@@ -567,7 +612,13 @@ class Reline::LineEditor
567
612
  new_first_line_started_from = calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list || prompt)
568
613
  end
569
614
  new_started_from = calculate_height_by_width(prompt_width + @cursor) - 1
570
- if back > old_highest_in_all
615
+ calculate_scroll_partial_screen(back, new_first_line_started_from + new_started_from)
616
+ if @scroll_partial_screen
617
+ move_cursor_up(@first_line_started_from + @started_from)
618
+ scroll_down(@screen_height - 1)
619
+ move_cursor_up(@screen_height)
620
+ Reline::IOGate.move_cursor_column(0)
621
+ elsif back > old_highest_in_all
571
622
  scroll_down(back - 1)
572
623
  move_cursor_up(back - 1)
573
624
  elsif back < old_highest_in_all
@@ -579,7 +630,6 @@ class Reline::LineEditor
579
630
  end
580
631
  move_cursor_up(old_highest_in_all - 1)
581
632
  end
582
- calculate_scroll_partial_screen(back, new_first_line_started_from + new_started_from)
583
633
  render_whole_lines(new_buffer, prompt_list || prompt, prompt_width)
584
634
  if @prompt_proc
585
635
  prompt = prompt_list[@line_index]
@@ -627,7 +677,6 @@ class Reline::LineEditor
627
677
  private def render_partial(prompt, prompt_width, line_to_render, this_started_from, with_control: true)
628
678
  visual_lines, height = split_by_width(line_to_render.nil? ? prompt : prompt + line_to_render, @screen_size.last)
629
679
  cursor_up_from_last_line = 0
630
- # TODO: This logic would be sometimes buggy if this logical line isn't the current @line_index.
631
680
  if @scroll_partial_screen
632
681
  last_visual_line = this_started_from + (height - 1)
633
682
  last_screen_line = @scroll_partial_screen + (@screen_height - 1)
@@ -665,8 +714,8 @@ class Reline::LineEditor
665
714
  @highest_in_this = height
666
715
  end
667
716
  move_cursor_up(@started_from)
668
- cursor_up_from_last_line = height - 1 - @started_from
669
717
  @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
718
+ cursor_up_from_last_line = height - 1 - @started_from
670
719
  end
671
720
  if Reline::Unicode::CSI_REGEXP.match?(prompt + line_to_render)
672
721
  @output.write "\e[0m" # clear character decorations
@@ -676,7 +725,7 @@ class Reline::LineEditor
676
725
  if line.nil?
677
726
  if calculate_width(visual_lines[index - 1], true) == Reline::IOGate.get_screen_size.last
678
727
  # reaches the end of line
679
- if Reline::IOGate.win?
728
+ if Reline::IOGate.win? and Reline::IOGate.win_legacy_console?
680
729
  # A newline is automatically inserted if a character is rendered at
681
730
  # eol on command prompt.
682
731
  else
@@ -694,7 +743,7 @@ class Reline::LineEditor
694
743
  next
695
744
  end
696
745
  @output.write line
697
- if Reline::IOGate.win? and calculate_width(line, true) == Reline::IOGate.get_screen_size.last
746
+ if Reline::IOGate.win? and Reline::IOGate.win_legacy_console? and calculate_width(line, true) == Reline::IOGate.get_screen_size.last
698
747
  # A newline is automatically inserted if a character is rendered at eol on command prompt.
699
748
  @rest_height -= 1 if @rest_height > 0
700
749
  end
@@ -762,6 +811,7 @@ class Reline::LineEditor
762
811
  end
763
812
  move_cursor_up(back)
764
813
  move_cursor_down(@first_line_started_from + @started_from)
814
+ @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
765
815
  Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
766
816
  end
767
817
 
@@ -1089,7 +1139,7 @@ class Reline::LineEditor
1089
1139
  unless completion_occurs
1090
1140
  @completion_state = CompletionState::NORMAL
1091
1141
  end
1092
- if not Reline::IOGate.in_pasting? and @just_cursor_moving.nil?
1142
+ if not @in_pasting and @just_cursor_moving.nil?
1093
1143
  if @previous_line_index and @buffer_of_lines[@previous_line_index] == @line
1094
1144
  @just_cursor_moving = true
1095
1145
  elsif @previous_line_index.nil? and @buffer_of_lines[@line_index] == @line and old_line == @line
@@ -1107,8 +1157,25 @@ class Reline::LineEditor
1107
1157
 
1108
1158
  def call_completion_proc
1109
1159
  result = retrieve_completion_block(true)
1110
- slice = result[1]
1111
- result = @completion_proc.(slice) if @completion_proc and slice
1160
+ preposing, target, postposing = result
1161
+ if @completion_proc and target
1162
+ argnum = @completion_proc.parameters.inject(0) { |result, item|
1163
+ case item.first
1164
+ when :req, :opt
1165
+ result + 1
1166
+ when :rest
1167
+ break 3
1168
+ end
1169
+ }
1170
+ case argnum
1171
+ when 1
1172
+ result = @completion_proc.(target)
1173
+ when 2
1174
+ result = @completion_proc.(target, preposing)
1175
+ when 3..Float::INFINITY
1176
+ result = @completion_proc.(target, preposing, postposing)
1177
+ end
1178
+ end
1112
1179
  Reline.core.instance_variable_set(:@completion_quote_character, nil)
1113
1180
  result
1114
1181
  end
@@ -1156,8 +1223,16 @@ class Reline::LineEditor
1156
1223
  end
1157
1224
 
1158
1225
  def retrieve_completion_block(set_completion_quote_character = false)
1159
- word_break_regexp = /\A[#{Regexp.escape(Reline.completer_word_break_characters)}]/
1160
- quote_characters_regexp = /\A[#{Regexp.escape(Reline.completer_quote_characters)}]/
1226
+ if Reline.completer_word_break_characters.empty?
1227
+ word_break_regexp = nil
1228
+ else
1229
+ word_break_regexp = /\A[#{Regexp.escape(Reline.completer_word_break_characters)}]/
1230
+ end
1231
+ if Reline.completer_quote_characters.empty?
1232
+ quote_characters_regexp = nil
1233
+ else
1234
+ quote_characters_regexp = /\A[#{Regexp.escape(Reline.completer_quote_characters)}]/
1235
+ end
1161
1236
  before = @line.byteslice(0, @byte_pointer)
1162
1237
  rest = nil
1163
1238
  break_pointer = nil
@@ -1178,14 +1253,14 @@ class Reline::LineEditor
1178
1253
  elsif quote and slice.start_with?(escaped_quote)
1179
1254
  # skip
1180
1255
  i += 2
1181
- elsif slice =~ quote_characters_regexp # find new "
1256
+ elsif quote_characters_regexp and slice =~ quote_characters_regexp # find new "
1182
1257
  rest = $'
1183
1258
  quote = $&
1184
1259
  closing_quote = /(?!\\)#{Regexp.escape(quote)}/
1185
1260
  escaped_quote = /\\#{Regexp.escape(quote)}/
1186
1261
  i += 1
1187
1262
  break_pointer = i - 1
1188
- elsif not quote and slice =~ word_break_regexp
1263
+ elsif word_break_regexp and not quote and slice =~ word_break_regexp
1189
1264
  rest = $'
1190
1265
  i += 1
1191
1266
  before = @line.byteslice(i, @byte_pointer - i)
@@ -1213,6 +1288,19 @@ class Reline::LineEditor
1213
1288
  end
1214
1289
  target = before
1215
1290
  end
1291
+ if @is_multiline
1292
+ if @previous_line_index
1293
+ lines = whole_lines(index: @previous_line_index, line: @line)
1294
+ else
1295
+ lines = whole_lines
1296
+ end
1297
+ if @line_index > 0
1298
+ preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing
1299
+ end
1300
+ if (lines.size - 1) > @line_index
1301
+ postposing = postposing + "\n" + lines[(@line_index + 1)..-1].join("\n")
1302
+ end
1303
+ end
1216
1304
  [preposing.encode(@encoding), target.encode(@encoding), postposing.encode(@encoding)]
1217
1305
  end
1218
1306
 
@@ -1240,10 +1328,32 @@ class Reline::LineEditor
1240
1328
 
1241
1329
  def delete_text(start = nil, length = nil)
1242
1330
  if start.nil? and length.nil?
1243
- @line&.clear
1244
- @byte_pointer = 0
1245
- @cursor = 0
1246
- @cursor_max = 0
1331
+ if @is_multiline
1332
+ if @buffer_of_lines.size == 1
1333
+ @line&.clear
1334
+ @byte_pointer = 0
1335
+ @cursor = 0
1336
+ @cursor_max = 0
1337
+ elsif @line_index == (@buffer_of_lines.size - 1) and @line_index > 0
1338
+ @buffer_of_lines.pop
1339
+ @line_index -= 1
1340
+ @line = @buffer_of_lines[@line_index]
1341
+ @byte_pointer = 0
1342
+ @cursor = 0
1343
+ @cursor_max = calculate_width(@line)
1344
+ elsif @line_index < (@buffer_of_lines.size - 1)
1345
+ @buffer_of_lines.delete_at(@line_index)
1346
+ @line = @buffer_of_lines[@line_index]
1347
+ @byte_pointer = 0
1348
+ @cursor = 0
1349
+ @cursor_max = calculate_width(@line)
1350
+ end
1351
+ else
1352
+ @line&.clear
1353
+ @byte_pointer = 0
1354
+ @cursor = 0
1355
+ @cursor_max = 0
1356
+ end
1247
1357
  elsif not start.nil? and not length.nil?
1248
1358
  if @line
1249
1359
  before = @line.byteslice(0, start)
@@ -1293,7 +1403,11 @@ class Reline::LineEditor
1293
1403
  if @buffer_of_lines.size == 1 and @line.nil?
1294
1404
  nil
1295
1405
  else
1296
- whole_lines.join("\n")
1406
+ if @previous_line_index
1407
+ whole_lines(index: @previous_line_index, line: @line).join("\n")
1408
+ else
1409
+ whole_lines.join("\n")
1410
+ end
1297
1411
  end
1298
1412
  end
1299
1413
 
@@ -1339,14 +1453,14 @@ class Reline::LineEditor
1339
1453
  cursor_line = @line.byteslice(0, @byte_pointer)
1340
1454
  insert_new_line(cursor_line, next_line)
1341
1455
  @cursor = 0
1342
- @check_new_auto_indent = true unless Reline::IOGate.in_pasting?
1456
+ @check_new_auto_indent = true unless @in_pasting
1343
1457
  end
1344
1458
  end
1345
1459
 
1346
1460
  private def ed_unassigned(key) end # do nothing
1347
1461
 
1348
1462
  private def process_insert(force: false)
1349
- return if @continuous_insertion_buffer.empty? or (Reline::IOGate.in_pasting? and not force)
1463
+ return if @continuous_insertion_buffer.empty? or (@in_pasting and not force)
1350
1464
  width = Reline::Unicode.calculate_width(@continuous_insertion_buffer)
1351
1465
  bytesize = @continuous_insertion_buffer.bytesize
1352
1466
  if @cursor == @cursor_max
@@ -1381,7 +1495,7 @@ class Reline::LineEditor
1381
1495
  str = key.chr
1382
1496
  bytesize = 1
1383
1497
  end
1384
- if Reline::IOGate.in_pasting?
1498
+ if @in_pasting
1385
1499
  @continuous_insertion_buffer << str
1386
1500
  return
1387
1501
  elsif not @continuous_insertion_buffer.empty?
@@ -2431,11 +2545,23 @@ class Reline::LineEditor
2431
2545
 
2432
2546
  private def vi_histedit(key)
2433
2547
  path = Tempfile.open { |fp|
2434
- fp.write @line
2548
+ if @is_multiline
2549
+ fp.write whole_lines.join("\n")
2550
+ else
2551
+ fp.write @line
2552
+ end
2435
2553
  fp.path
2436
2554
  }
2437
2555
  system("#{ENV['EDITOR']} #{path}")
2438
- @line = File.read(path)
2556
+ if @is_multiline
2557
+ @buffer_of_lines = File.read(path).split("\n")
2558
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2559
+ @line_index = 0
2560
+ @line = @buffer_of_lines[@line_index]
2561
+ @rerender_all = true
2562
+ else
2563
+ @line = File.read(path)
2564
+ end
2439
2565
  finish
2440
2566
  end
2441
2567