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