Skip to content

Commit e92dcbf

Browse files
authored
Implement bracketed paste insert (#655)
1 parent 328699e commit e92dcbf

File tree

6 files changed

+55
-49
lines changed

6 files changed

+55
-49
lines changed

lib/reline.rb

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,10 @@ def readline(prompt = '', add_hist = false)
312312
$stderr.sync = true
313313
$stderr.puts "Reline is used by #{Process.pid}"
314314
end
315+
unless config.test_mode or config.loaded?
316+
config.read
317+
io_gate.set_default_key_bindings(config)
318+
end
315319
otio = io_gate.prep
316320

317321
may_req_ambiguous_char_width
@@ -338,11 +342,6 @@ def readline(prompt = '', add_hist = false)
338342
end
339343
end
340344

341-
unless config.test_mode or config.loaded?
342-
config.read
343-
io_gate.set_default_key_bindings(config)
344-
end
345-
346345
line_editor.print_nomultiline_prompt(prompt)
347346
line_editor.update_dialogs
348347
line_editor.rerender
@@ -352,7 +351,15 @@ def readline(prompt = '', add_hist = false)
352351
loop do
353352
read_io(config.keyseq_timeout) { |inputs|
354353
line_editor.set_pasting_state(io_gate.in_pasting?)
355-
inputs.each { |key| line_editor.update(key) }
354+
inputs.each do |key|
355+
if key.char == :bracketed_paste_start
356+
text = io_gate.read_bracketed_paste
357+
line_editor.insert_pasted_text(text)
358+
line_editor.scroll_into_view
359+
else
360+
line_editor.update(key)
361+
end
362+
end
356363
}
357364
if line_editor.finished?
358365
line_editor.render_finished

lib/reline/ansi.rb

Lines changed: 22 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ def self.win?
4545
end
4646

4747
def self.set_default_key_bindings(config, allow_terminfo: true)
48+
set_bracketed_paste_key_bindings(config)
4849
set_default_key_bindings_ansi_cursor(config)
4950
if allow_terminfo && Reline::Terminfo.enabled?
5051
set_default_key_bindings_terminfo(config)
@@ -66,6 +67,12 @@ def self.set_default_key_bindings(config, allow_terminfo: true)
6667
end
6768
end
6869

70+
def self.set_bracketed_paste_key_bindings(config)
71+
[:emacs, :vi_insert, :vi_command].each do |keymap|
72+
config.add_default_key_binding_by_keymap(keymap, START_BRACKETED_PASTE.bytes, :bracketed_paste_start)
73+
end
74+
end
75+
6976
def self.set_default_key_bindings_ansi_cursor(config)
7077
ANSI_CURSOR_KEY_BINDINGS.each do |char, (default_func, modifiers)|
7178
bindings = [["\e[#{char}", default_func]] # CSI + char
@@ -178,46 +185,26 @@ def self.inner_getc(timeout_second)
178185
nil
179186
end
180187

181-
@@in_bracketed_paste_mode = false
182-
START_BRACKETED_PASTE = String.new("\e[200~,", encoding: Encoding::ASCII_8BIT)
183-
END_BRACKETED_PASTE = String.new("\e[200~.", encoding: Encoding::ASCII_8BIT)
184-
def self.getc_with_bracketed_paste(timeout_second)
188+
START_BRACKETED_PASTE = String.new("\e[200~", encoding: Encoding::ASCII_8BIT)
189+
END_BRACKETED_PASTE = String.new("\e[201~", encoding: Encoding::ASCII_8BIT)
190+
def self.read_bracketed_paste
185191
buffer = String.new(encoding: Encoding::ASCII_8BIT)
186-
buffer << inner_getc(timeout_second)
187-
while START_BRACKETED_PASTE.start_with?(buffer) or END_BRACKETED_PASTE.start_with?(buffer) do
188-
if START_BRACKETED_PASTE == buffer
189-
@@in_bracketed_paste_mode = true
190-
return inner_getc(timeout_second)
191-
elsif END_BRACKETED_PASTE == buffer
192-
@@in_bracketed_paste_mode = false
193-
ungetc(-1)
194-
return inner_getc(timeout_second)
195-
end
196-
succ_c = inner_getc(Reline.core.config.keyseq_timeout)
197-
198-
if succ_c
199-
buffer << succ_c
200-
else
201-
break
202-
end
192+
until buffer.end_with?(END_BRACKETED_PASTE)
193+
c = inner_getc(Float::INFINITY)
194+
break unless c
195+
buffer << c
203196
end
204-
buffer.bytes.reverse_each do |ch|
205-
ungetc ch
206-
end
207-
inner_getc(timeout_second)
197+
string = buffer.delete_suffix(END_BRACKETED_PASTE).force_encoding(encoding)
198+
string.valid_encoding? ? string : ''
208199
end
209200

210201
# if the usage expects to wait indefinitely, use Float::INFINITY for timeout_second
211202
def self.getc(timeout_second)
212-
if Reline.core.config.enable_bracketed_paste
213-
getc_with_bracketed_paste(timeout_second)
214-
else
215-
inner_getc(timeout_second)
216-
end
203+
inner_getc(timeout_second)
217204
end
218205

219206
def self.in_pasting?
220-
@@in_bracketed_paste_mode or (not empty_buffer?)
207+
not empty_buffer?
221208
end
222209

223210
def self.empty_buffer?
@@ -361,11 +348,15 @@ def self.set_winch_handler(&handler)
361348
end
362349

363350
def self.prep
351+
# Enable bracketed paste
352+
@@output.write "\e[?2004h" if Reline.core.config.enable_bracketed_paste
364353
retrieve_keybuffer
365354
nil
366355
end
367356

368357
def self.deprep(otio)
358+
# Disable bracketed paste
359+
@@output.write "\e[?2004l" if Reline.core.config.enable_bracketed_paste
369360
Signal.trap('WINCH', @@old_winch_handler) if @@old_winch_handler
370361
end
371362
end

lib/reline/config.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ def initialize
5151
@autocompletion = false
5252
@convert_meta = true if seven_bit_encoding?(Reline::IOGate.encoding)
5353
@loaded = false
54+
@enable_bracketed_paste = true
5455
end
5556

5657
def reset

lib/reline/line_editor.rb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ def multiline_off
283283
indent1 = @auto_indent_proc.(@buffer_of_lines.take(@line_index - 1).push(''), @line_index - 1, 0, true)
284284
indent2 = @auto_indent_proc.(@buffer_of_lines.take(@line_index), @line_index - 1, @buffer_of_lines[@line_index - 1].bytesize, false)
285285
indent = indent2 || indent1
286-
@buffer_of_lines[@line_index - 1] = ' ' * indent + @buffer_of_lines[@line_index - 1].gsub(/\A */, '')
286+
@buffer_of_lines[@line_index - 1] = ' ' * indent + @buffer_of_lines[@line_index - 1].gsub(/\A\s*/, '')
287287
)
288288
process_auto_indent @line_index, add_newline: true
289289
else
@@ -1305,6 +1305,16 @@ def confirm_multiline_termination
13051305
@confirm_multiline_termination_proc.(temp_buffer.join("\n") + "\n")
13061306
end
13071307

1308+
def insert_pasted_text(text)
1309+
pre = @buffer_of_lines[@line_index].byteslice(0, @byte_pointer)
1310+
post = @buffer_of_lines[@line_index].byteslice(@byte_pointer..)
1311+
lines = (pre + text.gsub(/\r\n?/, "\n") + post).split("\n", -1)
1312+
lines << '' if lines.empty?
1313+
@buffer_of_lines[@line_index, 1] = lines
1314+
@line_index += lines.size - 1
1315+
@byte_pointer = @buffer_of_lines[@line_index].bytesize - post.bytesize
1316+
end
1317+
13081318
def insert_text(text)
13091319
if @buffer_of_lines[@line_index].bytesize == @byte_pointer
13101320
@buffer_of_lines[@line_index] += text

lib/reline/unicode.rb

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,13 @@ class Reline::Unicode
4343

4444
def self.escape_for_print(str)
4545
str.chars.map! { |gr|
46-
escaped = EscapedPairs[gr.ord]
47-
if escaped && gr != -"\n" && gr != -"\t"
48-
escaped
49-
else
46+
case gr
47+
when -"\n"
5048
gr
49+
when -"\t"
50+
-' '
51+
else
52+
EscapedPairs[gr.ord] || gr
5153
end
5254
}.join
5355
end

test/reline/yamatanooroti/test_rendering.rb

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -543,15 +543,10 @@ def test_no_escape_sequence_passed_to_dynamic_prompt
543543
EOC
544544
end
545545

546-
def test_enable_bracketed_paste
546+
def test_bracketed_paste
547547
omit if Reline.core.io_gate.win?
548-
write_inputrc <<~LINES
549-
set enable-bracketed-paste on
550-
LINES
551548
start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
552-
write("\e[200~,")
553-
write("def hoge\n 3\nend")
554-
write("\e[200~.")
549+
write("\e[200~def hoge\r\t3\rend\e[201~")
555550
close
556551
assert_screen(<<~EOC)
557552
Multiline REPL.

0 commit comments

Comments
 (0)