reline 0.2.5 → 0.2.8.pre.2
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 +120 -56
- data/lib/reline/config.rb +39 -13
- data/lib/reline/general_io.rb +11 -3
- data/lib/reline/key_actor/base.rb +12 -0
- data/lib/reline/line_editor.rb +347 -18
- 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 +176 -80
- data/lib/reline.rb +83 -13
- metadata +6 -61
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 45220c00c821168746654f6c7f5ad3cc88ff8b4e3d3b6e5aefccc83b34392041
|
4
|
+
data.tar.gz: a3bbf71304ce777dbae4d5b1cc91f5a2b297b811194947e5e8e8a27b229667a2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a9d8e4a15c1eff337dc476b8b4f2d603b64d35faf255a9984f119b2f14a76bcd93f145d01d1e0698e2cb0a3d80c28c8b3baa343866e3cb7b8b28ab4642e77ebf
|
7
|
+
data.tar.gz: fcc70f912f7472acb0d7eeee8f221a60d06e3f08cd079b37a988392eca35723753e3b02638a4bcbb41876dc59cfcb46349b16bfff88e336ecea9bd2a4cf93231
|
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,100 @@ 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
|
+
[27, 91, 90] => :completion_journey_up, # S-Tab
|
41
|
+
}.each_pair do |key, func|
|
42
|
+
config.add_default_key_binding_by_keymap(:emacs, key, func)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.set_default_key_bindings_terminfo(config)
|
47
|
+
{
|
48
|
+
Reline::Terminfo.tigetstr('khome').bytes => :ed_move_to_beg,
|
49
|
+
Reline::Terminfo.tigetstr('kend').bytes => :ed_move_to_end,
|
50
|
+
Reline::Terminfo.tigetstr('kcuu1').bytes => :ed_prev_history,
|
51
|
+
Reline::Terminfo.tigetstr('kcud1').bytes => :ed_next_history,
|
52
|
+
Reline::Terminfo.tigetstr('kcuf1').bytes => :ed_next_char,
|
53
|
+
Reline::Terminfo.tigetstr('kcub1').bytes => :ed_prev_char,
|
54
|
+
# Escape sequences that omit the move distance and are set to defaults
|
55
|
+
# value 1 may be sometimes sent by pressing the arrow-key.
|
56
|
+
Reline::Terminfo.tigetstr('cuu').sub(/%p1%d/, '').bytes => :ed_prev_history,
|
57
|
+
Reline::Terminfo.tigetstr('cud').sub(/%p1%d/, '').bytes => :ed_next_history,
|
58
|
+
Reline::Terminfo.tigetstr('cuf').sub(/%p1%d/, '').bytes => :ed_next_char,
|
59
|
+
Reline::Terminfo.tigetstr('cub').sub(/%p1%d/, '').bytes => :ed_prev_char,
|
60
|
+
}.each_pair do |key, func|
|
61
|
+
config.add_default_key_binding_by_keymap(:emacs, key, func)
|
62
|
+
config.add_default_key_binding_by_keymap(:vi_insert, key, func)
|
63
|
+
config.add_default_key_binding_by_keymap(:vi_command, key, func)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.set_default_key_bindings_comprehensive_list(config)
|
68
|
+
{
|
69
|
+
# Console (80x25)
|
70
|
+
[27, 91, 49, 126] => :ed_move_to_beg, # Home
|
71
|
+
[27, 91, 52, 126] => :ed_move_to_end, # End
|
72
|
+
[27, 91, 51, 126] => :key_delete, # Del
|
73
|
+
[27, 91, 65] => :ed_prev_history, # ↑
|
74
|
+
[27, 91, 66] => :ed_next_history, # ↓
|
75
|
+
[27, 91, 67] => :ed_next_char, # →
|
76
|
+
[27, 91, 68] => :ed_prev_char, # ←
|
77
|
+
|
78
|
+
# KDE
|
79
|
+
[27, 91, 72] => :ed_move_to_beg, # Home
|
80
|
+
[27, 91, 70] => :ed_move_to_end, # End
|
81
|
+
# Del is 0x08
|
82
|
+
[27, 71, 65] => :ed_prev_history, # ↑
|
83
|
+
[27, 71, 66] => :ed_next_history, # ↓
|
84
|
+
[27, 71, 67] => :ed_next_char, # →
|
85
|
+
[27, 71, 68] => :ed_prev_char, # ←
|
86
|
+
|
87
|
+
# urxvt / exoterm
|
88
|
+
[27, 91, 55, 126] => :ed_move_to_beg, # Home
|
89
|
+
[27, 91, 56, 126] => :ed_move_to_end, # End
|
90
|
+
|
91
|
+
# GNOME
|
92
|
+
[27, 79, 72] => :ed_move_to_beg, # Home
|
93
|
+
[27, 79, 70] => :ed_move_to_end, # End
|
94
|
+
# Del is 0x08
|
95
|
+
# Arrow keys are the same of KDE
|
96
|
+
|
97
|
+
# iTerm2
|
98
|
+
[27, 27, 91, 67] => :em_next_word, # Option+→, extended entry
|
99
|
+
[27, 27, 91, 68] => :ed_prev_word, # Option+←, extended entry
|
100
|
+
[195, 166] => :em_next_word, # Option+f
|
101
|
+
[195, 162] => :ed_prev_word, # Option+b
|
102
|
+
|
103
|
+
[27, 79, 65] => :ed_prev_history, # ↑
|
104
|
+
[27, 79, 66] => :ed_next_history, # ↓
|
105
|
+
[27, 79, 67] => :ed_next_char, # →
|
106
|
+
[27, 79, 68] => :ed_prev_char, # ←
|
107
|
+
}.each_pair do |key, func|
|
108
|
+
config.add_default_key_binding_by_keymap(:emacs, key, func)
|
109
|
+
config.add_default_key_binding_by_keymap(:vi_insert, key, func)
|
110
|
+
config.add_default_key_binding_by_keymap(:vi_command, key, func)
|
111
|
+
end
|
112
|
+
end
|
59
113
|
|
60
114
|
@@input = STDIN
|
61
115
|
def self.input=(val)
|
@@ -79,6 +133,8 @@ class Reline::ANSI
|
|
79
133
|
rescue Errno::EIO
|
80
134
|
# Maybe the I/O has been closed.
|
81
135
|
nil
|
136
|
+
rescue Errno::ENOTTY
|
137
|
+
nil
|
82
138
|
end
|
83
139
|
|
84
140
|
@@in_bracketed_paste_mode = false
|
@@ -129,12 +185,7 @@ class Reline::ANSI
|
|
129
185
|
unless @@buf.empty?
|
130
186
|
return false
|
131
187
|
end
|
132
|
-
|
133
|
-
if rs and rs[0]
|
134
|
-
false
|
135
|
-
else
|
136
|
-
true
|
137
|
-
end
|
188
|
+
!@@input.wait_readable(0)
|
138
189
|
end
|
139
190
|
|
140
191
|
def self.ungetc(c)
|
@@ -143,8 +194,7 @@ class Reline::ANSI
|
|
143
194
|
|
144
195
|
def self.retrieve_keybuffer
|
145
196
|
begin
|
146
|
-
|
147
|
-
return if result.nil?
|
197
|
+
return unless @@input.wait_readable(0.001)
|
148
198
|
str = @@input.read_nonblock(1024)
|
149
199
|
str.bytes.each do |c|
|
150
200
|
@@buf.push(c)
|
@@ -225,6 +275,22 @@ class Reline::ANSI
|
|
225
275
|
end
|
226
276
|
end
|
227
277
|
|
278
|
+
def self.hide_cursor
|
279
|
+
if Reline::Terminfo.enabled?
|
280
|
+
@@output.write Reline::Terminfo.tigetstr('civis')
|
281
|
+
else
|
282
|
+
# ignored
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
def self.show_cursor
|
287
|
+
if Reline::Terminfo.enabled?
|
288
|
+
@@output.write Reline::Terminfo.tigetstr('cnorm')
|
289
|
+
else
|
290
|
+
# ignored
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
228
294
|
def self.erase_after_cursor
|
229
295
|
@@output.write "\e[K"
|
230
296
|
end
|
@@ -246,8 +312,6 @@ class Reline::ANSI
|
|
246
312
|
|
247
313
|
def self.prep
|
248
314
|
retrieve_keybuffer
|
249
|
-
int_handle = Signal.trap('INT', 'IGNORE')
|
250
|
-
Signal.trap('INT', int_handle)
|
251
315
|
nil
|
252
316
|
end
|
253
317
|
|
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
|
@@ -63,14 +65,17 @@ class Reline::Config
|
|
63
65
|
@history_size = -1 # unlimited
|
64
66
|
@keyseq_timeout = 500
|
65
67
|
@test_mode = false
|
68
|
+
@autocompletion = false
|
66
69
|
end
|
67
70
|
|
68
71
|
def reset
|
69
72
|
if editing_mode_is?(:vi_command)
|
70
73
|
@editing_mode_label = :vi_insert
|
71
74
|
end
|
72
|
-
@additional_key_bindings
|
73
|
-
|
75
|
+
@additional_key_bindings.keys.each do |key|
|
76
|
+
@additional_key_bindings[key].clear
|
77
|
+
end
|
78
|
+
reset_default_key_bindings
|
74
79
|
end
|
75
80
|
|
76
81
|
def editing_mode
|
@@ -85,6 +90,14 @@ class Reline::Config
|
|
85
90
|
(val.respond_to?(:any?) ? val : [val]).any?(@editing_mode_label)
|
86
91
|
end
|
87
92
|
|
93
|
+
def autocompletion=(val)
|
94
|
+
@autocompletion = val
|
95
|
+
end
|
96
|
+
|
97
|
+
def autocompletion
|
98
|
+
@autocompletion
|
99
|
+
end
|
100
|
+
|
88
101
|
def keymap
|
89
102
|
@key_actors[@keymap_label]
|
90
103
|
end
|
@@ -135,19 +148,35 @@ class Reline::Config
|
|
135
148
|
end
|
136
149
|
|
137
150
|
def key_bindings
|
138
|
-
# override @default_key_bindings with @additional_key_bindings
|
139
|
-
@default_key_bindings.merge(@additional_key_bindings)
|
151
|
+
# override @key_actors[@editing_mode_label].default_key_bindings with @additional_key_bindings[@editing_mode_label]
|
152
|
+
@key_actors[@editing_mode_label].default_key_bindings.merge(@additional_key_bindings[@editing_mode_label])
|
153
|
+
end
|
154
|
+
|
155
|
+
def add_default_key_binding_by_keymap(keymap, keystroke, target)
|
156
|
+
@key_actors[keymap].default_key_bindings[keystroke] = target
|
140
157
|
end
|
141
158
|
|
142
159
|
def add_default_key_binding(keystroke, target)
|
143
|
-
@default_key_bindings[keystroke] = target
|
160
|
+
@key_actors[@keymap_label].default_key_bindings[keystroke] = target
|
144
161
|
end
|
145
162
|
|
146
163
|
def reset_default_key_bindings
|
147
|
-
@
|
164
|
+
@key_actors.values.each do |ka|
|
165
|
+
ka.reset_default_key_bindings
|
166
|
+
end
|
148
167
|
end
|
149
168
|
|
150
169
|
def read_lines(lines, file = nil)
|
170
|
+
if not lines.empty? and lines.first.encoding != Reline.encoding_system_needs
|
171
|
+
begin
|
172
|
+
lines = lines.map do |l|
|
173
|
+
l.encode(Reline.encoding_system_needs)
|
174
|
+
rescue Encoding::UndefinedConversionError
|
175
|
+
mes = "The inputrc encoded in #{lines.first.encoding.name} can't be converted to the locale #{Reline.encoding_system_needs.name}."
|
176
|
+
raise Reline::ConfigEncodingConversionError.new(mes)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
151
180
|
conditions = [@skip_section, @if_stack]
|
152
181
|
@skip_section = nil
|
153
182
|
@if_stack = []
|
@@ -174,7 +203,7 @@ class Reline::Config
|
|
174
203
|
key, func_name = $1, $2
|
175
204
|
keystroke, func = bind_key(key, func_name)
|
176
205
|
next unless keystroke
|
177
|
-
@additional_key_bindings[keystroke] = func
|
206
|
+
@additional_key_bindings[@keymap_label][keystroke] = func
|
178
207
|
end
|
179
208
|
end
|
180
209
|
unless @if_stack.empty?
|
@@ -282,11 +311,8 @@ class Reline::Config
|
|
282
311
|
end
|
283
312
|
|
284
313
|
def retrieve_string(str)
|
285
|
-
if str =~ /\A"(.*)"\z/
|
286
|
-
|
287
|
-
else
|
288
|
-
parse_keyseq(str).map(&:chr).join
|
289
|
-
end
|
314
|
+
str = $1 if str =~ /\A"(.*)"\z/
|
315
|
+
parse_keyseq(str).map { |c| c.chr(Reline.encoding_system_needs) }.join
|
290
316
|
end
|
291
317
|
|
292
318
|
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,294 @@ class Reline::LineEditor
|
|
465
478
|
end
|
466
479
|
end
|
467
480
|
|
481
|
+
class DialogProcScope
|
482
|
+
def initialize(line_editor, config, proc_to_exec, context)
|
483
|
+
@line_editor = line_editor
|
484
|
+
@config = config
|
485
|
+
@proc_to_exec = proc_to_exec
|
486
|
+
@context = context
|
487
|
+
@cursor_pos = Reline::CursorPos.new
|
488
|
+
end
|
489
|
+
|
490
|
+
def context
|
491
|
+
@context
|
492
|
+
end
|
493
|
+
|
494
|
+
def retrieve_completion_block(set_completion_quote_character = false)
|
495
|
+
@line_editor.retrieve_completion_block(set_completion_quote_character)
|
496
|
+
end
|
497
|
+
|
498
|
+
def call_completion_proc_with_checking_args(pre, target, post)
|
499
|
+
@line_editor.call_completion_proc_with_checking_args(pre, target, post)
|
500
|
+
end
|
501
|
+
|
502
|
+
def set_cursor_pos(col, row)
|
503
|
+
@cursor_pos.x = col
|
504
|
+
@cursor_pos.y = row
|
505
|
+
end
|
506
|
+
|
507
|
+
def cursor_pos
|
508
|
+
@cursor_pos
|
509
|
+
end
|
510
|
+
|
511
|
+
def just_cursor_moving
|
512
|
+
@line_editor.instance_variable_get(:@just_cursor_moving)
|
513
|
+
end
|
514
|
+
|
515
|
+
def screen_width
|
516
|
+
@line_editor.instance_variable_get(:@screen_size).last
|
517
|
+
end
|
518
|
+
|
519
|
+
def completion_journey_data
|
520
|
+
@line_editor.instance_variable_get(:@completion_journey_data)
|
521
|
+
end
|
522
|
+
|
523
|
+
def config
|
524
|
+
@config
|
525
|
+
end
|
526
|
+
|
527
|
+
def call
|
528
|
+
instance_exec(&@proc_to_exec)
|
529
|
+
end
|
530
|
+
end
|
531
|
+
|
532
|
+
class Dialog
|
533
|
+
attr_reader :name
|
534
|
+
attr_accessor :column, :vertical_offset, :contents, :lines_backup
|
535
|
+
|
536
|
+
def initialize(name, proc_scope)
|
537
|
+
@name = name
|
538
|
+
@proc_scope = proc_scope
|
539
|
+
end
|
540
|
+
|
541
|
+
def set_cursor_pos(col, row)
|
542
|
+
@proc_scope.set_cursor_pos(col, row)
|
543
|
+
end
|
544
|
+
|
545
|
+
def call
|
546
|
+
@proc_scope.call
|
547
|
+
end
|
548
|
+
end
|
549
|
+
|
550
|
+
def add_dialog_proc(name, p, context = nil)
|
551
|
+
return if @dialogs.any? { |d| d.name == name }
|
552
|
+
@dialogs << Dialog.new(name, DialogProcScope.new(self, @config, p, context))
|
553
|
+
end
|
554
|
+
|
555
|
+
DIALOG_HEIGHT = 20
|
556
|
+
DIALOG_WIDTH = 40
|
557
|
+
private def render_dialog(cursor_column)
|
558
|
+
@dialogs.each do |dialog|
|
559
|
+
render_each_dialog(dialog, cursor_column)
|
560
|
+
end
|
561
|
+
end
|
562
|
+
|
563
|
+
private def render_each_dialog(dialog, cursor_column)
|
564
|
+
if @in_pasting
|
565
|
+
dialog.contents = nil
|
566
|
+
return
|
567
|
+
end
|
568
|
+
dialog.set_cursor_pos(cursor_column, @first_line_started_from + @started_from)
|
569
|
+
pos, result, pointer, bg = dialog.call
|
570
|
+
old_dialog_contents = dialog.contents
|
571
|
+
old_dialog_column = dialog.column
|
572
|
+
old_dialog_vertical_offset = dialog.vertical_offset
|
573
|
+
if result and not result.empty?
|
574
|
+
dialog.contents = result
|
575
|
+
dialog.contents = dialog.contents[0...DIALOG_HEIGHT] if dialog.contents.size > DIALOG_HEIGHT
|
576
|
+
else
|
577
|
+
dialog.lines_backup = {
|
578
|
+
lines: modify_lines(whole_lines),
|
579
|
+
line_index: @line_index,
|
580
|
+
first_line_started_from: @first_line_started_from,
|
581
|
+
started_from: @started_from,
|
582
|
+
byte_pointer: @byte_pointer
|
583
|
+
}
|
584
|
+
clear_each_dialog(dialog)
|
585
|
+
dialog.contents = nil
|
586
|
+
return
|
587
|
+
end
|
588
|
+
upper_space = @first_line_started_from - @started_from
|
589
|
+
lower_space = @highest_in_all - @first_line_started_from - @started_from - 1
|
590
|
+
dialog.column = pos.x
|
591
|
+
diff = (dialog.column + DIALOG_WIDTH) - (@screen_size.last - 1)
|
592
|
+
if diff > 0
|
593
|
+
dialog.column -= diff
|
594
|
+
end
|
595
|
+
if (lower_space + @rest_height) >= DIALOG_HEIGHT
|
596
|
+
dialog.vertical_offset = pos.y + 1
|
597
|
+
elsif upper_space >= DIALOG_HEIGHT
|
598
|
+
dialog.vertical_offset = pos.y + -(DIALOG_HEIGHT + 1)
|
599
|
+
else
|
600
|
+
if (lower_space + @rest_height) < DIALOG_HEIGHT
|
601
|
+
scroll_down(DIALOG_HEIGHT)
|
602
|
+
move_cursor_up(DIALOG_HEIGHT)
|
603
|
+
end
|
604
|
+
dialog.vertical_offset = pos.y + 1
|
605
|
+
end
|
606
|
+
Reline::IOGate.hide_cursor
|
607
|
+
reset_dialog(dialog, old_dialog_contents, old_dialog_column, old_dialog_vertical_offset)
|
608
|
+
move_cursor_down(dialog.vertical_offset)
|
609
|
+
Reline::IOGate.move_cursor_column(dialog.column)
|
610
|
+
dialog.contents.each_with_index do |item, i|
|
611
|
+
if i == pointer
|
612
|
+
bg_color = '45'
|
613
|
+
else
|
614
|
+
if bg
|
615
|
+
bg_color = bg
|
616
|
+
else
|
617
|
+
bg_color = '46'
|
618
|
+
end
|
619
|
+
end
|
620
|
+
@output.write "\e[#{bg_color}m%-#{DIALOG_WIDTH}s\e[49m" % item.slice(0, DIALOG_WIDTH)
|
621
|
+
Reline::IOGate.move_cursor_column(dialog.column)
|
622
|
+
move_cursor_down(1) if i < (dialog.contents.size - 1)
|
623
|
+
end
|
624
|
+
Reline::IOGate.move_cursor_column(cursor_column)
|
625
|
+
move_cursor_up(dialog.vertical_offset + dialog.contents.size - 1)
|
626
|
+
Reline::IOGate.show_cursor
|
627
|
+
dialog.lines_backup = {
|
628
|
+
lines: modify_lines(whole_lines),
|
629
|
+
line_index: @line_index,
|
630
|
+
first_line_started_from: @first_line_started_from,
|
631
|
+
started_from: @started_from,
|
632
|
+
byte_pointer: @byte_pointer
|
633
|
+
}
|
634
|
+
end
|
635
|
+
|
636
|
+
private def reset_dialog(dialog, old_dialog_contents, old_dialog_column, old_dialog_vertical_offset)
|
637
|
+
return if dialog.lines_backup.nil? or old_dialog_contents.nil?
|
638
|
+
prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines], prompt)
|
639
|
+
visual_lines = []
|
640
|
+
visual_start = nil
|
641
|
+
dialog.lines_backup[:lines].each_with_index { |l, i|
|
642
|
+
pr = prompt_list ? prompt_list[i] : prompt
|
643
|
+
vl, _ = split_by_width(pr + l, @screen_size.last)
|
644
|
+
vl.compact!
|
645
|
+
if i == dialog.lines_backup[:line_index]
|
646
|
+
visual_start = visual_lines.size + dialog.lines_backup[:started_from]
|
647
|
+
end
|
648
|
+
visual_lines.concat(vl)
|
649
|
+
}
|
650
|
+
old_y = dialog.lines_backup[:first_line_started_from] + dialog.lines_backup[:started_from]
|
651
|
+
y = @first_line_started_from + @started_from
|
652
|
+
y_diff = y - old_y
|
653
|
+
if (old_y + old_dialog_vertical_offset) < (y + dialog.vertical_offset)
|
654
|
+
# rerender top
|
655
|
+
move_cursor_down(old_dialog_vertical_offset - y_diff)
|
656
|
+
start = visual_start + old_dialog_vertical_offset
|
657
|
+
line_num = dialog.vertical_offset - old_dialog_vertical_offset
|
658
|
+
line_num.times do |i|
|
659
|
+
Reline::IOGate.move_cursor_column(old_dialog_column)
|
660
|
+
if visual_lines[start + i].nil?
|
661
|
+
s = ' ' * DIALOG_WIDTH
|
662
|
+
else
|
663
|
+
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog_column, DIALOG_WIDTH)
|
664
|
+
end
|
665
|
+
@output.write "\e[39m\e[49m%-#{DIALOG_WIDTH}s\e[39m\e[49m" % s
|
666
|
+
move_cursor_down(1) if i < (line_num - 1)
|
667
|
+
end
|
668
|
+
move_cursor_up(old_dialog_vertical_offset + line_num - 1 - y_diff)
|
669
|
+
end
|
670
|
+
if (old_y + old_dialog_vertical_offset + old_dialog_contents.size) > (y + dialog.vertical_offset + dialog.contents.size)
|
671
|
+
# rerender bottom
|
672
|
+
move_cursor_down(dialog.vertical_offset + dialog.contents.size - y_diff)
|
673
|
+
start = visual_start + dialog.vertical_offset + dialog.contents.size
|
674
|
+
line_num = (old_dialog_vertical_offset + old_dialog_contents.size) - (dialog.vertical_offset + dialog.contents.size)
|
675
|
+
line_num.times do |i|
|
676
|
+
Reline::IOGate.move_cursor_column(old_dialog_column)
|
677
|
+
if visual_lines[start + i].nil?
|
678
|
+
s = ' ' * DIALOG_WIDTH
|
679
|
+
else
|
680
|
+
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog_column, DIALOG_WIDTH)
|
681
|
+
end
|
682
|
+
@output.write "\e[39m\e[49m%-#{DIALOG_WIDTH}s\e[39m\e[49m" % s
|
683
|
+
move_cursor_down(1) if i < (line_num - 1)
|
684
|
+
end
|
685
|
+
move_cursor_up(dialog.vertical_offset + dialog.contents.size + line_num - 1 - y_diff)
|
686
|
+
end
|
687
|
+
if old_dialog_column < dialog.column
|
688
|
+
# rerender left
|
689
|
+
move_cursor_down(old_dialog_vertical_offset - y_diff)
|
690
|
+
width = dialog.column - old_dialog_column
|
691
|
+
start = visual_start + old_dialog_vertical_offset
|
692
|
+
line_num = old_dialog_contents.size
|
693
|
+
line_num.times do |i|
|
694
|
+
Reline::IOGate.move_cursor_column(old_dialog_column)
|
695
|
+
if visual_lines[start + i].nil?
|
696
|
+
s = ' ' * width
|
697
|
+
else
|
698
|
+
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog_column, width)
|
699
|
+
end
|
700
|
+
@output.write "\e[39m\e[49m%-#{width}s\e[39m\e[49m" % s
|
701
|
+
move_cursor_down(1) if i < (line_num - 1)
|
702
|
+
end
|
703
|
+
move_cursor_up(old_dialog_vertical_offset + line_num - 1 - y_diff)
|
704
|
+
end
|
705
|
+
if (old_dialog_column + DIALOG_WIDTH) > (dialog.column + DIALOG_WIDTH)
|
706
|
+
# rerender right
|
707
|
+
move_cursor_down(old_dialog_vertical_offset + y_diff)
|
708
|
+
width = (old_dialog_column + DIALOG_WIDTH) - (dialog.column + DIALOG_WIDTH)
|
709
|
+
start = visual_start + old_dialog_vertical_offset
|
710
|
+
line_num = old_dialog_contents.size
|
711
|
+
line_num.times do |i|
|
712
|
+
Reline::IOGate.move_cursor_column(old_dialog_column + DIALOG_WIDTH)
|
713
|
+
if visual_lines[start + i].nil?
|
714
|
+
s = ' ' * width
|
715
|
+
else
|
716
|
+
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog_column + DIALOG_WIDTH, width)
|
717
|
+
end
|
718
|
+
Reline::IOGate.move_cursor_column(dialog.column + DIALOG_WIDTH)
|
719
|
+
@output.write "\e[39m\e[49m%-#{width}s\e[39m\e[49m" % s
|
720
|
+
move_cursor_down(1) if i < (line_num - 1)
|
721
|
+
end
|
722
|
+
move_cursor_up(old_dialog_vertical_offset + line_num - 1 + y_diff)
|
723
|
+
end
|
724
|
+
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
725
|
+
end
|
726
|
+
|
727
|
+
private def clear_dialog
|
728
|
+
@dialogs.each do |dialog|
|
729
|
+
clear_each_dialog(dialog)
|
730
|
+
end
|
731
|
+
end
|
732
|
+
|
733
|
+
private def clear_each_dialog(dialog)
|
734
|
+
return unless dialog.contents
|
735
|
+
prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines], prompt)
|
736
|
+
visual_lines = []
|
737
|
+
visual_lines_under_dialog = []
|
738
|
+
visual_start = nil
|
739
|
+
dialog.lines_backup[:lines].each_with_index { |l, i|
|
740
|
+
pr = prompt_list ? prompt_list[i] : prompt
|
741
|
+
vl, _ = split_by_width(pr + l, @screen_size.last)
|
742
|
+
vl.compact!
|
743
|
+
if i == dialog.lines_backup[:line_index]
|
744
|
+
visual_start = visual_lines.size + dialog.lines_backup[:started_from] + dialog.vertical_offset
|
745
|
+
end
|
746
|
+
visual_lines.concat(vl)
|
747
|
+
}
|
748
|
+
visual_lines_under_dialog = visual_lines[visual_start, dialog.contents.size]
|
749
|
+
visual_lines_under_dialog = [] if visual_lines_under_dialog.nil?
|
750
|
+
Reline::IOGate.hide_cursor
|
751
|
+
move_cursor_down(dialog.vertical_offset)
|
752
|
+
dialog_vertical_size = dialog.contents.size
|
753
|
+
dialog_vertical_size.times do |i|
|
754
|
+
if i < visual_lines_under_dialog.size
|
755
|
+
Reline::IOGate.move_cursor_column(0)
|
756
|
+
@output.write "\e[39m\e[49m%-#{DIALOG_WIDTH}s\e[39m\e[49m" % visual_lines_under_dialog[i]
|
757
|
+
else
|
758
|
+
Reline::IOGate.move_cursor_column(dialog.column)
|
759
|
+
@output.write "\e[39m\e[49m#{' ' * DIALOG_WIDTH}\e[39m\e[49m"
|
760
|
+
end
|
761
|
+
Reline::IOGate.erase_after_cursor
|
762
|
+
move_cursor_down(1) if i < (dialog_vertical_size - 1)
|
763
|
+
end
|
764
|
+
move_cursor_up(dialog_vertical_size - 1 + dialog.vertical_offset)
|
765
|
+
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
766
|
+
Reline::IOGate.show_cursor
|
767
|
+
end
|
768
|
+
|
468
769
|
private def calculate_scroll_partial_screen(highest_in_all, cursor_y)
|
469
770
|
if @screen_height < highest_in_all
|
470
771
|
old_scroll_partial_screen = @scroll_partial_screen
|
@@ -678,7 +979,6 @@ class Reline::LineEditor
|
|
678
979
|
private def render_partial(prompt, prompt_width, line_to_render, this_started_from, with_control: true)
|
679
980
|
visual_lines, height = split_by_width(line_to_render.nil? ? prompt : prompt + line_to_render, @screen_size.last)
|
680
981
|
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
982
|
if @scroll_partial_screen
|
683
983
|
last_visual_line = this_started_from + (height - 1)
|
684
984
|
last_screen_line = @scroll_partial_screen + (@screen_height - 1)
|
@@ -926,6 +1226,16 @@ class Reline::LineEditor
|
|
926
1226
|
@completion_journey_data = CompletionJourneyData.new(
|
927
1227
|
preposing, postposing,
|
928
1228
|
[target] + list.select{ |item| item.start_with?(target) }, 0)
|
1229
|
+
if @completion_journey_data.list.size == 1
|
1230
|
+
@completion_journey_data.pointer = 0
|
1231
|
+
else
|
1232
|
+
case direction
|
1233
|
+
when :up
|
1234
|
+
@completion_journey_data.pointer = @completion_journey_data.list.size - 1
|
1235
|
+
when :down
|
1236
|
+
@completion_journey_data.pointer = 1
|
1237
|
+
end
|
1238
|
+
end
|
929
1239
|
@completion_state = CompletionState::JOURNEY
|
930
1240
|
else
|
931
1241
|
case direction
|
@@ -940,13 +1250,13 @@ class Reline::LineEditor
|
|
940
1250
|
@completion_journey_data.pointer = 0
|
941
1251
|
end
|
942
1252
|
end
|
943
|
-
completed = @completion_journey_data.list[@completion_journey_data.pointer]
|
944
|
-
@line = @completion_journey_data.preposing + completed + @completion_journey_data.postposing
|
945
|
-
line_to_pointer = @completion_journey_data.preposing + completed
|
946
|
-
@cursor_max = calculate_width(@line)
|
947
|
-
@cursor = calculate_width(line_to_pointer)
|
948
|
-
@byte_pointer = line_to_pointer.bytesize
|
949
1253
|
end
|
1254
|
+
completed = @completion_journey_data.list[@completion_journey_data.pointer]
|
1255
|
+
@line = @completion_journey_data.preposing + completed + @completion_journey_data.postposing
|
1256
|
+
line_to_pointer = @completion_journey_data.preposing + completed
|
1257
|
+
@cursor_max = calculate_width(@line)
|
1258
|
+
@cursor = calculate_width(line_to_pointer)
|
1259
|
+
@byte_pointer = line_to_pointer.bytesize
|
950
1260
|
end
|
951
1261
|
|
952
1262
|
private def run_for_operators(key, method_symbol, &block)
|
@@ -1121,7 +1431,20 @@ class Reline::LineEditor
|
|
1121
1431
|
if result.is_a?(Array)
|
1122
1432
|
completion_occurs = true
|
1123
1433
|
process_insert
|
1124
|
-
|
1434
|
+
if @config.autocompletion
|
1435
|
+
move_completed_list(result, :down)
|
1436
|
+
else
|
1437
|
+
complete(result)
|
1438
|
+
end
|
1439
|
+
end
|
1440
|
+
end
|
1441
|
+
elsif @config.editing_mode_is?(:emacs, :vi_insert) and key.char == :completion_journey_up
|
1442
|
+
if not @config.disable_completion and @config.autocompletion
|
1443
|
+
result = call_completion_proc
|
1444
|
+
if result.is_a?(Array)
|
1445
|
+
completion_occurs = true
|
1446
|
+
process_insert
|
1447
|
+
move_completed_list(result, :up)
|
1125
1448
|
end
|
1126
1449
|
end
|
1127
1450
|
elsif not @config.disable_completion and @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key.char)
|
@@ -1140,6 +1463,7 @@ class Reline::LineEditor
|
|
1140
1463
|
end
|
1141
1464
|
unless completion_occurs
|
1142
1465
|
@completion_state = CompletionState::NORMAL
|
1466
|
+
@completion_journey_data = nil
|
1143
1467
|
end
|
1144
1468
|
if not @in_pasting and @just_cursor_moving.nil?
|
1145
1469
|
if @previous_line_index and @buffer_of_lines[@previous_line_index] == @line
|
@@ -1159,7 +1483,13 @@ class Reline::LineEditor
|
|
1159
1483
|
|
1160
1484
|
def call_completion_proc
|
1161
1485
|
result = retrieve_completion_block(true)
|
1162
|
-
|
1486
|
+
pre, target, post = result
|
1487
|
+
result = call_completion_proc_with_checking_args(pre, target, post)
|
1488
|
+
Reline.core.instance_variable_set(:@completion_quote_character, nil)
|
1489
|
+
result
|
1490
|
+
end
|
1491
|
+
|
1492
|
+
def call_completion_proc_with_checking_args(pre, target, post)
|
1163
1493
|
if @completion_proc and target
|
1164
1494
|
argnum = @completion_proc.parameters.inject(0) { |result, item|
|
1165
1495
|
case item.first
|
@@ -1173,12 +1503,11 @@ class Reline::LineEditor
|
|
1173
1503
|
when 1
|
1174
1504
|
result = @completion_proc.(target)
|
1175
1505
|
when 2
|
1176
|
-
result = @completion_proc.(target,
|
1506
|
+
result = @completion_proc.(target, pre)
|
1177
1507
|
when 3..Float::INFINITY
|
1178
|
-
result = @completion_proc.(target,
|
1508
|
+
result = @completion_proc.(target, pre, post)
|
1179
1509
|
end
|
1180
1510
|
end
|
1181
|
-
Reline.core.instance_variable_set(:@completion_quote_character, nil)
|
1182
1511
|
result
|
1183
1512
|
end
|
1184
1513
|
|