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 +4 -4
- data/README.md +46 -0
- data/lib/reline.rb +26 -13
- data/lib/reline/ansi.rb +100 -46
- data/lib/reline/config.rb +22 -13
- data/lib/reline/general_io.rb +11 -3
- data/lib/reline/key_actor/base.rb +12 -0
- data/lib/reline/line_editor.rb +161 -35
- data/lib/reline/terminfo.rb +84 -0
- data/lib/reline/unicode.rb +1 -0
- data/lib/reline/version.rb +1 -1
- data/lib/reline/windows.rb +138 -78
- metadata +4 -59
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fc0f6fb7c331a37907554b292249a75b218646aa14298586790cc09d6c174894
|
4
|
+
data.tar.gz: 79fd7b502a7ab601f6642964bb0987dae5d0a62bc8cfda016d8806c8b76bb723
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
-
#
|
275
|
-
#
|
276
|
-
#
|
277
|
-
#
|
278
|
-
#
|
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
|
-
|
456
|
-
|
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
|
-
|
462
|
-
|
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
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
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
|
-
@
|
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
|
-
|
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
|
-
@
|
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
|
-
|
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)
|
data/lib/reline/general_io.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
data/lib/reline/line_editor.rb
CHANGED
@@ -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
|
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.
|
334
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
1111
|
-
|
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
|
-
|
1160
|
-
|
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
|
-
@
|
1244
|
-
|
1245
|
-
|
1246
|
-
|
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
|
-
|
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
|
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 (
|
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
|
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
|
-
|
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
|
-
@
|
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
|
|