Skip to content

Commit

Permalink
Implement bracketed paste insert (#655)
Browse files Browse the repository at this point in the history
tompng authored May 8, 2024

Verified

This commit was signed with the committer’s verified signature. The key has expired.
rrbutani Rahul Butani
1 parent 328699e commit e92dcbf
Showing 6 changed files with 55 additions and 49 deletions.
19 changes: 13 additions & 6 deletions lib/reline.rb
Original file line number Diff line number Diff line change
@@ -312,6 +312,10 @@ def readline(prompt = '', add_hist = false)
$stderr.sync = true
$stderr.puts "Reline is used by #{Process.pid}"
end
unless config.test_mode or config.loaded?
config.read
io_gate.set_default_key_bindings(config)
end
otio = io_gate.prep

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

unless config.test_mode or config.loaded?
config.read
io_gate.set_default_key_bindings(config)
end

line_editor.print_nomultiline_prompt(prompt)
line_editor.update_dialogs
line_editor.rerender
@@ -352,7 +351,15 @@ def readline(prompt = '', add_hist = false)
loop do
read_io(config.keyseq_timeout) { |inputs|
line_editor.set_pasting_state(io_gate.in_pasting?)
inputs.each { |key| line_editor.update(key) }
inputs.each do |key|
if key.char == :bracketed_paste_start
text = io_gate.read_bracketed_paste
line_editor.insert_pasted_text(text)
line_editor.scroll_into_view
else
line_editor.update(key)
end
end
}
if line_editor.finished?
line_editor.render_finished
53 changes: 22 additions & 31 deletions lib/reline/ansi.rb
Original file line number Diff line number Diff line change
@@ -45,6 +45,7 @@ def self.win?
end

def self.set_default_key_bindings(config, allow_terminfo: true)
set_bracketed_paste_key_bindings(config)
set_default_key_bindings_ansi_cursor(config)
if allow_terminfo && Reline::Terminfo.enabled?
set_default_key_bindings_terminfo(config)
@@ -66,6 +67,12 @@ def self.set_default_key_bindings(config, allow_terminfo: true)
end
end

def self.set_bracketed_paste_key_bindings(config)
[:emacs, :vi_insert, :vi_command].each do |keymap|
config.add_default_key_binding_by_keymap(keymap, START_BRACKETED_PASTE.bytes, :bracketed_paste_start)
end
end

def self.set_default_key_bindings_ansi_cursor(config)
ANSI_CURSOR_KEY_BINDINGS.each do |char, (default_func, modifiers)|
bindings = [["\e[#{char}", default_func]] # CSI + char
@@ -178,46 +185,26 @@ def self.inner_getc(timeout_second)
nil
end

@@in_bracketed_paste_mode = false
START_BRACKETED_PASTE = String.new("\e[200~,", encoding: Encoding::ASCII_8BIT)
END_BRACKETED_PASTE = String.new("\e[200~.", encoding: Encoding::ASCII_8BIT)
def self.getc_with_bracketed_paste(timeout_second)
START_BRACKETED_PASTE = String.new("\e[200~", encoding: Encoding::ASCII_8BIT)
END_BRACKETED_PASTE = String.new("\e[201~", encoding: Encoding::ASCII_8BIT)
def self.read_bracketed_paste
buffer = String.new(encoding: Encoding::ASCII_8BIT)
buffer << inner_getc(timeout_second)
while START_BRACKETED_PASTE.start_with?(buffer) or END_BRACKETED_PASTE.start_with?(buffer) do
if START_BRACKETED_PASTE == buffer
@@in_bracketed_paste_mode = true
return inner_getc(timeout_second)
elsif END_BRACKETED_PASTE == buffer
@@in_bracketed_paste_mode = false
ungetc(-1)
return inner_getc(timeout_second)
end
succ_c = inner_getc(Reline.core.config.keyseq_timeout)

if succ_c
buffer << succ_c
else
break
end
until buffer.end_with?(END_BRACKETED_PASTE)
c = inner_getc(Float::INFINITY)
break unless c
buffer << c
end
buffer.bytes.reverse_each do |ch|
ungetc ch
end
inner_getc(timeout_second)
string = buffer.delete_suffix(END_BRACKETED_PASTE).force_encoding(encoding)
string.valid_encoding? ? string : ''
end

# if the usage expects to wait indefinitely, use Float::INFINITY for timeout_second
def self.getc(timeout_second)
if Reline.core.config.enable_bracketed_paste
getc_with_bracketed_paste(timeout_second)
else
inner_getc(timeout_second)
end
inner_getc(timeout_second)
end

def self.in_pasting?
@@in_bracketed_paste_mode or (not empty_buffer?)
not empty_buffer?
end

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

def self.prep
# Enable bracketed paste
@@output.write "\e[?2004h" if Reline.core.config.enable_bracketed_paste
retrieve_keybuffer
nil
end

def self.deprep(otio)
# Disable bracketed paste
@@output.write "\e[?2004l" if Reline.core.config.enable_bracketed_paste
Signal.trap('WINCH', @@old_winch_handler) if @@old_winch_handler
end
end
1 change: 1 addition & 0 deletions lib/reline/config.rb
Original file line number Diff line number Diff line change
@@ -51,6 +51,7 @@ def initialize
@autocompletion = false
@convert_meta = true if seven_bit_encoding?(Reline::IOGate.encoding)
@loaded = false
@enable_bracketed_paste = true
end

def reset
12 changes: 11 additions & 1 deletion lib/reline/line_editor.rb
Original file line number Diff line number Diff line change
@@ -283,7 +283,7 @@ def multiline_off
indent1 = @auto_indent_proc.(@buffer_of_lines.take(@line_index - 1).push(''), @line_index - 1, 0, true)
indent2 = @auto_indent_proc.(@buffer_of_lines.take(@line_index), @line_index - 1, @buffer_of_lines[@line_index - 1].bytesize, false)
indent = indent2 || indent1
@buffer_of_lines[@line_index - 1] = ' ' * indent + @buffer_of_lines[@line_index - 1].gsub(/\A */, '')
@buffer_of_lines[@line_index - 1] = ' ' * indent + @buffer_of_lines[@line_index - 1].gsub(/\A\s*/, '')
)
process_auto_indent @line_index, add_newline: true
else
@@ -1305,6 +1305,16 @@ def confirm_multiline_termination
@confirm_multiline_termination_proc.(temp_buffer.join("\n") + "\n")
end

def insert_pasted_text(text)
pre = @buffer_of_lines[@line_index].byteslice(0, @byte_pointer)
post = @buffer_of_lines[@line_index].byteslice(@byte_pointer..)
lines = (pre + text.gsub(/\r\n?/, "\n") + post).split("\n", -1)
lines << '' if lines.empty?
@buffer_of_lines[@line_index, 1] = lines
@line_index += lines.size - 1
@byte_pointer = @buffer_of_lines[@line_index].bytesize - post.bytesize
end

def insert_text(text)
if @buffer_of_lines[@line_index].bytesize == @byte_pointer
@buffer_of_lines[@line_index] += text
10 changes: 6 additions & 4 deletions lib/reline/unicode.rb
Original file line number Diff line number Diff line change
@@ -43,11 +43,13 @@ class Reline::Unicode

def self.escape_for_print(str)
str.chars.map! { |gr|
escaped = EscapedPairs[gr.ord]
if escaped && gr != -"\n" && gr != -"\t"
escaped
else
case gr
when -"\n"
gr
when -"\t"
-' '
else
EscapedPairs[gr.ord] || gr
end
}.join
end
9 changes: 2 additions & 7 deletions test/reline/yamatanooroti/test_rendering.rb
Original file line number Diff line number Diff line change
@@ -543,15 +543,10 @@ def test_no_escape_sequence_passed_to_dynamic_prompt
EOC
end

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

0 comments on commit e92dcbf

Please sign in to comment.