reline 0.2.4 → 0.2.8.pre.1
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/ansi.rb +119 -56
- data/lib/reline/config.rb +30 -13
- data/lib/reline/general_io.rb +11 -3
- data/lib/reline/key_actor/base.rb +12 -0
- data/lib/reline/line_editor.rb +395 -23
- data/lib/reline/terminfo.rb +126 -0
- data/lib/reline/unicode.rb +30 -0
- data/lib/reline/version.rb +1 -1
- data/lib/reline/windows.rb +164 -80
- data/lib/reline.rb +78 -13
- metadata +6 -62
- data/lib/reline/line_editor.rb.orig +0 -2696
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2c1dd7202ef6de3ebf89e2ed39c83af1e48bcd46d5f967037edc0097301b8286
|
4
|
+
data.tar.gz: 4d2047341862d83b785903974e4766b5a3d481a033208bfcc1d8891a0b116e65
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a9109bb68de735bd5dba6de8dffecfd23fecd8f09aa0b1f92d0bca6b008ef7c3d3785c16292db8ea90c9baf4d275ef382cd6d0d15487fbe6e491c74a670004bd
|
7
|
+
data.tar.gz: af8f8b99ceb34416253857caf4d87a07bde1fddf72d80ff14caeb2a5940178566bd63fc4a6884eddd60959ac3f352c056bb73bbe646915582b5b3b4d091beeed
|
data/README.md
CHANGED
@@ -8,6 +8,52 @@ This is a screen capture of *IRB improved by Reline*.
|
|
8
8
|
|
9
9
|
Reline is compatible with the API of Ruby's stdlib 'readline', GNU Readline and Editline by pure Ruby implementation.
|
10
10
|
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
### Single line editing mode
|
14
|
+
|
15
|
+
It's compatible with the readline standard library.
|
16
|
+
|
17
|
+
See [the document of readline stdlib](https://ruby-doc.org/stdlib/libdoc/readline/rdoc/Readline.html) or [bin/example](https://github.com/ruby/reline/blob/master/bin/example).
|
18
|
+
|
19
|
+
### Multi-line editing mode
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
require "reline"
|
23
|
+
|
24
|
+
prompt = 'prompt> '
|
25
|
+
use_history = true
|
26
|
+
|
27
|
+
begin
|
28
|
+
while true
|
29
|
+
text = Reline.readmultiline(prompt, use_history) do |multiline_input|
|
30
|
+
# Accept the input until `end` is entered
|
31
|
+
multiline_input.split.last == "end"
|
32
|
+
end
|
33
|
+
|
34
|
+
puts 'You entered:'
|
35
|
+
puts text
|
36
|
+
end
|
37
|
+
# If you want to exit, type Ctrl-C
|
38
|
+
rescue Interrupt
|
39
|
+
puts '^C'
|
40
|
+
exit 0
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
```bash
|
45
|
+
$ ruby example.rb
|
46
|
+
prompt> aaa
|
47
|
+
prompt> bbb
|
48
|
+
prompt> end
|
49
|
+
You entered:
|
50
|
+
aaa
|
51
|
+
bbb
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
See also: [test/reline/yamatanooroti/multiline_repl](https://github.com/ruby/reline/blob/master/test/reline/yamatanooroti/multiline_repl)
|
56
|
+
|
11
57
|
## License
|
12
58
|
|
13
59
|
The gem is available as open source under the terms of the [Ruby License](https://www.ruby-lang.org/en/about/license.txt).
|
data/lib/reline/ansi.rb
CHANGED
@@ -1,7 +1,13 @@
|
|
1
1
|
require 'io/console'
|
2
|
+
require 'io/wait'
|
2
3
|
require 'timeout'
|
4
|
+
require_relative 'terminfo'
|
3
5
|
|
4
6
|
class Reline::ANSI
|
7
|
+
if Reline::Terminfo.enabled?
|
8
|
+
Reline::Terminfo.setupterm(0, 2)
|
9
|
+
end
|
10
|
+
|
5
11
|
def self.encoding
|
6
12
|
Encoding.default_external
|
7
13
|
end
|
@@ -10,52 +16,99 @@ class Reline::ANSI
|
|
10
16
|
false
|
11
17
|
end
|
12
18
|
|
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
|
-
|
19
|
+
def self.set_default_key_bindings(config)
|
20
|
+
if Reline::Terminfo.enabled?
|
21
|
+
set_default_key_bindings_terminfo(config)
|
22
|
+
else
|
23
|
+
set_default_key_bindings_comprehensive_list(config)
|
24
|
+
end
|
25
|
+
{
|
26
|
+
# extended entries of terminfo
|
27
|
+
[27, 91, 49, 59, 53, 67] => :em_next_word, # Ctrl+→, extended entry
|
28
|
+
[27, 91, 49, 59, 53, 68] => :ed_prev_word, # Ctrl+←, extended entry
|
29
|
+
[27, 91, 49, 59, 51, 67] => :em_next_word, # Meta+→, extended entry
|
30
|
+
[27, 91, 49, 59, 51, 68] => :ed_prev_word, # Meta+←, extended entry
|
31
|
+
}.each_pair do |key, func|
|
32
|
+
config.add_default_key_binding_by_keymap(:emacs, key, func)
|
33
|
+
config.add_default_key_binding_by_keymap(:vi_insert, key, func)
|
34
|
+
config.add_default_key_binding_by_keymap(:vi_command, key, func)
|
35
|
+
end
|
36
|
+
{
|
37
|
+
# default bindings
|
38
|
+
[27, 32] => :em_set_mark, # M-<space>
|
39
|
+
[24, 24] => :em_exchange_mark, # C-x C-x
|
40
|
+
}.each_pair do |key, func|
|
41
|
+
config.add_default_key_binding_by_keymap(:emacs, key, func)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.set_default_key_bindings_terminfo(config)
|
46
|
+
{
|
47
|
+
Reline::Terminfo.tigetstr('khome').bytes => :ed_move_to_beg,
|
48
|
+
Reline::Terminfo.tigetstr('kend').bytes => :ed_move_to_end,
|
49
|
+
Reline::Terminfo.tigetstr('kcuu1').bytes => :ed_prev_history,
|
50
|
+
Reline::Terminfo.tigetstr('kcud1').bytes => :ed_next_history,
|
51
|
+
Reline::Terminfo.tigetstr('kcuf1').bytes => :ed_next_char,
|
52
|
+
Reline::Terminfo.tigetstr('kcub1').bytes => :ed_prev_char,
|
53
|
+
# Escape sequences that omit the move distance and are set to defaults
|
54
|
+
# value 1 may be sometimes sent by pressing the arrow-key.
|
55
|
+
Reline::Terminfo.tigetstr('cuu').sub(/%p1%d/, '').bytes => :ed_prev_history,
|
56
|
+
Reline::Terminfo.tigetstr('cud').sub(/%p1%d/, '').bytes => :ed_next_history,
|
57
|
+
Reline::Terminfo.tigetstr('cuf').sub(/%p1%d/, '').bytes => :ed_next_char,
|
58
|
+
Reline::Terminfo.tigetstr('cub').sub(/%p1%d/, '').bytes => :ed_prev_char,
|
59
|
+
}.each_pair do |key, func|
|
60
|
+
config.add_default_key_binding_by_keymap(:emacs, key, func)
|
61
|
+
config.add_default_key_binding_by_keymap(:vi_insert, key, func)
|
62
|
+
config.add_default_key_binding_by_keymap(:vi_command, key, func)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.set_default_key_bindings_comprehensive_list(config)
|
67
|
+
{
|
68
|
+
# Console (80x25)
|
69
|
+
[27, 91, 49, 126] => :ed_move_to_beg, # Home
|
70
|
+
[27, 91, 52, 126] => :ed_move_to_end, # End
|
71
|
+
[27, 91, 51, 126] => :key_delete, # Del
|
72
|
+
[27, 91, 65] => :ed_prev_history, # ↑
|
73
|
+
[27, 91, 66] => :ed_next_history, # ↓
|
74
|
+
[27, 91, 67] => :ed_next_char, # →
|
75
|
+
[27, 91, 68] => :ed_prev_char, # ←
|
76
|
+
|
77
|
+
# KDE
|
78
|
+
[27, 91, 72] => :ed_move_to_beg, # Home
|
79
|
+
[27, 91, 70] => :ed_move_to_end, # End
|
80
|
+
# Del is 0x08
|
81
|
+
[27, 71, 65] => :ed_prev_history, # ↑
|
82
|
+
[27, 71, 66] => :ed_next_history, # ↓
|
83
|
+
[27, 71, 67] => :ed_next_char, # →
|
84
|
+
[27, 71, 68] => :ed_prev_char, # ←
|
85
|
+
|
86
|
+
# urxvt / exoterm
|
87
|
+
[27, 91, 55, 126] => :ed_move_to_beg, # Home
|
88
|
+
[27, 91, 56, 126] => :ed_move_to_end, # End
|
89
|
+
|
90
|
+
# GNOME
|
91
|
+
[27, 79, 72] => :ed_move_to_beg, # Home
|
92
|
+
[27, 79, 70] => :ed_move_to_end, # End
|
93
|
+
# Del is 0x08
|
94
|
+
# Arrow keys are the same of KDE
|
95
|
+
|
96
|
+
# iTerm2
|
97
|
+
[27, 27, 91, 67] => :em_next_word, # Option+→, extended entry
|
98
|
+
[27, 27, 91, 68] => :ed_prev_word, # Option+←, extended entry
|
99
|
+
[195, 166] => :em_next_word, # Option+f
|
100
|
+
[195, 162] => :ed_prev_word, # Option+b
|
101
|
+
|
102
|
+
[27, 79, 65] => :ed_prev_history, # ↑
|
103
|
+
[27, 79, 66] => :ed_next_history, # ↓
|
104
|
+
[27, 79, 67] => :ed_next_char, # →
|
105
|
+
[27, 79, 68] => :ed_prev_char, # ←
|
106
|
+
}.each_pair do |key, func|
|
107
|
+
config.add_default_key_binding_by_keymap(:emacs, key, func)
|
108
|
+
config.add_default_key_binding_by_keymap(:vi_insert, key, func)
|
109
|
+
config.add_default_key_binding_by_keymap(:vi_command, key, func)
|
110
|
+
end
|
111
|
+
end
|
59
112
|
|
60
113
|
@@input = STDIN
|
61
114
|
def self.input=(val)
|
@@ -79,6 +132,8 @@ class Reline::ANSI
|
|
79
132
|
rescue Errno::EIO
|
80
133
|
# Maybe the I/O has been closed.
|
81
134
|
nil
|
135
|
+
rescue Errno::ENOTTY
|
136
|
+
nil
|
82
137
|
end
|
83
138
|
|
84
139
|
@@in_bracketed_paste_mode = false
|
@@ -129,12 +184,7 @@ class Reline::ANSI
|
|
129
184
|
unless @@buf.empty?
|
130
185
|
return false
|
131
186
|
end
|
132
|
-
|
133
|
-
if rs and rs[0]
|
134
|
-
false
|
135
|
-
else
|
136
|
-
true
|
137
|
-
end
|
187
|
+
!@@input.wait_readable(0)
|
138
188
|
end
|
139
189
|
|
140
190
|
def self.ungetc(c)
|
@@ -143,8 +193,7 @@ class Reline::ANSI
|
|
143
193
|
|
144
194
|
def self.retrieve_keybuffer
|
145
195
|
begin
|
146
|
-
|
147
|
-
return if result.nil?
|
196
|
+
return unless @@input.wait_readable(0.001)
|
148
197
|
str = @@input.read_nonblock(1024)
|
149
198
|
str.bytes.each do |c|
|
150
199
|
@@buf.push(c)
|
@@ -225,6 +274,22 @@ class Reline::ANSI
|
|
225
274
|
end
|
226
275
|
end
|
227
276
|
|
277
|
+
def self.hide_cursor
|
278
|
+
if Reline::Terminfo.enabled?
|
279
|
+
@@output.write Reline::Terminfo.tigetstr('civis')
|
280
|
+
else
|
281
|
+
# ignored
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
def self.show_cursor
|
286
|
+
if Reline::Terminfo.enabled?
|
287
|
+
@@output.write Reline::Terminfo.tigetstr('cnorm')
|
288
|
+
else
|
289
|
+
# ignored
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
228
293
|
def self.erase_after_cursor
|
229
294
|
@@output.write "\e[K"
|
230
295
|
end
|
@@ -246,8 +311,6 @@ class Reline::ANSI
|
|
246
311
|
|
247
312
|
def self.prep
|
248
313
|
retrieve_keybuffer
|
249
|
-
int_handle = Signal.trap('INT', 'IGNORE')
|
250
|
-
Signal.trap('INT', int_handle)
|
251
314
|
nil
|
252
315
|
end
|
253
316
|
|
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,10 @@ 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
|
77
|
+
reset_default_key_bindings
|
74
78
|
end
|
75
79
|
|
76
80
|
def editing_mode
|
@@ -135,19 +139,35 @@ class Reline::Config
|
|
135
139
|
end
|
136
140
|
|
137
141
|
def key_bindings
|
138
|
-
# override @default_key_bindings with @additional_key_bindings
|
139
|
-
@default_key_bindings.merge(@additional_key_bindings)
|
142
|
+
# override @key_actors[@editing_mode_label].default_key_bindings with @additional_key_bindings[@editing_mode_label]
|
143
|
+
@key_actors[@editing_mode_label].default_key_bindings.merge(@additional_key_bindings[@editing_mode_label])
|
144
|
+
end
|
145
|
+
|
146
|
+
def add_default_key_binding_by_keymap(keymap, keystroke, target)
|
147
|
+
@key_actors[keymap].default_key_bindings[keystroke] = target
|
140
148
|
end
|
141
149
|
|
142
150
|
def add_default_key_binding(keystroke, target)
|
143
|
-
@default_key_bindings[keystroke] = target
|
151
|
+
@key_actors[@keymap_label].default_key_bindings[keystroke] = target
|
144
152
|
end
|
145
153
|
|
146
154
|
def reset_default_key_bindings
|
147
|
-
@
|
155
|
+
@key_actors.values.each do |ka|
|
156
|
+
ka.reset_default_key_bindings
|
157
|
+
end
|
148
158
|
end
|
149
159
|
|
150
160
|
def read_lines(lines, file = nil)
|
161
|
+
if not lines.empty? and lines.first.encoding != Reline.encoding_system_needs
|
162
|
+
begin
|
163
|
+
lines = lines.map do |l|
|
164
|
+
l.encode(Reline.encoding_system_needs)
|
165
|
+
rescue Encoding::UndefinedConversionError
|
166
|
+
mes = "The inputrc encoded in #{lines.first.encoding.name} can't be converted to the locale #{Reline.encoding_system_needs.name}."
|
167
|
+
raise Reline::ConfigEncodingConversionError.new(mes)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
151
171
|
conditions = [@skip_section, @if_stack]
|
152
172
|
@skip_section = nil
|
153
173
|
@if_stack = []
|
@@ -174,7 +194,7 @@ class Reline::Config
|
|
174
194
|
key, func_name = $1, $2
|
175
195
|
keystroke, func = bind_key(key, func_name)
|
176
196
|
next unless keystroke
|
177
|
-
@additional_key_bindings[keystroke] = func
|
197
|
+
@additional_key_bindings[@keymap_label][keystroke] = func
|
178
198
|
end
|
179
199
|
end
|
180
200
|
unless @if_stack.empty?
|
@@ -282,11 +302,8 @@ class Reline::Config
|
|
282
302
|
end
|
283
303
|
|
284
304
|
def retrieve_string(str)
|
285
|
-
if str =~ /\A"(.*)"\z/
|
286
|
-
|
287
|
-
else
|
288
|
-
parse_keyseq(str).map(&:chr).join
|
289
|
-
end
|
305
|
+
str = $1 if str =~ /\A"(.*)"\z/
|
306
|
+
parse_keyseq(str).map { |c| c.chr(Reline.encoding_system_needs) }.join
|
290
307
|
end
|
291
308
|
|
292
309
|
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
@@ -150,7 +150,8 @@ class Reline::LineEditor
|
|
150
150
|
@screen_size = Reline::IOGate.get_screen_size
|
151
151
|
@screen_height = @screen_size.first
|
152
152
|
reset_variables(prompt, encoding: encoding)
|
153
|
-
@old_trap = Signal.trap(
|
153
|
+
@old_trap = Signal.trap(:INT) {
|
154
|
+
clear_dialog
|
154
155
|
if @scroll_partial_screen
|
155
156
|
move_cursor_down(@screen_height - (@line_index - @scroll_partial_screen) - 1)
|
156
157
|
else
|
@@ -158,8 +159,16 @@ class Reline::LineEditor
|
|
158
159
|
end
|
159
160
|
Reline::IOGate.move_cursor_column(0)
|
160
161
|
scroll_down(1)
|
161
|
-
|
162
|
-
|
162
|
+
case @old_trap
|
163
|
+
when 'DEFAULT', 'SYSTEM_DEFAULT'
|
164
|
+
raise Interrupt
|
165
|
+
when 'IGNORE'
|
166
|
+
# Do nothing
|
167
|
+
when 'EXIT'
|
168
|
+
exit
|
169
|
+
else
|
170
|
+
@old_trap.call
|
171
|
+
end
|
163
172
|
}
|
164
173
|
Reline::IOGate.set_winch_handler do
|
165
174
|
@rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
|
@@ -241,6 +250,7 @@ class Reline::LineEditor
|
|
241
250
|
@drop_terminate_spaces = false
|
242
251
|
@in_pasting = false
|
243
252
|
@auto_indent_proc = nil
|
253
|
+
@dialogs = []
|
244
254
|
reset_line
|
245
255
|
end
|
246
256
|
|
@@ -406,10 +416,10 @@ class Reline::LineEditor
|
|
406
416
|
Reline::IOGate.erase_after_cursor
|
407
417
|
end
|
408
418
|
@output.flush
|
419
|
+
clear_dialog
|
409
420
|
return
|
410
421
|
end
|
411
422
|
new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
|
412
|
-
# FIXME: end of logical line sometimes breaks
|
413
423
|
rendered = false
|
414
424
|
if @add_newline_to_end_of_buffer
|
415
425
|
rerender_added_newline(prompt, prompt_width)
|
@@ -417,6 +427,7 @@ class Reline::LineEditor
|
|
417
427
|
else
|
418
428
|
if @just_cursor_moving and not @rerender_all
|
419
429
|
rendered = just_move_cursor
|
430
|
+
render_dialog((prompt_width + @cursor) % @screen_size.last)
|
420
431
|
@just_cursor_moving = false
|
421
432
|
return
|
422
433
|
elsif @previous_line_index or new_highest_in_this != @highest_in_this
|
@@ -439,18 +450,20 @@ class Reline::LineEditor
|
|
439
450
|
new_lines = whole_lines
|
440
451
|
end
|
441
452
|
line = modify_lines(new_lines)[@line_index]
|
453
|
+
clear_dialog
|
442
454
|
prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
|
443
455
|
render_partial(prompt, prompt_width, line, @first_line_started_from)
|
444
456
|
move_cursor_down(@highest_in_all - (@first_line_started_from + @highest_in_this - 1) - 1)
|
445
457
|
scroll_down(1)
|
446
458
|
Reline::IOGate.move_cursor_column(0)
|
447
459
|
Reline::IOGate.erase_after_cursor
|
448
|
-
|
449
|
-
|
460
|
+
else
|
461
|
+
if not rendered and not @in_pasting
|
450
462
|
line = modify_lines(whole_lines)[@line_index]
|
451
463
|
prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
|
452
464
|
render_partial(prompt, prompt_width, line, @first_line_started_from)
|
453
465
|
end
|
466
|
+
render_dialog((prompt_width + @cursor) % @screen_size.last)
|
454
467
|
end
|
455
468
|
@buffer_of_lines[@line_index] = @line
|
456
469
|
@rest_height = 0 if @scroll_partial_screen
|
@@ -465,6 +478,289 @@ class Reline::LineEditor
|
|
465
478
|
end
|
466
479
|
end
|
467
480
|
|
481
|
+
class DialogProcScope
|
482
|
+
def initialize(line_editor, proc_to_exec, context)
|
483
|
+
@line_editor = line_editor
|
484
|
+
@proc_to_exec = proc_to_exec
|
485
|
+
@context = context
|
486
|
+
@cursor_pos = Reline::CursorPos.new
|
487
|
+
end
|
488
|
+
|
489
|
+
def context
|
490
|
+
@context
|
491
|
+
end
|
492
|
+
|
493
|
+
def retrieve_completion_block(set_completion_quote_character = false)
|
494
|
+
@line_editor.retrieve_completion_block(set_completion_quote_character)
|
495
|
+
end
|
496
|
+
|
497
|
+
def call_completion_proc_with_checking_args(pre, target, post)
|
498
|
+
@line_editor.call_completion_proc_with_checking_args(pre, target, post)
|
499
|
+
end
|
500
|
+
|
501
|
+
def set_cursor_pos(col, row)
|
502
|
+
@cursor_pos.x = col
|
503
|
+
@cursor_pos.y = row
|
504
|
+
end
|
505
|
+
|
506
|
+
def cursor_pos
|
507
|
+
@cursor_pos
|
508
|
+
end
|
509
|
+
|
510
|
+
def just_cursor_moving
|
511
|
+
@line_editor.instance_variable_get(:@just_cursor_moving)
|
512
|
+
end
|
513
|
+
|
514
|
+
def screen_width
|
515
|
+
@line_editor.instance_variable_get(:@screen_size).last
|
516
|
+
end
|
517
|
+
|
518
|
+
def completion_journey_data
|
519
|
+
@line_editor.instance_variable_get(:@completion_journey_data)
|
520
|
+
end
|
521
|
+
|
522
|
+
def call
|
523
|
+
instance_exec(&@proc_to_exec)
|
524
|
+
end
|
525
|
+
end
|
526
|
+
|
527
|
+
class Dialog
|
528
|
+
attr_reader :name
|
529
|
+
attr_accessor :column, :vertical_offset, :contents, :lines_backup
|
530
|
+
|
531
|
+
def initialize(name, proc_scope)
|
532
|
+
@name = name
|
533
|
+
@proc_scope = proc_scope
|
534
|
+
end
|
535
|
+
|
536
|
+
def set_cursor_pos(col, row)
|
537
|
+
@proc_scope.set_cursor_pos(col, row)
|
538
|
+
end
|
539
|
+
|
540
|
+
def call
|
541
|
+
@proc_scope.call
|
542
|
+
end
|
543
|
+
end
|
544
|
+
|
545
|
+
def add_dialog_proc(name, p, context = nil)
|
546
|
+
return if @dialogs.any? { |d| d.name == name }
|
547
|
+
@dialogs << Dialog.new(name, DialogProcScope.new(self, p, context))
|
548
|
+
end
|
549
|
+
|
550
|
+
DIALOG_HEIGHT = 20
|
551
|
+
DIALOG_WIDTH = 40
|
552
|
+
private def render_dialog(cursor_column)
|
553
|
+
@dialogs.each do |dialog|
|
554
|
+
render_each_dialog(dialog, cursor_column)
|
555
|
+
end
|
556
|
+
end
|
557
|
+
|
558
|
+
private def render_each_dialog(dialog, cursor_column)
|
559
|
+
if @in_pasting
|
560
|
+
dialog.contents = nil
|
561
|
+
return
|
562
|
+
end
|
563
|
+
dialog.set_cursor_pos(cursor_column, @first_line_started_from + @started_from)
|
564
|
+
pos, result, pointer, bg = dialog.call
|
565
|
+
old_dialog_contents = dialog.contents
|
566
|
+
old_dialog_column = dialog.column
|
567
|
+
old_dialog_vertical_offset = dialog.vertical_offset
|
568
|
+
if result and not result.empty?
|
569
|
+
dialog.contents = result
|
570
|
+
dialog.contents = dialog.contents[0...DIALOG_HEIGHT] if dialog.contents.size > DIALOG_HEIGHT
|
571
|
+
else
|
572
|
+
dialog.lines_backup = {
|
573
|
+
lines: modify_lines(whole_lines),
|
574
|
+
line_index: @line_index,
|
575
|
+
first_line_started_from: @first_line_started_from,
|
576
|
+
started_from: @started_from,
|
577
|
+
byte_pointer: @byte_pointer
|
578
|
+
}
|
579
|
+
clear_each_dialog(dialog)
|
580
|
+
dialog.contents = nil
|
581
|
+
return
|
582
|
+
end
|
583
|
+
upper_space = @first_line_started_from - @started_from
|
584
|
+
lower_space = @highest_in_all - @first_line_started_from - @started_from - 1
|
585
|
+
dialog.column = pos.x
|
586
|
+
diff = (dialog.column + DIALOG_WIDTH) - (@screen_size.last - 1)
|
587
|
+
if diff > 0
|
588
|
+
dialog.column -= diff
|
589
|
+
end
|
590
|
+
if (lower_space + @rest_height) >= DIALOG_HEIGHT
|
591
|
+
dialog.vertical_offset = pos.y + 1
|
592
|
+
elsif upper_space >= DIALOG_HEIGHT
|
593
|
+
dialog.vertical_offset = pos.y + -(DIALOG_HEIGHT + 1)
|
594
|
+
else
|
595
|
+
if (lower_space + @rest_height) < DIALOG_HEIGHT
|
596
|
+
scroll_down(DIALOG_HEIGHT)
|
597
|
+
move_cursor_up(DIALOG_HEIGHT)
|
598
|
+
end
|
599
|
+
dialog.vertical_offset = pos.y + 1
|
600
|
+
end
|
601
|
+
Reline::IOGate.hide_cursor
|
602
|
+
reset_dialog(dialog, old_dialog_contents, old_dialog_column, old_dialog_vertical_offset)
|
603
|
+
move_cursor_down(dialog.vertical_offset)
|
604
|
+
Reline::IOGate.move_cursor_column(dialog.column)
|
605
|
+
dialog.contents.each_with_index do |item, i|
|
606
|
+
if i == pointer
|
607
|
+
bg_color = '45'
|
608
|
+
else
|
609
|
+
if bg
|
610
|
+
bg_color = bg
|
611
|
+
else
|
612
|
+
bg_color = '46'
|
613
|
+
end
|
614
|
+
end
|
615
|
+
@output.write "\e[#{bg_color}m%-#{DIALOG_WIDTH}s\e[49m" % item.slice(0, DIALOG_WIDTH)
|
616
|
+
Reline::IOGate.move_cursor_column(dialog.column)
|
617
|
+
move_cursor_down(1) if i < (dialog.contents.size - 1)
|
618
|
+
end
|
619
|
+
Reline::IOGate.move_cursor_column(cursor_column)
|
620
|
+
move_cursor_up(dialog.vertical_offset + dialog.contents.size - 1)
|
621
|
+
Reline::IOGate.show_cursor
|
622
|
+
dialog.lines_backup = {
|
623
|
+
lines: modify_lines(whole_lines),
|
624
|
+
line_index: @line_index,
|
625
|
+
first_line_started_from: @first_line_started_from,
|
626
|
+
started_from: @started_from,
|
627
|
+
byte_pointer: @byte_pointer
|
628
|
+
}
|
629
|
+
end
|
630
|
+
|
631
|
+
private def reset_dialog(dialog, old_dialog_contents, old_dialog_column, old_dialog_vertical_offset)
|
632
|
+
return if dialog.lines_backup.nil? or old_dialog_contents.nil?
|
633
|
+
prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines], prompt)
|
634
|
+
visual_lines = []
|
635
|
+
visual_start = nil
|
636
|
+
dialog.lines_backup[:lines].each_with_index { |l, i|
|
637
|
+
pr = prompt_list ? prompt_list[i] : prompt
|
638
|
+
vl, _ = split_by_width(pr + l, @screen_size.last)
|
639
|
+
vl.compact!
|
640
|
+
if i == dialog.lines_backup[:line_index]
|
641
|
+
visual_start = visual_lines.size + dialog.lines_backup[:started_from]
|
642
|
+
end
|
643
|
+
visual_lines.concat(vl)
|
644
|
+
}
|
645
|
+
old_y = dialog.lines_backup[:first_line_started_from] + dialog.lines_backup[:started_from]
|
646
|
+
y = @first_line_started_from + @started_from
|
647
|
+
y_diff = y - old_y
|
648
|
+
if (old_y + old_dialog_vertical_offset) < (y + dialog.vertical_offset)
|
649
|
+
# rerender top
|
650
|
+
move_cursor_down(old_dialog_vertical_offset - y_diff)
|
651
|
+
start = visual_start + old_dialog_vertical_offset
|
652
|
+
line_num = dialog.vertical_offset - old_dialog_vertical_offset
|
653
|
+
line_num.times do |i|
|
654
|
+
Reline::IOGate.move_cursor_column(old_dialog_column)
|
655
|
+
if visual_lines[start + i].nil?
|
656
|
+
s = ' ' * DIALOG_WIDTH
|
657
|
+
else
|
658
|
+
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog_column, DIALOG_WIDTH)
|
659
|
+
end
|
660
|
+
@output.write "\e[39m\e[49m%-#{DIALOG_WIDTH}s\e[39m\e[49m" % s
|
661
|
+
move_cursor_down(1) if i < (line_num - 1)
|
662
|
+
end
|
663
|
+
move_cursor_up(old_dialog_vertical_offset + line_num - 1 - y_diff)
|
664
|
+
end
|
665
|
+
if (old_y + old_dialog_vertical_offset + old_dialog_contents.size) > (y + dialog.vertical_offset + dialog.contents.size)
|
666
|
+
# rerender bottom
|
667
|
+
move_cursor_down(dialog.vertical_offset + dialog.contents.size - y_diff)
|
668
|
+
start = visual_start + dialog.vertical_offset + dialog.contents.size
|
669
|
+
line_num = (old_dialog_vertical_offset + old_dialog_contents.size) - (dialog.vertical_offset + dialog.contents.size)
|
670
|
+
line_num.times do |i|
|
671
|
+
Reline::IOGate.move_cursor_column(old_dialog_column)
|
672
|
+
if visual_lines[start + i].nil?
|
673
|
+
s = ' ' * DIALOG_WIDTH
|
674
|
+
else
|
675
|
+
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog_column, DIALOG_WIDTH)
|
676
|
+
end
|
677
|
+
@output.write "\e[39m\e[49m%-#{DIALOG_WIDTH}s\e[39m\e[49m" % s
|
678
|
+
move_cursor_down(1) if i < (line_num - 1)
|
679
|
+
end
|
680
|
+
move_cursor_up(dialog.vertical_offset + dialog.contents.size + line_num - 1 - y_diff)
|
681
|
+
end
|
682
|
+
if old_dialog_column < dialog.column
|
683
|
+
# rerender left
|
684
|
+
move_cursor_down(old_dialog_vertical_offset - y_diff)
|
685
|
+
width = dialog.column - old_dialog_column
|
686
|
+
start = visual_start + old_dialog_vertical_offset
|
687
|
+
line_num = old_dialog_contents.size
|
688
|
+
line_num.times do |i|
|
689
|
+
Reline::IOGate.move_cursor_column(old_dialog_column)
|
690
|
+
if visual_lines[start + i].nil?
|
691
|
+
s = ' ' * width
|
692
|
+
else
|
693
|
+
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog_column, width)
|
694
|
+
end
|
695
|
+
@output.write "\e[39m\e[49m%-#{width}s\e[39m\e[49m" % s
|
696
|
+
move_cursor_down(1) if i < (line_num - 1)
|
697
|
+
end
|
698
|
+
move_cursor_up(old_dialog_vertical_offset + line_num - 1 - y_diff)
|
699
|
+
end
|
700
|
+
if (old_dialog_column + DIALOG_WIDTH) > (dialog.column + DIALOG_WIDTH)
|
701
|
+
# rerender right
|
702
|
+
move_cursor_down(old_dialog_vertical_offset + y_diff)
|
703
|
+
width = (old_dialog_column + DIALOG_WIDTH) - (dialog.column + DIALOG_WIDTH)
|
704
|
+
start = visual_start + old_dialog_vertical_offset
|
705
|
+
line_num = old_dialog_contents.size
|
706
|
+
line_num.times do |i|
|
707
|
+
Reline::IOGate.move_cursor_column(old_dialog_column + DIALOG_WIDTH)
|
708
|
+
if visual_lines[start + i].nil?
|
709
|
+
s = ' ' * width
|
710
|
+
else
|
711
|
+
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog_column + DIALOG_WIDTH, width)
|
712
|
+
end
|
713
|
+
Reline::IOGate.move_cursor_column(dialog.column + DIALOG_WIDTH)
|
714
|
+
@output.write "\e[39m\e[49m%-#{width}s\e[39m\e[49m" % s
|
715
|
+
move_cursor_down(1) if i < (line_num - 1)
|
716
|
+
end
|
717
|
+
move_cursor_up(old_dialog_vertical_offset + line_num - 1 + y_diff)
|
718
|
+
end
|
719
|
+
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
720
|
+
end
|
721
|
+
|
722
|
+
private def clear_dialog
|
723
|
+
@dialogs.each do |dialog|
|
724
|
+
clear_each_dialog(dialog)
|
725
|
+
end
|
726
|
+
end
|
727
|
+
|
728
|
+
private def clear_each_dialog(dialog)
|
729
|
+
return unless dialog.contents
|
730
|
+
prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines], prompt)
|
731
|
+
visual_lines = []
|
732
|
+
visual_lines_under_dialog = []
|
733
|
+
visual_start = nil
|
734
|
+
dialog.lines_backup[:lines].each_with_index { |l, i|
|
735
|
+
pr = prompt_list ? prompt_list[i] : prompt
|
736
|
+
vl, _ = split_by_width(pr + l, @screen_size.last)
|
737
|
+
vl.compact!
|
738
|
+
if i == dialog.lines_backup[:line_index]
|
739
|
+
visual_start = visual_lines.size + dialog.lines_backup[:started_from] + dialog.vertical_offset
|
740
|
+
end
|
741
|
+
visual_lines.concat(vl)
|
742
|
+
}
|
743
|
+
visual_lines_under_dialog = visual_lines[visual_start, dialog.contents.size]
|
744
|
+
visual_lines_under_dialog = [] if visual_lines_under_dialog.nil?
|
745
|
+
Reline::IOGate.hide_cursor
|
746
|
+
move_cursor_down(dialog.vertical_offset)
|
747
|
+
dialog_vertical_size = dialog.contents.size
|
748
|
+
dialog_vertical_size.times do |i|
|
749
|
+
if i < visual_lines_under_dialog.size
|
750
|
+
Reline::IOGate.move_cursor_column(0)
|
751
|
+
@output.write "\e[39m\e[49m%-#{DIALOG_WIDTH}s\e[39m\e[49m" % visual_lines_under_dialog[i]
|
752
|
+
else
|
753
|
+
Reline::IOGate.move_cursor_column(dialog.column)
|
754
|
+
@output.write "\e[39m\e[49m#{' ' * DIALOG_WIDTH}\e[39m\e[49m"
|
755
|
+
end
|
756
|
+
Reline::IOGate.erase_after_cursor
|
757
|
+
move_cursor_down(1) if i < (dialog_vertical_size - 1)
|
758
|
+
end
|
759
|
+
move_cursor_up(dialog_vertical_size - 1 + dialog.vertical_offset)
|
760
|
+
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
761
|
+
Reline::IOGate.show_cursor
|
762
|
+
end
|
763
|
+
|
468
764
|
private def calculate_scroll_partial_screen(highest_in_all, cursor_y)
|
469
765
|
if @screen_height < highest_in_all
|
470
766
|
old_scroll_partial_screen = @scroll_partial_screen
|
@@ -678,7 +974,6 @@ class Reline::LineEditor
|
|
678
974
|
private def render_partial(prompt, prompt_width, line_to_render, this_started_from, with_control: true)
|
679
975
|
visual_lines, height = split_by_width(line_to_render.nil? ? prompt : prompt + line_to_render, @screen_size.last)
|
680
976
|
cursor_up_from_last_line = 0
|
681
|
-
# TODO: This logic would be sometimes buggy if this logical line isn't the current @line_index.
|
682
977
|
if @scroll_partial_screen
|
683
978
|
last_visual_line = this_started_from + (height - 1)
|
684
979
|
last_screen_line = @scroll_partial_screen + (@screen_height - 1)
|
@@ -813,6 +1108,7 @@ class Reline::LineEditor
|
|
813
1108
|
end
|
814
1109
|
move_cursor_up(back)
|
815
1110
|
move_cursor_down(@first_line_started_from + @started_from)
|
1111
|
+
@rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
|
816
1112
|
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
817
1113
|
end
|
818
1114
|
|
@@ -925,6 +1221,16 @@ class Reline::LineEditor
|
|
925
1221
|
@completion_journey_data = CompletionJourneyData.new(
|
926
1222
|
preposing, postposing,
|
927
1223
|
[target] + list.select{ |item| item.start_with?(target) }, 0)
|
1224
|
+
if @completion_journey_data.list.size == 1
|
1225
|
+
@completion_journey_data.pointer = 0
|
1226
|
+
else
|
1227
|
+
case direction
|
1228
|
+
when :up
|
1229
|
+
@completion_journey_data.pointer = @completion_journey_data.list.size - 1
|
1230
|
+
when :down
|
1231
|
+
@completion_journey_data.pointer = 1
|
1232
|
+
end
|
1233
|
+
end
|
928
1234
|
@completion_state = CompletionState::JOURNEY
|
929
1235
|
else
|
930
1236
|
case direction
|
@@ -939,13 +1245,13 @@ class Reline::LineEditor
|
|
939
1245
|
@completion_journey_data.pointer = 0
|
940
1246
|
end
|
941
1247
|
end
|
942
|
-
completed = @completion_journey_data.list[@completion_journey_data.pointer]
|
943
|
-
@line = @completion_journey_data.preposing + completed + @completion_journey_data.postposing
|
944
|
-
line_to_pointer = @completion_journey_data.preposing + completed
|
945
|
-
@cursor_max = calculate_width(@line)
|
946
|
-
@cursor = calculate_width(line_to_pointer)
|
947
|
-
@byte_pointer = line_to_pointer.bytesize
|
948
1248
|
end
|
1249
|
+
completed = @completion_journey_data.list[@completion_journey_data.pointer]
|
1250
|
+
@line = @completion_journey_data.preposing + completed + @completion_journey_data.postposing
|
1251
|
+
line_to_pointer = @completion_journey_data.preposing + completed
|
1252
|
+
@cursor_max = calculate_width(@line)
|
1253
|
+
@cursor = calculate_width(line_to_pointer)
|
1254
|
+
@byte_pointer = line_to_pointer.bytesize
|
949
1255
|
end
|
950
1256
|
|
951
1257
|
private def run_for_operators(key, method_symbol, &block)
|
@@ -1139,6 +1445,7 @@ class Reline::LineEditor
|
|
1139
1445
|
end
|
1140
1446
|
unless completion_occurs
|
1141
1447
|
@completion_state = CompletionState::NORMAL
|
1448
|
+
@completion_journey_data = nil
|
1142
1449
|
end
|
1143
1450
|
if not @in_pasting and @just_cursor_moving.nil?
|
1144
1451
|
if @previous_line_index and @buffer_of_lines[@previous_line_index] == @line
|
@@ -1158,12 +1465,34 @@ class Reline::LineEditor
|
|
1158
1465
|
|
1159
1466
|
def call_completion_proc
|
1160
1467
|
result = retrieve_completion_block(true)
|
1161
|
-
|
1162
|
-
result =
|
1468
|
+
pre, target, post = result
|
1469
|
+
result = call_completion_proc_with_checking_args(pre, target, post)
|
1163
1470
|
Reline.core.instance_variable_set(:@completion_quote_character, nil)
|
1164
1471
|
result
|
1165
1472
|
end
|
1166
1473
|
|
1474
|
+
def call_completion_proc_with_checking_args(pre, target, post)
|
1475
|
+
if @completion_proc and target
|
1476
|
+
argnum = @completion_proc.parameters.inject(0) { |result, item|
|
1477
|
+
case item.first
|
1478
|
+
when :req, :opt
|
1479
|
+
result + 1
|
1480
|
+
when :rest
|
1481
|
+
break 3
|
1482
|
+
end
|
1483
|
+
}
|
1484
|
+
case argnum
|
1485
|
+
when 1
|
1486
|
+
result = @completion_proc.(target)
|
1487
|
+
when 2
|
1488
|
+
result = @completion_proc.(target, pre)
|
1489
|
+
when 3..Float::INFINITY
|
1490
|
+
result = @completion_proc.(target, pre, post)
|
1491
|
+
end
|
1492
|
+
end
|
1493
|
+
result
|
1494
|
+
end
|
1495
|
+
|
1167
1496
|
private def process_auto_indent
|
1168
1497
|
return if not @check_new_auto_indent and @previous_line_index # move cursor up or down
|
1169
1498
|
if @check_new_auto_indent and @previous_line_index and @previous_line_index > 0 and @line_index > @previous_line_index
|
@@ -1207,8 +1536,16 @@ class Reline::LineEditor
|
|
1207
1536
|
end
|
1208
1537
|
|
1209
1538
|
def retrieve_completion_block(set_completion_quote_character = false)
|
1210
|
-
|
1211
|
-
|
1539
|
+
if Reline.completer_word_break_characters.empty?
|
1540
|
+
word_break_regexp = nil
|
1541
|
+
else
|
1542
|
+
word_break_regexp = /\A[#{Regexp.escape(Reline.completer_word_break_characters)}]/
|
1543
|
+
end
|
1544
|
+
if Reline.completer_quote_characters.empty?
|
1545
|
+
quote_characters_regexp = nil
|
1546
|
+
else
|
1547
|
+
quote_characters_regexp = /\A[#{Regexp.escape(Reline.completer_quote_characters)}]/
|
1548
|
+
end
|
1212
1549
|
before = @line.byteslice(0, @byte_pointer)
|
1213
1550
|
rest = nil
|
1214
1551
|
break_pointer = nil
|
@@ -1229,14 +1566,14 @@ class Reline::LineEditor
|
|
1229
1566
|
elsif quote and slice.start_with?(escaped_quote)
|
1230
1567
|
# skip
|
1231
1568
|
i += 2
|
1232
|
-
elsif slice =~ quote_characters_regexp # find new "
|
1569
|
+
elsif quote_characters_regexp and slice =~ quote_characters_regexp # find new "
|
1233
1570
|
rest = $'
|
1234
1571
|
quote = $&
|
1235
1572
|
closing_quote = /(?!\\)#{Regexp.escape(quote)}/
|
1236
1573
|
escaped_quote = /\\#{Regexp.escape(quote)}/
|
1237
1574
|
i += 1
|
1238
1575
|
break_pointer = i - 1
|
1239
|
-
elsif not quote and slice =~ word_break_regexp
|
1576
|
+
elsif word_break_regexp and not quote and slice =~ word_break_regexp
|
1240
1577
|
rest = $'
|
1241
1578
|
i += 1
|
1242
1579
|
before = @line.byteslice(i, @byte_pointer - i)
|
@@ -1264,6 +1601,19 @@ class Reline::LineEditor
|
|
1264
1601
|
end
|
1265
1602
|
target = before
|
1266
1603
|
end
|
1604
|
+
if @is_multiline
|
1605
|
+
if @previous_line_index
|
1606
|
+
lines = whole_lines(index: @previous_line_index, line: @line)
|
1607
|
+
else
|
1608
|
+
lines = whole_lines
|
1609
|
+
end
|
1610
|
+
if @line_index > 0
|
1611
|
+
preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing
|
1612
|
+
end
|
1613
|
+
if (lines.size - 1) > @line_index
|
1614
|
+
postposing = postposing + "\n" + lines[(@line_index + 1)..-1].join("\n")
|
1615
|
+
end
|
1616
|
+
end
|
1267
1617
|
[preposing.encode(@encoding), target.encode(@encoding), postposing.encode(@encoding)]
|
1268
1618
|
end
|
1269
1619
|
|
@@ -1291,10 +1641,32 @@ class Reline::LineEditor
|
|
1291
1641
|
|
1292
1642
|
def delete_text(start = nil, length = nil)
|
1293
1643
|
if start.nil? and length.nil?
|
1294
|
-
@
|
1295
|
-
|
1296
|
-
|
1297
|
-
|
1644
|
+
if @is_multiline
|
1645
|
+
if @buffer_of_lines.size == 1
|
1646
|
+
@line&.clear
|
1647
|
+
@byte_pointer = 0
|
1648
|
+
@cursor = 0
|
1649
|
+
@cursor_max = 0
|
1650
|
+
elsif @line_index == (@buffer_of_lines.size - 1) and @line_index > 0
|
1651
|
+
@buffer_of_lines.pop
|
1652
|
+
@line_index -= 1
|
1653
|
+
@line = @buffer_of_lines[@line_index]
|
1654
|
+
@byte_pointer = 0
|
1655
|
+
@cursor = 0
|
1656
|
+
@cursor_max = calculate_width(@line)
|
1657
|
+
elsif @line_index < (@buffer_of_lines.size - 1)
|
1658
|
+
@buffer_of_lines.delete_at(@line_index)
|
1659
|
+
@line = @buffer_of_lines[@line_index]
|
1660
|
+
@byte_pointer = 0
|
1661
|
+
@cursor = 0
|
1662
|
+
@cursor_max = calculate_width(@line)
|
1663
|
+
end
|
1664
|
+
else
|
1665
|
+
@line&.clear
|
1666
|
+
@byte_pointer = 0
|
1667
|
+
@cursor = 0
|
1668
|
+
@cursor_max = 0
|
1669
|
+
end
|
1298
1670
|
elsif not start.nil? and not length.nil?
|
1299
1671
|
if @line
|
1300
1672
|
before = @line.byteslice(0, start)
|