reline 0.2.1 → 0.2.6

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: 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