reline 0.1.9 → 0.2.3
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 +4 -0
- data/lib/reline.rb +25 -5
- data/lib/reline/ansi.rb +44 -3
- data/lib/reline/config.rb +9 -7
- data/lib/reline/key_actor/emacs.rb +1 -1
- data/lib/reline/kill_ring.rb +12 -0
- data/lib/reline/line_editor.rb +498 -221
- data/lib/reline/line_editor.rb.orig +2696 -0
- data/lib/reline/unicode.rb +5 -4
- data/lib/reline/version.rb +1 -1
- data/lib/reline/windows.rb +9 -1
- data/license_of_rb-readline +25 -0
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8da8b8b73cb81c8d6288188ba05a0ea83a7dade5e60a54c5e9a591cdaa6ce729
|
4
|
+
data.tar.gz: b810a0521daf74369007cd0195f861e30f66cb75c86c29630216691ceb3cfd64
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bcdfacec31f8a2a672629b6a709cc9378d215758156ba11490a02d9372fa349706a3d7129db08832969baf3de823c5f553062e536dce69dd524f24e602807fc1
|
7
|
+
data.tar.gz: e7eb36a5936ff7ad3642b27869a57b766f33c77b149296d06e2c34b3ab1676b21880ba3dde12f1a5faae0f6280e662adc29fa1fd0daf43e6a394e5c1569765a8
|
data/README.md
CHANGED
@@ -11,3 +11,7 @@ Reline is compatible with the API of Ruby's stdlib 'readline', GNU Readline and
|
|
11
11
|
## License
|
12
12
|
|
13
13
|
The gem is available as open source under the terms of the [Ruby License](https://www.ruby-lang.org/en/about/license.txt).
|
14
|
+
|
15
|
+
## Acknowledgments for [rb-readline](https://github.com/ConnorAtherton/rb-readline)
|
16
|
+
|
17
|
+
In developing Reline, we have used some of the rb-readline implementation, so this library includes [copyright notice, list of conditions and the disclaimer](license_of_rb-readline) under the 3-Clause BSD License. Reline would never have been developed without rb-readline. Thank you for the tremendous accomplishments.
|
data/lib/reline.rb
CHANGED
@@ -36,7 +36,6 @@ module Reline
|
|
36
36
|
attr_accessor :config
|
37
37
|
attr_accessor :key_stroke
|
38
38
|
attr_accessor :line_editor
|
39
|
-
attr_accessor :ambiguous_width
|
40
39
|
attr_accessor :last_incremental_search
|
41
40
|
attr_reader :output
|
42
41
|
|
@@ -44,6 +43,7 @@ module Reline
|
|
44
43
|
self.output = STDOUT
|
45
44
|
yield self
|
46
45
|
@completion_quote_character = nil
|
46
|
+
@bracketed_paste_finished = false
|
47
47
|
end
|
48
48
|
|
49
49
|
def encoding
|
@@ -199,7 +199,11 @@ module Reline
|
|
199
199
|
|
200
200
|
private def inner_readline(prompt, add_hist, multiline, &confirm_multiline_termination)
|
201
201
|
if ENV['RELINE_STDERR_TTY']
|
202
|
-
|
202
|
+
if Reline::IOGate.win?
|
203
|
+
$stderr = File.open(ENV['RELINE_STDERR_TTY'], 'a')
|
204
|
+
else
|
205
|
+
$stderr.reopen(ENV['RELINE_STDERR_TTY'], 'w')
|
206
|
+
end
|
203
207
|
$stderr.sync = true
|
204
208
|
$stderr.puts "Reline is used by #{Process.pid}"
|
205
209
|
end
|
@@ -239,12 +243,18 @@ module Reline
|
|
239
243
|
loop do
|
240
244
|
prev_pasting_state = Reline::IOGate.in_pasting?
|
241
245
|
read_io(config.keyseq_timeout) { |inputs|
|
246
|
+
line_editor.set_pasting_state(Reline::IOGate.in_pasting?)
|
242
247
|
inputs.each { |c|
|
243
248
|
line_editor.input_key(c)
|
244
249
|
line_editor.rerender
|
245
250
|
}
|
251
|
+
if @bracketed_paste_finished
|
252
|
+
line_editor.rerender_all
|
253
|
+
@bracketed_paste_finished = false
|
254
|
+
end
|
246
255
|
}
|
247
256
|
if prev_pasting_state == true and not Reline::IOGate.in_pasting? and not line_editor.finished?
|
257
|
+
line_editor.set_pasting_state(false)
|
248
258
|
prev_pasting_state = false
|
249
259
|
line_editor.rerender_all
|
250
260
|
end
|
@@ -275,8 +285,13 @@ module Reline
|
|
275
285
|
buffer = []
|
276
286
|
loop do
|
277
287
|
c = Reline::IOGate.getc
|
278
|
-
|
279
|
-
|
288
|
+
if c == -1
|
289
|
+
result = :unmatched
|
290
|
+
@bracketed_paste_finished = true
|
291
|
+
else
|
292
|
+
buffer << c
|
293
|
+
result = key_stroke.match_status(buffer)
|
294
|
+
end
|
280
295
|
case result
|
281
296
|
when :matched
|
282
297
|
expanded = key_stroke.expand(buffer).map{ |expanded_c|
|
@@ -342,9 +357,14 @@ module Reline
|
|
342
357
|
end
|
343
358
|
end
|
344
359
|
|
360
|
+
def ambiguous_width
|
361
|
+
may_req_ambiguous_char_width unless defined? @ambiguous_width
|
362
|
+
@ambiguous_width
|
363
|
+
end
|
364
|
+
|
345
365
|
private def may_req_ambiguous_char_width
|
346
366
|
@ambiguous_width = 2 if Reline::IOGate == Reline::GeneralIO or STDOUT.is_a?(File)
|
347
|
-
return if ambiguous_width
|
367
|
+
return if @ambiguous_width
|
348
368
|
Reline::IOGate.move_cursor_column(0)
|
349
369
|
begin
|
350
370
|
output.write "\u{25bd}"
|
data/lib/reline/ansi.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'io/console'
|
2
|
+
require 'timeout'
|
2
3
|
|
3
4
|
class Reline::ANSI
|
4
5
|
def self.encoding
|
@@ -67,7 +68,7 @@ class Reline::ANSI
|
|
67
68
|
end
|
68
69
|
|
69
70
|
@@buf = []
|
70
|
-
def self.
|
71
|
+
def self.inner_getc
|
71
72
|
unless @@buf.empty?
|
72
73
|
return @@buf.shift
|
73
74
|
end
|
@@ -80,8 +81,48 @@ class Reline::ANSI
|
|
80
81
|
nil
|
81
82
|
end
|
82
83
|
|
84
|
+
@@in_bracketed_paste_mode = false
|
85
|
+
START_BRACKETED_PASTE = String.new("\e[200~,", encoding: Encoding::ASCII_8BIT)
|
86
|
+
END_BRACKETED_PASTE = String.new("\e[200~.", encoding: Encoding::ASCII_8BIT)
|
87
|
+
def self.getc_with_bracketed_paste
|
88
|
+
buffer = String.new(encoding: Encoding::ASCII_8BIT)
|
89
|
+
buffer << inner_getc
|
90
|
+
while START_BRACKETED_PASTE.start_with?(buffer) or END_BRACKETED_PASTE.start_with?(buffer) do
|
91
|
+
if START_BRACKETED_PASTE == buffer
|
92
|
+
@@in_bracketed_paste_mode = true
|
93
|
+
return inner_getc
|
94
|
+
elsif END_BRACKETED_PASTE == buffer
|
95
|
+
@@in_bracketed_paste_mode = false
|
96
|
+
ungetc(-1)
|
97
|
+
return inner_getc
|
98
|
+
end
|
99
|
+
begin
|
100
|
+
succ_c = nil
|
101
|
+
Timeout.timeout(Reline.core.config.keyseq_timeout * 100) {
|
102
|
+
succ_c = inner_getc
|
103
|
+
}
|
104
|
+
rescue Timeout::Error
|
105
|
+
break
|
106
|
+
else
|
107
|
+
buffer << succ_c
|
108
|
+
end
|
109
|
+
end
|
110
|
+
buffer.bytes.reverse_each do |ch|
|
111
|
+
ungetc ch
|
112
|
+
end
|
113
|
+
inner_getc
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.getc
|
117
|
+
if Reline.core.config.enable_bracketed_paste
|
118
|
+
getc_with_bracketed_paste
|
119
|
+
else
|
120
|
+
inner_getc
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
83
124
|
def self.in_pasting?
|
84
|
-
not Reline::IOGate.empty_buffer?
|
125
|
+
@@in_bracketed_paste_mode or (not Reline::IOGate.empty_buffer?)
|
85
126
|
end
|
86
127
|
|
87
128
|
def self.empty_buffer?
|
@@ -131,7 +172,7 @@ class Reline::ANSI
|
|
131
172
|
|
132
173
|
def self.cursor_pos
|
133
174
|
begin
|
134
|
-
res = ''
|
175
|
+
res = +''
|
135
176
|
m = nil
|
136
177
|
@@input.raw do |stdin|
|
137
178
|
@@output << "\e[6n"
|
data/lib/reline/config.rb
CHANGED
@@ -34,9 +34,11 @@ class Reline::Config
|
|
34
34
|
show-all-if-unmodified
|
35
35
|
visible-stats
|
36
36
|
show-mode-in-prompt
|
37
|
-
vi-cmd-mode-
|
38
|
-
vi-ins-mode-
|
37
|
+
vi-cmd-mode-string
|
38
|
+
vi-ins-mode-string
|
39
39
|
emacs-mode-string
|
40
|
+
enable-bracketed-paste
|
41
|
+
isearch-terminators
|
40
42
|
}
|
41
43
|
VARIABLE_NAME_SYMBOLS = VARIABLE_NAMES.map { |v| :"#{v.tr(?-, ?_)}" }
|
42
44
|
VARIABLE_NAME_SYMBOLS.each do |v|
|
@@ -54,8 +56,8 @@ class Reline::Config
|
|
54
56
|
@key_actors[:emacs] = Reline::KeyActor::Emacs.new
|
55
57
|
@key_actors[:vi_insert] = Reline::KeyActor::ViInsert.new
|
56
58
|
@key_actors[:vi_command] = Reline::KeyActor::ViCommand.new
|
57
|
-
@
|
58
|
-
@
|
59
|
+
@vi_cmd_mode_string = '(cmd)'
|
60
|
+
@vi_ins_mode_string = '(ins)'
|
59
61
|
@emacs_mode_string = '@'
|
60
62
|
# https://tiswww.case.edu/php/chet/readline/readline.html#IDX25
|
61
63
|
@history_size = -1 # unlimited
|
@@ -237,7 +239,7 @@ class Reline::Config
|
|
237
239
|
when 'completion-query-items'
|
238
240
|
@completion_query_items = value.to_i
|
239
241
|
when 'isearch-terminators'
|
240
|
-
@isearch_terminators =
|
242
|
+
@isearch_terminators = retrieve_string(value)
|
241
243
|
when 'editing-mode'
|
242
244
|
case value
|
243
245
|
when 'emacs'
|
@@ -268,9 +270,9 @@ class Reline::Config
|
|
268
270
|
@show_mode_in_prompt = false
|
269
271
|
end
|
270
272
|
when 'vi-cmd-mode-string'
|
271
|
-
@
|
273
|
+
@vi_cmd_mode_string = retrieve_string(value)
|
272
274
|
when 'vi-ins-mode-string'
|
273
|
-
@
|
275
|
+
@vi_ins_mode_string = retrieve_string(value)
|
274
276
|
when 'emacs-mode-string'
|
275
277
|
@emacs_mode_string = retrieve_string(value)
|
276
278
|
when *VARIABLE_NAMES then
|
data/lib/reline/kill_ring.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
class Reline::KillRing
|
2
|
+
include Enumerable
|
3
|
+
|
2
4
|
module State
|
3
5
|
FRESH = :fresh
|
4
6
|
CONTINUED = :continued
|
@@ -110,4 +112,14 @@ class Reline::KillRing
|
|
110
112
|
nil
|
111
113
|
end
|
112
114
|
end
|
115
|
+
|
116
|
+
def each
|
117
|
+
start = head = @ring.head
|
118
|
+
loop do
|
119
|
+
break if head.nil?
|
120
|
+
yield head.str
|
121
|
+
head = head.backward
|
122
|
+
break if head == start
|
123
|
+
end
|
124
|
+
end
|
113
125
|
end
|
data/lib/reline/line_editor.rb
CHANGED
@@ -50,18 +50,46 @@ class Reline::LineEditor
|
|
50
50
|
CompletionJourneyData = Struct.new('CompletionJourneyData', :preposing, :postposing, :list, :pointer)
|
51
51
|
MenuInfo = Struct.new('MenuInfo', :target, :list)
|
52
52
|
|
53
|
+
PROMPT_LIST_CACHE_TIMEOUT = 0.5
|
54
|
+
|
53
55
|
def initialize(config, encoding)
|
54
56
|
@config = config
|
55
57
|
@completion_append_character = ''
|
56
58
|
reset_variables(encoding: encoding)
|
57
59
|
end
|
58
60
|
|
61
|
+
def set_pasting_state(in_pasting)
|
62
|
+
@in_pasting = in_pasting
|
63
|
+
end
|
64
|
+
|
59
65
|
def simplified_rendering?
|
60
66
|
if finished?
|
61
67
|
false
|
68
|
+
elsif @just_cursor_moving and not @rerender_all
|
69
|
+
true
|
62
70
|
else
|
63
|
-
not @rerender_all and not finished? and
|
71
|
+
not @rerender_all and not finished? and @in_pasting
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
private def check_mode_string
|
76
|
+
mode_string = nil
|
77
|
+
if @config.show_mode_in_prompt
|
78
|
+
if @config.editing_mode_is?(:vi_command)
|
79
|
+
mode_string = @config.vi_cmd_mode_string
|
80
|
+
elsif @config.editing_mode_is?(:vi_insert)
|
81
|
+
mode_string = @config.vi_ins_mode_string
|
82
|
+
elsif @config.editing_mode_is?(:emacs)
|
83
|
+
mode_string = @config.emacs_mode_string
|
84
|
+
else
|
85
|
+
mode_string = '?'
|
86
|
+
end
|
87
|
+
end
|
88
|
+
if mode_string != @prev_mode_string
|
89
|
+
@rerender_all = true
|
64
90
|
end
|
91
|
+
@prev_mode_string = mode_string
|
92
|
+
mode_string
|
65
93
|
end
|
66
94
|
|
67
95
|
private def check_multiline_prompt(buffer, prompt)
|
@@ -74,39 +102,44 @@ class Reline::LineEditor
|
|
74
102
|
else
|
75
103
|
prompt = @prompt
|
76
104
|
end
|
77
|
-
|
105
|
+
if simplified_rendering?
|
106
|
+
mode_string = check_mode_string
|
107
|
+
prompt = mode_string + prompt if mode_string
|
108
|
+
return [prompt, calculate_width(prompt, true), [prompt] * buffer.size]
|
109
|
+
end
|
78
110
|
if @prompt_proc
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
mode_icon = @config.vi_ins_mode_icon
|
86
|
-
elsif @config.editing_mode_is?(:emacs)
|
87
|
-
mode_icon = @config.emacs_mode_string
|
88
|
-
else
|
89
|
-
mode_icon = '?'
|
111
|
+
use_cached_prompt_list = false
|
112
|
+
if @cached_prompt_list
|
113
|
+
if @just_cursor_moving
|
114
|
+
use_cached_prompt_list = true
|
115
|
+
elsif Time.now.to_f < (@prompt_cache_time + PROMPT_LIST_CACHE_TIMEOUT) and buffer.size == @cached_prompt_list.size
|
116
|
+
use_cached_prompt_list = true
|
90
117
|
end
|
91
|
-
prompt_list.map!{ |pr| mode_icon + pr }
|
92
118
|
end
|
119
|
+
use_cached_prompt_list = false if @rerender_all
|
120
|
+
if use_cached_prompt_list
|
121
|
+
prompt_list = @cached_prompt_list
|
122
|
+
else
|
123
|
+
prompt_list = @cached_prompt_list = @prompt_proc.(buffer)
|
124
|
+
@prompt_cache_time = Time.now.to_f
|
125
|
+
end
|
126
|
+
prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
|
127
|
+
mode_string = check_mode_string
|
128
|
+
prompt_list = prompt_list.map{ |pr| mode_string + pr } if mode_string
|
93
129
|
prompt = prompt_list[@line_index]
|
130
|
+
prompt = prompt_list[0] if prompt.nil?
|
131
|
+
prompt = prompt_list.last if prompt.nil?
|
132
|
+
if buffer.size > prompt_list.size
|
133
|
+
(buffer.size - prompt_list.size).times do
|
134
|
+
prompt_list << prompt_list.last
|
135
|
+
end
|
136
|
+
end
|
94
137
|
prompt_width = calculate_width(prompt, true)
|
95
138
|
[prompt, prompt_width, prompt_list]
|
96
139
|
else
|
140
|
+
mode_string = check_mode_string
|
141
|
+
prompt = mode_string + prompt if mode_string
|
97
142
|
prompt_width = calculate_width(prompt, true)
|
98
|
-
if @config.show_mode_in_prompt
|
99
|
-
if @config.editing_mode_is?(:vi_command)
|
100
|
-
mode_icon = @config.vi_cmd_mode_icon
|
101
|
-
elsif @config.editing_mode_is?(:vi_insert)
|
102
|
-
mode_icon = @config.vi_ins_mode_icon
|
103
|
-
elsif @config.editing_mode_is?(:emacs)
|
104
|
-
mode_icon = @config.emacs_mode_string
|
105
|
-
else
|
106
|
-
mode_icon = '?'
|
107
|
-
end
|
108
|
-
prompt = mode_icon + prompt
|
109
|
-
end
|
110
143
|
[prompt, prompt_width, nil]
|
111
144
|
end
|
112
145
|
end
|
@@ -114,8 +147,16 @@ class Reline::LineEditor
|
|
114
147
|
def reset(prompt = '', encoding:)
|
115
148
|
@rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
|
116
149
|
@screen_size = Reline::IOGate.get_screen_size
|
150
|
+
@screen_height = @screen_size.first
|
117
151
|
reset_variables(prompt, encoding: encoding)
|
118
152
|
@old_trap = Signal.trap('SIGINT') {
|
153
|
+
if @scroll_partial_screen
|
154
|
+
move_cursor_down(@screen_height - (@line_index - @scroll_partial_screen) - 1)
|
155
|
+
else
|
156
|
+
move_cursor_down(@highest_in_all - @line_index - 1)
|
157
|
+
end
|
158
|
+
Reline::IOGate.move_cursor_column(0)
|
159
|
+
scroll_down(1)
|
119
160
|
@old_trap.call if @old_trap.respond_to?(:call) # can also be string, ex: "DEFAULT"
|
120
161
|
raise Interrupt
|
121
162
|
}
|
@@ -123,6 +164,7 @@ class Reline::LineEditor
|
|
123
164
|
@rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
|
124
165
|
old_screen_size = @screen_size
|
125
166
|
@screen_size = Reline::IOGate.get_screen_size
|
167
|
+
@screen_height = @screen_size.first
|
126
168
|
if old_screen_size.last < @screen_size.last # columns increase
|
127
169
|
@rerender_all = true
|
128
170
|
rerender
|
@@ -174,7 +216,7 @@ class Reline::LineEditor
|
|
174
216
|
@cleared = false
|
175
217
|
@rerender_all = false
|
176
218
|
@history_pointer = nil
|
177
|
-
@kill_ring
|
219
|
+
@kill_ring ||= Reline::KillRing.new
|
178
220
|
@vi_clipboard = ''
|
179
221
|
@vi_arg = nil
|
180
222
|
@waiting_proc = nil
|
@@ -188,8 +230,16 @@ class Reline::LineEditor
|
|
188
230
|
@searching_prompt = nil
|
189
231
|
@first_char = true
|
190
232
|
@add_newline_to_end_of_buffer = false
|
233
|
+
@just_cursor_moving = nil
|
234
|
+
@cached_prompt_list = nil
|
235
|
+
@prompt_cache_time = nil
|
191
236
|
@eof = false
|
192
237
|
@continuous_insertion_buffer = String.new(encoding: @encoding)
|
238
|
+
@scroll_partial_screen = nil
|
239
|
+
@prev_mode_string = nil
|
240
|
+
@drop_terminate_spaces = false
|
241
|
+
@in_pasting = false
|
242
|
+
@auto_indent_proc = nil
|
193
243
|
reset_line
|
194
244
|
end
|
195
245
|
|
@@ -234,6 +284,7 @@ class Reline::LineEditor
|
|
234
284
|
@buffer_of_lines.insert(@line_index + 1, String.new(next_line, encoding: @encoding))
|
235
285
|
@previous_line_index = @line_index
|
236
286
|
@line_index += 1
|
287
|
+
@just_cursor_moving = false
|
237
288
|
end
|
238
289
|
|
239
290
|
private def calculate_height_by_width(width)
|
@@ -274,28 +325,28 @@ class Reline::LineEditor
|
|
274
325
|
end
|
275
326
|
end
|
276
327
|
|
277
|
-
private def calculate_nearest_cursor
|
278
|
-
|
328
|
+
private def calculate_nearest_cursor(line_to_calc = @line, cursor = @cursor, started_from = @started_from, byte_pointer = @byte_pointer, update = true)
|
329
|
+
new_cursor_max = calculate_width(line_to_calc)
|
279
330
|
new_cursor = 0
|
280
331
|
new_byte_pointer = 0
|
281
332
|
height = 1
|
282
333
|
max_width = @screen_size.last
|
283
334
|
if @config.editing_mode_is?(:vi_command)
|
284
|
-
last_byte_size = Reline::Unicode.get_prev_mbchar_size(
|
335
|
+
last_byte_size = Reline::Unicode.get_prev_mbchar_size(line_to_calc, line_to_calc.bytesize)
|
285
336
|
if last_byte_size > 0
|
286
|
-
last_mbchar =
|
337
|
+
last_mbchar = line_to_calc.byteslice(line_to_calc.bytesize - last_byte_size, last_byte_size)
|
287
338
|
last_width = Reline::Unicode.get_mbchar_width(last_mbchar)
|
288
|
-
|
339
|
+
end_of_line_cursor = new_cursor_max - last_width
|
289
340
|
else
|
290
|
-
|
341
|
+
end_of_line_cursor = new_cursor_max
|
291
342
|
end
|
292
343
|
else
|
293
|
-
|
344
|
+
end_of_line_cursor = new_cursor_max
|
294
345
|
end
|
295
|
-
|
346
|
+
line_to_calc.encode(Encoding::UTF_8).grapheme_clusters.each do |gc|
|
296
347
|
mbchar_width = Reline::Unicode.get_mbchar_width(gc)
|
297
348
|
now = new_cursor + mbchar_width
|
298
|
-
if now >
|
349
|
+
if now > end_of_line_cursor or now > cursor
|
299
350
|
break
|
300
351
|
end
|
301
352
|
new_cursor += mbchar_width
|
@@ -304,13 +355,20 @@ class Reline::LineEditor
|
|
304
355
|
end
|
305
356
|
new_byte_pointer += gc.bytesize
|
306
357
|
end
|
307
|
-
|
308
|
-
|
309
|
-
|
358
|
+
new_started_from = height - 1
|
359
|
+
if update
|
360
|
+
@cursor = new_cursor
|
361
|
+
@cursor_max = new_cursor_max
|
362
|
+
@started_from = new_started_from
|
363
|
+
@byte_pointer = new_byte_pointer
|
364
|
+
else
|
365
|
+
[new_cursor, new_cursor_max, new_started_from, new_byte_pointer]
|
366
|
+
end
|
310
367
|
end
|
311
368
|
|
312
369
|
def rerender_all
|
313
370
|
@rerender_all = true
|
371
|
+
process_insert(force: true)
|
314
372
|
rerender
|
315
373
|
end
|
316
374
|
|
@@ -319,193 +377,330 @@ class Reline::LineEditor
|
|
319
377
|
if @menu_info
|
320
378
|
scroll_down(@highest_in_all - @first_line_started_from)
|
321
379
|
@rerender_all = true
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
@output.flush
|
326
|
-
scroll_down(1)
|
327
|
-
end
|
328
|
-
scroll_down(@highest_in_all - 1)
|
329
|
-
move_cursor_up(@highest_in_all - 1 - @first_line_started_from)
|
380
|
+
end
|
381
|
+
if @menu_info
|
382
|
+
show_menu
|
330
383
|
@menu_info = nil
|
331
384
|
end
|
332
385
|
prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
|
333
386
|
if @cleared
|
334
|
-
|
387
|
+
clear_screen_buffer(prompt, prompt_list, prompt_width)
|
335
388
|
@cleared = false
|
336
|
-
back = 0
|
337
|
-
modify_lines(whole_lines).each_with_index do |line, index|
|
338
|
-
if @prompt_proc
|
339
|
-
pr = prompt_list[index]
|
340
|
-
height = render_partial(pr, calculate_width(pr), line, false)
|
341
|
-
else
|
342
|
-
height = render_partial(prompt, prompt_width, line, false)
|
343
|
-
end
|
344
|
-
if index < (@buffer_of_lines.size - 1)
|
345
|
-
move_cursor_down(height)
|
346
|
-
back += height
|
347
|
-
end
|
348
|
-
end
|
349
|
-
move_cursor_up(back)
|
350
|
-
move_cursor_down(@first_line_started_from + @started_from)
|
351
|
-
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
352
389
|
return
|
353
390
|
end
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
prompt, prompt_width, prompt_list = check_multiline_prompt(
|
360
|
-
@buffer_of_lines[@previous_line_index] = @line
|
361
|
-
@line = @buffer_of_lines[@line_index]
|
362
|
-
render_partial(prompt, prompt_width, @line, false)
|
363
|
-
@cursor = @cursor_max = calculate_width(@line)
|
364
|
-
@byte_pointer = @line.bytesize
|
365
|
-
@highest_in_all += @highest_in_this
|
366
|
-
@highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
|
367
|
-
@first_line_started_from += @started_from + 1
|
368
|
-
@started_from = calculate_height_by_width(prompt_width + @cursor) - 1
|
369
|
-
@previous_line_index = nil
|
370
|
-
@add_newline_to_end_of_buffer = false
|
371
|
-
elsif @previous_line_index or new_highest_in_this != @highest_in_this
|
391
|
+
if @is_multiline and finished? and @scroll_partial_screen
|
392
|
+
# Re-output all code higher than the screen when finished.
|
393
|
+
Reline::IOGate.move_cursor_up(@first_line_started_from + @started_from - @scroll_partial_screen)
|
394
|
+
Reline::IOGate.move_cursor_column(0)
|
395
|
+
@scroll_partial_screen = nil
|
396
|
+
prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
|
372
397
|
if @previous_line_index
|
373
398
|
new_lines = whole_lines(index: @previous_line_index, line: @line)
|
374
399
|
else
|
375
400
|
new_lines = whole_lines
|
376
401
|
end
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
402
|
+
modify_lines(new_lines).each_with_index do |line, index|
|
403
|
+
@output.write "#{prompt_list ? prompt_list[index] : prompt}#{line}\n"
|
404
|
+
Reline::IOGate.erase_after_cursor
|
405
|
+
end
|
406
|
+
@output.flush
|
407
|
+
return
|
408
|
+
end
|
409
|
+
new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
|
410
|
+
# FIXME: end of logical line sometimes breaks
|
411
|
+
rendered = false
|
412
|
+
if @add_newline_to_end_of_buffer
|
413
|
+
rerender_added_newline(prompt, prompt_width)
|
414
|
+
@add_newline_to_end_of_buffer = false
|
415
|
+
else
|
416
|
+
if @just_cursor_moving and not @rerender_all
|
417
|
+
rendered = just_move_cursor
|
418
|
+
@just_cursor_moving = false
|
419
|
+
return
|
420
|
+
elsif @previous_line_index or new_highest_in_this != @highest_in_this
|
421
|
+
rerender_changed_current_line
|
422
|
+
@previous_line_index = nil
|
423
|
+
rendered = true
|
424
|
+
elsif @rerender_all
|
425
|
+
rerender_all_lines
|
426
|
+
@rerender_all = false
|
427
|
+
rendered = true
|
391
428
|
else
|
392
|
-
move_cursor_up(all_height - 1)
|
393
429
|
end
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
end
|
401
|
-
height = render_partial(prompt, prompt_width, line, false)
|
402
|
-
if index < (new_lines.size - 1)
|
403
|
-
scroll_down(1)
|
404
|
-
back += height
|
430
|
+
end
|
431
|
+
if @is_multiline
|
432
|
+
if finished?
|
433
|
+
# Always rerender on finish because output_modifier_proc may return a different output.
|
434
|
+
if @previous_line_index
|
435
|
+
new_lines = whole_lines(index: @previous_line_index, line: @line)
|
405
436
|
else
|
406
|
-
|
437
|
+
new_lines = whole_lines
|
438
|
+
end
|
439
|
+
line = modify_lines(new_lines)[@line_index]
|
440
|
+
prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
|
441
|
+
render_partial(prompt, prompt_width, line, @first_line_started_from)
|
442
|
+
move_cursor_down(@highest_in_all - (@first_line_started_from + @highest_in_this - 1) - 1)
|
443
|
+
scroll_down(1)
|
444
|
+
Reline::IOGate.move_cursor_column(0)
|
445
|
+
Reline::IOGate.erase_after_cursor
|
446
|
+
elsif not rendered
|
447
|
+
unless @in_pasting
|
448
|
+
line = modify_lines(whole_lines)[@line_index]
|
449
|
+
prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
|
450
|
+
render_partial(prompt, prompt_width, line, @first_line_started_from)
|
407
451
|
end
|
408
452
|
end
|
409
|
-
|
410
|
-
if @
|
411
|
-
|
412
|
-
|
453
|
+
@buffer_of_lines[@line_index] = @line
|
454
|
+
@rest_height = 0 if @scroll_partial_screen
|
455
|
+
else
|
456
|
+
line = modify_lines(whole_lines)[@line_index]
|
457
|
+
render_partial(prompt, prompt_width, line, 0)
|
458
|
+
if finished?
|
459
|
+
scroll_down(1)
|
460
|
+
Reline::IOGate.move_cursor_column(0)
|
461
|
+
Reline::IOGate.erase_after_cursor
|
413
462
|
end
|
414
|
-
|
415
|
-
|
416
|
-
|
463
|
+
end
|
464
|
+
end
|
465
|
+
|
466
|
+
private def calculate_scroll_partial_screen(highest_in_all, cursor_y)
|
467
|
+
if @screen_height < highest_in_all
|
468
|
+
old_scroll_partial_screen = @scroll_partial_screen
|
469
|
+
if cursor_y == 0
|
470
|
+
@scroll_partial_screen = 0
|
471
|
+
elsif cursor_y == (highest_in_all - 1)
|
472
|
+
@scroll_partial_screen = highest_in_all - @screen_height
|
473
|
+
else
|
474
|
+
if @scroll_partial_screen
|
475
|
+
if cursor_y <= @scroll_partial_screen
|
476
|
+
@scroll_partial_screen = cursor_y
|
477
|
+
elsif (@scroll_partial_screen + @screen_height - 1) < cursor_y
|
478
|
+
@scroll_partial_screen = cursor_y - (@screen_height - 1)
|
479
|
+
end
|
417
480
|
else
|
418
|
-
|
481
|
+
if cursor_y > (@screen_height - 1)
|
482
|
+
@scroll_partial_screen = cursor_y - (@screen_height - 1)
|
483
|
+
else
|
484
|
+
@scroll_partial_screen = 0
|
485
|
+
end
|
419
486
|
end
|
420
|
-
if @prompt_proc
|
421
|
-
prompt = prompt_list[@line_index]
|
422
|
-
prompt_width = calculate_width(prompt, true)
|
423
487
|
end
|
424
|
-
|
425
|
-
|
426
|
-
@started_from = calculate_height_by_width(prompt_width + @cursor) - 1
|
427
|
-
move_cursor_down(@started_from)
|
428
|
-
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
429
|
-
@highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
|
430
|
-
@previous_line_index = nil
|
431
|
-
rendered = true
|
432
|
-
elsif @rerender_all
|
433
|
-
move_cursor_up(@first_line_started_from + @started_from)
|
434
|
-
Reline::IOGate.move_cursor_column(0)
|
435
|
-
back = 0
|
436
|
-
new_buffer = whole_lines
|
437
|
-
prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
|
438
|
-
new_buffer.each_with_index do |line, index|
|
439
|
-
prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
|
440
|
-
width = prompt_width + calculate_width(line)
|
441
|
-
height = calculate_height_by_width(width)
|
442
|
-
back += height
|
488
|
+
if @scroll_partial_screen != old_scroll_partial_screen
|
489
|
+
@rerender_all = true
|
443
490
|
end
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
elsif back < @highest_in_all
|
448
|
-
scroll_down(back)
|
449
|
-
Reline::IOGate.erase_after_cursor
|
450
|
-
(@highest_in_all - back - 1).times do
|
451
|
-
scroll_down(1)
|
452
|
-
Reline::IOGate.erase_after_cursor
|
453
|
-
end
|
454
|
-
move_cursor_up(@highest_in_all - 1)
|
491
|
+
else
|
492
|
+
if @scroll_partial_screen
|
493
|
+
@rerender_all = true
|
455
494
|
end
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
495
|
+
@scroll_partial_screen = nil
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
499
|
+
private def rerender_added_newline(prompt, prompt_width)
|
500
|
+
scroll_down(1)
|
501
|
+
@buffer_of_lines[@previous_line_index] = @line
|
502
|
+
@line = @buffer_of_lines[@line_index]
|
503
|
+
unless @in_pasting
|
504
|
+
render_partial(prompt, prompt_width, @line, @first_line_started_from + @started_from + 1, with_control: false)
|
505
|
+
end
|
506
|
+
@cursor = @cursor_max = calculate_width(@line)
|
507
|
+
@byte_pointer = @line.bytesize
|
508
|
+
@highest_in_all += @highest_in_this
|
509
|
+
@highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
|
510
|
+
@first_line_started_from += @started_from + 1
|
511
|
+
@started_from = calculate_height_by_width(prompt_width + @cursor) - 1
|
512
|
+
@previous_line_index = nil
|
513
|
+
end
|
514
|
+
|
515
|
+
def just_move_cursor
|
516
|
+
prompt, prompt_width, prompt_list = check_multiline_prompt(@buffer_of_lines, prompt)
|
517
|
+
move_cursor_up(@started_from)
|
518
|
+
new_first_line_started_from =
|
519
|
+
if @line_index.zero?
|
520
|
+
0
|
521
|
+
else
|
522
|
+
calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
|
465
523
|
end
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
@
|
473
|
-
|
474
|
-
if @line_index.zero?
|
475
|
-
0
|
476
|
-
else
|
477
|
-
calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list || prompt)
|
478
|
-
end
|
479
|
-
@started_from = calculate_height_by_width(prompt_width + @cursor) - 1
|
480
|
-
move_cursor_down(@first_line_started_from + @started_from)
|
481
|
-
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
524
|
+
first_line_diff = new_first_line_started_from - @first_line_started_from
|
525
|
+
new_cursor, new_cursor_max, new_started_from, new_byte_pointer = calculate_nearest_cursor(@buffer_of_lines[@line_index], @cursor, @started_from, @byte_pointer, false)
|
526
|
+
new_started_from = calculate_height_by_width(prompt_width + new_cursor) - 1
|
527
|
+
calculate_scroll_partial_screen(@highest_in_all, new_first_line_started_from + new_started_from)
|
528
|
+
@previous_line_index = nil
|
529
|
+
if @rerender_all
|
530
|
+
@line = @buffer_of_lines[@line_index]
|
531
|
+
rerender_all_lines
|
482
532
|
@rerender_all = false
|
483
|
-
|
533
|
+
true
|
534
|
+
else
|
535
|
+
@line = @buffer_of_lines[@line_index]
|
536
|
+
@first_line_started_from = new_first_line_started_from
|
537
|
+
@started_from = new_started_from
|
538
|
+
@cursor = new_cursor
|
539
|
+
@cursor_max = new_cursor_max
|
540
|
+
@byte_pointer = new_byte_pointer
|
541
|
+
move_cursor_down(first_line_diff + @started_from)
|
542
|
+
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
543
|
+
false
|
484
544
|
end
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
545
|
+
end
|
546
|
+
|
547
|
+
private def rerender_changed_current_line
|
548
|
+
if @previous_line_index
|
549
|
+
new_lines = whole_lines(index: @previous_line_index, line: @line)
|
550
|
+
else
|
551
|
+
new_lines = whole_lines
|
552
|
+
end
|
553
|
+
prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
|
554
|
+
all_height = calculate_height_by_lines(new_lines, prompt_list || prompt)
|
555
|
+
diff = all_height - @highest_in_all
|
556
|
+
move_cursor_down(@highest_in_all - @first_line_started_from - @started_from - 1)
|
557
|
+
if diff > 0
|
558
|
+
scroll_down(diff)
|
559
|
+
move_cursor_up(all_height - 1)
|
560
|
+
elsif diff < 0
|
561
|
+
(-diff).times do
|
492
562
|
Reline::IOGate.move_cursor_column(0)
|
493
563
|
Reline::IOGate.erase_after_cursor
|
494
|
-
|
495
|
-
render_partial(prompt, prompt_width, line)
|
564
|
+
move_cursor_up(1)
|
496
565
|
end
|
566
|
+
move_cursor_up(all_height - 1)
|
497
567
|
else
|
498
|
-
|
499
|
-
|
568
|
+
move_cursor_up(all_height - 1)
|
569
|
+
end
|
570
|
+
@highest_in_all = all_height
|
571
|
+
back = render_whole_lines(new_lines, prompt_list || prompt, prompt_width)
|
572
|
+
move_cursor_up(back)
|
573
|
+
if @previous_line_index
|
574
|
+
@buffer_of_lines[@previous_line_index] = @line
|
575
|
+
@line = @buffer_of_lines[@line_index]
|
576
|
+
end
|
577
|
+
@first_line_started_from =
|
578
|
+
if @line_index.zero?
|
579
|
+
0
|
580
|
+
else
|
581
|
+
calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
|
582
|
+
end
|
583
|
+
if @prompt_proc
|
584
|
+
prompt = prompt_list[@line_index]
|
585
|
+
prompt_width = calculate_width(prompt, true)
|
586
|
+
end
|
587
|
+
move_cursor_down(@first_line_started_from)
|
588
|
+
calculate_nearest_cursor
|
589
|
+
@started_from = calculate_height_by_width(prompt_width + @cursor) - 1
|
590
|
+
move_cursor_down(@started_from)
|
591
|
+
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
592
|
+
@highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
|
593
|
+
end
|
594
|
+
|
595
|
+
private def rerender_all_lines
|
596
|
+
move_cursor_up(@first_line_started_from + @started_from)
|
597
|
+
Reline::IOGate.move_cursor_column(0)
|
598
|
+
back = 0
|
599
|
+
new_buffer = whole_lines
|
600
|
+
prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
|
601
|
+
new_buffer.each_with_index do |line, index|
|
602
|
+
prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
|
603
|
+
width = prompt_width + calculate_width(line)
|
604
|
+
height = calculate_height_by_width(width)
|
605
|
+
back += height
|
606
|
+
end
|
607
|
+
old_highest_in_all = @highest_in_all
|
608
|
+
if @line_index.zero?
|
609
|
+
new_first_line_started_from = 0
|
610
|
+
else
|
611
|
+
new_first_line_started_from = calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list || prompt)
|
612
|
+
end
|
613
|
+
new_started_from = calculate_height_by_width(prompt_width + @cursor) - 1
|
614
|
+
calculate_scroll_partial_screen(back, new_first_line_started_from + new_started_from)
|
615
|
+
if @scroll_partial_screen
|
616
|
+
move_cursor_up(@first_line_started_from + @started_from)
|
617
|
+
scroll_down(@screen_height - 1)
|
618
|
+
move_cursor_up(@screen_height)
|
619
|
+
Reline::IOGate.move_cursor_column(0)
|
620
|
+
elsif back > old_highest_in_all
|
621
|
+
scroll_down(back - 1)
|
622
|
+
move_cursor_up(back - 1)
|
623
|
+
elsif back < old_highest_in_all
|
624
|
+
scroll_down(back)
|
625
|
+
Reline::IOGate.erase_after_cursor
|
626
|
+
(old_highest_in_all - back - 1).times do
|
500
627
|
scroll_down(1)
|
501
|
-
Reline::IOGate.move_cursor_column(0)
|
502
628
|
Reline::IOGate.erase_after_cursor
|
503
629
|
end
|
630
|
+
move_cursor_up(old_highest_in_all - 1)
|
631
|
+
end
|
632
|
+
render_whole_lines(new_buffer, prompt_list || prompt, prompt_width)
|
633
|
+
if @prompt_proc
|
634
|
+
prompt = prompt_list[@line_index]
|
635
|
+
prompt_width = calculate_width(prompt, true)
|
636
|
+
end
|
637
|
+
@highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
|
638
|
+
@highest_in_all = back
|
639
|
+
@first_line_started_from = new_first_line_started_from
|
640
|
+
@started_from = new_started_from
|
641
|
+
if @scroll_partial_screen
|
642
|
+
Reline::IOGate.move_cursor_up(@screen_height - (@first_line_started_from + @started_from - @scroll_partial_screen) - 1)
|
643
|
+
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
644
|
+
else
|
645
|
+
move_cursor_down(@first_line_started_from + @started_from - back + 1)
|
646
|
+
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
647
|
+
end
|
648
|
+
end
|
649
|
+
|
650
|
+
private def render_whole_lines(lines, prompt, prompt_width)
|
651
|
+
rendered_height = 0
|
652
|
+
modify_lines(lines).each_with_index do |line, index|
|
653
|
+
if prompt.is_a?(Array)
|
654
|
+
line_prompt = prompt[index]
|
655
|
+
prompt_width = calculate_width(line_prompt, true)
|
656
|
+
else
|
657
|
+
line_prompt = prompt
|
658
|
+
end
|
659
|
+
height = render_partial(line_prompt, prompt_width, line, rendered_height, with_control: false)
|
660
|
+
if index < (lines.size - 1)
|
661
|
+
if @scroll_partial_screen
|
662
|
+
if (@scroll_partial_screen - height) < rendered_height and (@scroll_partial_screen + @screen_height - 1) >= (rendered_height + height)
|
663
|
+
move_cursor_down(1)
|
664
|
+
end
|
665
|
+
else
|
666
|
+
scroll_down(1)
|
667
|
+
end
|
668
|
+
rendered_height += height
|
669
|
+
else
|
670
|
+
rendered_height += height - 1
|
671
|
+
end
|
504
672
|
end
|
673
|
+
rendered_height
|
505
674
|
end
|
506
675
|
|
507
|
-
private def render_partial(prompt, prompt_width, line_to_render, with_control
|
676
|
+
private def render_partial(prompt, prompt_width, line_to_render, this_started_from, with_control: true)
|
508
677
|
visual_lines, height = split_by_width(line_to_render.nil? ? prompt : prompt + line_to_render, @screen_size.last)
|
678
|
+
cursor_up_from_last_line = 0
|
679
|
+
# TODO: This logic would be sometimes buggy if this logical line isn't the current @line_index.
|
680
|
+
if @scroll_partial_screen
|
681
|
+
last_visual_line = this_started_from + (height - 1)
|
682
|
+
last_screen_line = @scroll_partial_screen + (@screen_height - 1)
|
683
|
+
if (@scroll_partial_screen - this_started_from) >= height
|
684
|
+
# Render nothing because this line is before the screen.
|
685
|
+
visual_lines = []
|
686
|
+
elsif this_started_from > last_screen_line
|
687
|
+
# Render nothing because this line is after the screen.
|
688
|
+
visual_lines = []
|
689
|
+
else
|
690
|
+
deleted_lines_before_screen = []
|
691
|
+
if @scroll_partial_screen > this_started_from and last_visual_line >= @scroll_partial_screen
|
692
|
+
# A part of visual lines are before the screen.
|
693
|
+
deleted_lines_before_screen = visual_lines.shift((@scroll_partial_screen - this_started_from) * 2)
|
694
|
+
deleted_lines_before_screen.compact!
|
695
|
+
end
|
696
|
+
if this_started_from <= last_screen_line and last_screen_line < last_visual_line
|
697
|
+
# A part of visual lines are after the screen.
|
698
|
+
visual_lines.pop((last_visual_line - last_screen_line) * 2)
|
699
|
+
end
|
700
|
+
move_cursor_up(deleted_lines_before_screen.size - @started_from)
|
701
|
+
cursor_up_from_last_line = @started_from - deleted_lines_before_screen.size
|
702
|
+
end
|
703
|
+
end
|
509
704
|
if with_control
|
510
705
|
if height > @highest_in_this
|
511
706
|
diff = height - @highest_in_this
|
@@ -520,22 +715,22 @@ class Reline::LineEditor
|
|
520
715
|
end
|
521
716
|
move_cursor_up(@started_from)
|
522
717
|
@started_from = calculate_height_by_width(prompt_width + @cursor) - 1
|
718
|
+
cursor_up_from_last_line = height - 1 - @started_from
|
719
|
+
end
|
720
|
+
if Reline::Unicode::CSI_REGEXP.match?(prompt + line_to_render)
|
721
|
+
@output.write "\e[0m" # clear character decorations
|
523
722
|
end
|
524
|
-
Reline::IOGate.move_cursor_column(0)
|
525
723
|
visual_lines.each_with_index do |line, index|
|
724
|
+
Reline::IOGate.move_cursor_column(0)
|
526
725
|
if line.nil?
|
527
726
|
if calculate_width(visual_lines[index - 1], true) == Reline::IOGate.get_screen_size.last
|
528
|
-
#
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
# cursor position.
|
536
|
-
move_cursor_down(1)
|
537
|
-
Reline::IOGate.move_cursor_column(0)
|
538
|
-
end
|
727
|
+
# Reaches the end of line.
|
728
|
+
#
|
729
|
+
# When the cursor is at the end of the line and erases characters
|
730
|
+
# after the cursor, some terminals delete the character at the
|
731
|
+
# cursor position.
|
732
|
+
move_cursor_down(1)
|
733
|
+
Reline::IOGate.move_cursor_column(0)
|
539
734
|
else
|
540
735
|
Reline::IOGate.erase_after_cursor
|
541
736
|
move_cursor_down(1)
|
@@ -544,25 +739,24 @@ class Reline::LineEditor
|
|
544
739
|
next
|
545
740
|
end
|
546
741
|
@output.write line
|
547
|
-
if Reline::IOGate.win? and calculate_width(line, true) == Reline::IOGate.get_screen_size.last
|
548
|
-
# A newline is automatically inserted if a character is rendered at eol on command prompt.
|
549
|
-
@rest_height -= 1 if @rest_height > 0
|
550
|
-
end
|
551
742
|
@output.flush
|
552
743
|
if @first_prompt
|
553
744
|
@first_prompt = false
|
554
745
|
@pre_input_hook&.call
|
555
746
|
end
|
556
747
|
end
|
557
|
-
|
558
|
-
|
748
|
+
unless visual_lines.empty?
|
749
|
+
Reline::IOGate.erase_after_cursor
|
750
|
+
Reline::IOGate.move_cursor_column(0)
|
751
|
+
end
|
559
752
|
if with_control
|
560
753
|
# Just after rendring, so the cursor is on the last line.
|
561
754
|
if finished?
|
562
755
|
Reline::IOGate.move_cursor_column(0)
|
563
756
|
else
|
564
757
|
# Moves up from bottom of lines to the cursor position.
|
565
|
-
move_cursor_up(
|
758
|
+
move_cursor_up(cursor_up_from_last_line)
|
759
|
+
# This logic is buggy if a fullwidth char is wrapped because there is only one halfwidth at end of a line.
|
566
760
|
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
567
761
|
end
|
568
762
|
end
|
@@ -579,6 +773,39 @@ class Reline::LineEditor
|
|
579
773
|
end
|
580
774
|
end
|
581
775
|
|
776
|
+
private def show_menu
|
777
|
+
scroll_down(@highest_in_all - @first_line_started_from)
|
778
|
+
@rerender_all = true
|
779
|
+
@menu_info.list.sort!.each do |item|
|
780
|
+
Reline::IOGate.move_cursor_column(0)
|
781
|
+
@output.write item
|
782
|
+
@output.flush
|
783
|
+
scroll_down(1)
|
784
|
+
end
|
785
|
+
scroll_down(@highest_in_all - 1)
|
786
|
+
move_cursor_up(@highest_in_all - 1 - @first_line_started_from)
|
787
|
+
end
|
788
|
+
|
789
|
+
private def clear_screen_buffer(prompt, prompt_list, prompt_width)
|
790
|
+
Reline::IOGate.clear_screen
|
791
|
+
back = 0
|
792
|
+
modify_lines(whole_lines).each_with_index do |line, index|
|
793
|
+
if @prompt_proc
|
794
|
+
pr = prompt_list[index]
|
795
|
+
height = render_partial(pr, calculate_width(pr), line, back, with_control: false)
|
796
|
+
else
|
797
|
+
height = render_partial(prompt, prompt_width, line, back, with_control: false)
|
798
|
+
end
|
799
|
+
if index < (@buffer_of_lines.size - 1)
|
800
|
+
move_cursor_down(height)
|
801
|
+
back += height
|
802
|
+
end
|
803
|
+
end
|
804
|
+
move_cursor_up(back)
|
805
|
+
move_cursor_down(@first_line_started_from + @started_from)
|
806
|
+
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
807
|
+
end
|
808
|
+
|
582
809
|
def editing_mode
|
583
810
|
@config.editing_mode
|
584
811
|
end
|
@@ -866,6 +1093,7 @@ class Reline::LineEditor
|
|
866
1093
|
end
|
867
1094
|
|
868
1095
|
def input_key(key)
|
1096
|
+
@just_cursor_moving = nil
|
869
1097
|
if key.char.nil?
|
870
1098
|
if @first_char
|
871
1099
|
@line = nil
|
@@ -873,6 +1101,7 @@ class Reline::LineEditor
|
|
873
1101
|
finish
|
874
1102
|
return
|
875
1103
|
end
|
1104
|
+
old_line = @line.dup
|
876
1105
|
@first_char = false
|
877
1106
|
completion_occurs = false
|
878
1107
|
if @config.editing_mode_is?(:emacs, :vi_insert) and key.char == "\C-i".ord
|
@@ -901,6 +1130,17 @@ class Reline::LineEditor
|
|
901
1130
|
unless completion_occurs
|
902
1131
|
@completion_state = CompletionState::NORMAL
|
903
1132
|
end
|
1133
|
+
if not @in_pasting and @just_cursor_moving.nil?
|
1134
|
+
if @previous_line_index and @buffer_of_lines[@previous_line_index] == @line
|
1135
|
+
@just_cursor_moving = true
|
1136
|
+
elsif @previous_line_index.nil? and @buffer_of_lines[@line_index] == @line and old_line == @line
|
1137
|
+
@just_cursor_moving = true
|
1138
|
+
else
|
1139
|
+
@just_cursor_moving = false
|
1140
|
+
end
|
1141
|
+
else
|
1142
|
+
@just_cursor_moving = false
|
1143
|
+
end
|
904
1144
|
if @is_multiline and @auto_indent_proc and not simplified_rendering?
|
905
1145
|
process_auto_indent
|
906
1146
|
end
|
@@ -939,6 +1179,7 @@ class Reline::LineEditor
|
|
939
1179
|
new_lines = whole_lines
|
940
1180
|
end
|
941
1181
|
new_indent = @auto_indent_proc.(new_lines, @line_index, @byte_pointer, @check_new_auto_indent)
|
1182
|
+
new_indent = @cursor_max if new_indent&.> @cursor_max
|
942
1183
|
if new_indent&.>= 0
|
943
1184
|
md = new_lines[@line_index].match(/\A */)
|
944
1185
|
prev_indent = md[0].count(' ')
|
@@ -1093,7 +1334,11 @@ class Reline::LineEditor
|
|
1093
1334
|
if @buffer_of_lines.size == 1 and @line.nil?
|
1094
1335
|
nil
|
1095
1336
|
else
|
1096
|
-
|
1337
|
+
if @previous_line_index
|
1338
|
+
whole_lines(index: @previous_line_index, line: @line).join("\n")
|
1339
|
+
else
|
1340
|
+
whole_lines.join("\n")
|
1341
|
+
end
|
1097
1342
|
end
|
1098
1343
|
end
|
1099
1344
|
|
@@ -1139,14 +1384,14 @@ class Reline::LineEditor
|
|
1139
1384
|
cursor_line = @line.byteslice(0, @byte_pointer)
|
1140
1385
|
insert_new_line(cursor_line, next_line)
|
1141
1386
|
@cursor = 0
|
1142
|
-
@check_new_auto_indent = true
|
1387
|
+
@check_new_auto_indent = true unless @in_pasting
|
1143
1388
|
end
|
1144
1389
|
end
|
1145
1390
|
|
1146
1391
|
private def ed_unassigned(key) end # do nothing
|
1147
1392
|
|
1148
1393
|
private def process_insert(force: false)
|
1149
|
-
return if @continuous_insertion_buffer.empty? or (
|
1394
|
+
return if @continuous_insertion_buffer.empty? or (@in_pasting and not force)
|
1150
1395
|
width = Reline::Unicode.calculate_width(@continuous_insertion_buffer)
|
1151
1396
|
bytesize = @continuous_insertion_buffer.bytesize
|
1152
1397
|
if @cursor == @cursor_max
|
@@ -1181,7 +1426,7 @@ class Reline::LineEditor
|
|
1181
1426
|
str = key.chr
|
1182
1427
|
bytesize = 1
|
1183
1428
|
end
|
1184
|
-
if
|
1429
|
+
if @in_pasting
|
1185
1430
|
@continuous_insertion_buffer << str
|
1186
1431
|
return
|
1187
1432
|
elsif not @continuous_insertion_buffer.empty?
|
@@ -1193,7 +1438,12 @@ class Reline::LineEditor
|
|
1193
1438
|
else
|
1194
1439
|
@line = byteinsert(@line, @byte_pointer, str)
|
1195
1440
|
end
|
1441
|
+
last_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
|
1196
1442
|
@byte_pointer += bytesize
|
1443
|
+
last_mbchar = @line.byteslice((@byte_pointer - bytesize - last_byte_size), last_byte_size)
|
1444
|
+
if last_byte_size != 0 and (last_mbchar + str).grapheme_clusters.size == 1
|
1445
|
+
width = 0
|
1446
|
+
end
|
1197
1447
|
@cursor += width
|
1198
1448
|
@cursor_max += width
|
1199
1449
|
end
|
@@ -1405,9 +1655,11 @@ class Reline::LineEditor
|
|
1405
1655
|
searcher = generate_searcher
|
1406
1656
|
searcher.resume(key)
|
1407
1657
|
@searching_prompt = "(reverse-i-search)`': "
|
1658
|
+
termination_keys = ["\C-j".ord]
|
1659
|
+
termination_keys.concat(@config.isearch_terminators&.chars&.map(&:ord)) if @config.isearch_terminators
|
1408
1660
|
@waiting_proc = ->(k) {
|
1409
1661
|
case k
|
1410
|
-
when
|
1662
|
+
when *termination_keys
|
1411
1663
|
if @history_pointer
|
1412
1664
|
buffer = Reline::HISTORY[@history_pointer]
|
1413
1665
|
else
|
@@ -1426,6 +1678,8 @@ class Reline::LineEditor
|
|
1426
1678
|
@waiting_proc = nil
|
1427
1679
|
@cursor_max = calculate_width(@line)
|
1428
1680
|
@cursor = @byte_pointer = 0
|
1681
|
+
@rerender_all = true
|
1682
|
+
@cached_prompt_list = nil
|
1429
1683
|
searcher.resume(-1)
|
1430
1684
|
when "\C-g".ord
|
1431
1685
|
if @is_multiline
|
@@ -1469,6 +1723,8 @@ class Reline::LineEditor
|
|
1469
1723
|
@waiting_proc = nil
|
1470
1724
|
@cursor_max = calculate_width(@line)
|
1471
1725
|
@cursor = @byte_pointer = 0
|
1726
|
+
@rerender_all = true
|
1727
|
+
@cached_prompt_list = nil
|
1472
1728
|
searcher.resume(-1)
|
1473
1729
|
end
|
1474
1730
|
end
|
@@ -1521,7 +1777,7 @@ class Reline::LineEditor
|
|
1521
1777
|
@buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
|
1522
1778
|
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
|
1523
1779
|
@line_index = line_no
|
1524
|
-
@line = @buffer_of_lines
|
1780
|
+
@line = @buffer_of_lines[@line_index]
|
1525
1781
|
@rerender_all = true
|
1526
1782
|
else
|
1527
1783
|
@line = Reline::HISTORY[@history_pointer]
|
@@ -1569,7 +1825,7 @@ class Reline::LineEditor
|
|
1569
1825
|
@line_index = line_no
|
1570
1826
|
end
|
1571
1827
|
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
|
1572
|
-
@line = @buffer_of_lines
|
1828
|
+
@line = @buffer_of_lines[@line_index]
|
1573
1829
|
@rerender_all = true
|
1574
1830
|
else
|
1575
1831
|
if @history_pointer.nil? and substr.empty?
|
@@ -1761,6 +2017,7 @@ class Reline::LineEditor
|
|
1761
2017
|
@cursor = 0
|
1762
2018
|
end
|
1763
2019
|
end
|
2020
|
+
alias_method :kill_line, :em_kill_line
|
1764
2021
|
|
1765
2022
|
private def em_delete(key)
|
1766
2023
|
if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
|
@@ -1811,6 +2068,7 @@ class Reline::LineEditor
|
|
1811
2068
|
@byte_pointer += yanked.bytesize
|
1812
2069
|
end
|
1813
2070
|
end
|
2071
|
+
alias_method :yank, :em_yank
|
1814
2072
|
|
1815
2073
|
private def em_yank_pop(key)
|
1816
2074
|
yanked, prev_yank = @kill_ring.yank_pop
|
@@ -1827,6 +2085,7 @@ class Reline::LineEditor
|
|
1827
2085
|
@byte_pointer += yanked.bytesize
|
1828
2086
|
end
|
1829
2087
|
end
|
2088
|
+
alias_method :yank_pop, :em_yank_pop
|
1830
2089
|
|
1831
2090
|
private def ed_clear_screen(key)
|
1832
2091
|
@cleared = true
|
@@ -1957,9 +2216,10 @@ class Reline::LineEditor
|
|
1957
2216
|
@byte_pointer -= byte_size
|
1958
2217
|
@cursor -= width
|
1959
2218
|
@cursor_max -= width
|
1960
|
-
@kill_ring.append(deleted)
|
2219
|
+
@kill_ring.append(deleted, true)
|
1961
2220
|
end
|
1962
2221
|
end
|
2222
|
+
alias_method :unix_word_rubout, :em_kill_region
|
1963
2223
|
|
1964
2224
|
private def copy_for_vi(text)
|
1965
2225
|
if @config.editing_mode_is?(:vi_insert) or @config.editing_mode_is?(:vi_command)
|
@@ -1984,7 +2244,7 @@ class Reline::LineEditor
|
|
1984
2244
|
|
1985
2245
|
private def vi_next_word(key, arg: 1)
|
1986
2246
|
if @line.bytesize > @byte_pointer
|
1987
|
-
byte_size, width = Reline::Unicode.vi_forward_word(@line, @byte_pointer)
|
2247
|
+
byte_size, width = Reline::Unicode.vi_forward_word(@line, @byte_pointer, @drop_terminate_spaces)
|
1988
2248
|
@byte_pointer += byte_size
|
1989
2249
|
@cursor += width
|
1990
2250
|
end
|
@@ -2112,6 +2372,7 @@ class Reline::LineEditor
|
|
2112
2372
|
end
|
2113
2373
|
|
2114
2374
|
private def vi_change_meta(key, arg: 1)
|
2375
|
+
@drop_terminate_spaces = true
|
2115
2376
|
@waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
|
2116
2377
|
if byte_pointer_diff > 0
|
2117
2378
|
@line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
|
@@ -2123,6 +2384,7 @@ class Reline::LineEditor
|
|
2123
2384
|
@cursor_max -= cursor_diff.abs
|
2124
2385
|
@byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
|
2125
2386
|
@config.editing_mode = :vi_insert
|
2387
|
+
@drop_terminate_spaces = false
|
2126
2388
|
}
|
2127
2389
|
@waiting_operator_vi_arg = arg
|
2128
2390
|
end
|
@@ -2178,6 +2440,9 @@ class Reline::LineEditor
|
|
2178
2440
|
width = Reline::Unicode.get_mbchar_width(mbchar)
|
2179
2441
|
@cursor_max -= width
|
2180
2442
|
if @cursor > 0 and @cursor >= @cursor_max
|
2443
|
+
byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
|
2444
|
+
mbchar = @line.byteslice(@byte_pointer - byte_size, byte_size)
|
2445
|
+
width = Reline::Unicode.get_mbchar_width(mbchar)
|
2181
2446
|
@byte_pointer -= byte_size
|
2182
2447
|
@cursor -= width
|
2183
2448
|
end
|
@@ -2211,11 +2476,23 @@ class Reline::LineEditor
|
|
2211
2476
|
|
2212
2477
|
private def vi_histedit(key)
|
2213
2478
|
path = Tempfile.open { |fp|
|
2214
|
-
|
2479
|
+
if @is_multiline
|
2480
|
+
fp.write whole_lines.join("\n")
|
2481
|
+
else
|
2482
|
+
fp.write @line
|
2483
|
+
end
|
2215
2484
|
fp.path
|
2216
2485
|
}
|
2217
2486
|
system("#{ENV['EDITOR']} #{path}")
|
2218
|
-
@
|
2487
|
+
if @is_multiline
|
2488
|
+
@buffer_of_lines = File.read(path).split("\n")
|
2489
|
+
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
|
2490
|
+
@line_index = 0
|
2491
|
+
@line = @buffer_of_lines[@line_index]
|
2492
|
+
@rerender_all = true
|
2493
|
+
else
|
2494
|
+
@line = File.read(path)
|
2495
|
+
end
|
2219
2496
|
finish
|
2220
2497
|
end
|
2221
2498
|
|
@@ -2274,7 +2551,7 @@ class Reline::LineEditor
|
|
2274
2551
|
byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
|
2275
2552
|
before = @line.byteslice(0, @byte_pointer)
|
2276
2553
|
remaining_point = @byte_pointer + byte_size
|
2277
|
-
after = @line.byteslice(remaining_point, @line.
|
2554
|
+
after = @line.byteslice(remaining_point, @line.bytesize - remaining_point)
|
2278
2555
|
@line = before + k.chr + after
|
2279
2556
|
@cursor_max = calculate_width(@line)
|
2280
2557
|
@waiting_proc = nil
|
@@ -2285,7 +2562,7 @@ class Reline::LineEditor
|
|
2285
2562
|
end
|
2286
2563
|
before = @line.byteslice(0, @byte_pointer)
|
2287
2564
|
remaining_point = @byte_pointer + byte_size
|
2288
|
-
after = @line.byteslice(remaining_point, @line.
|
2565
|
+
after = @line.byteslice(remaining_point, @line.bytesize - remaining_point)
|
2289
2566
|
replaced = k.chr * arg
|
2290
2567
|
@line = before + replaced + after
|
2291
2568
|
@byte_pointer += replaced.bytesize
|