[ruby/reline] Implement bracketed paste insert

(https://github.com/ruby/reline/pull/655)

https://github.com/ruby/reline/commit/e92dcbf514
This commit is contained in:
tomoya ishida 2024-05-09 01:00:26 +09:00 committed by git
parent ad9c89fab8
commit 26446cccc9
6 changed files with 55 additions and 49 deletions

View File

@ -312,6 +312,10 @@ module Reline
$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 @@ module Reline
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 @@ module Reline
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

View File

@ -45,6 +45,7 @@ class Reline::ANSI
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 @@ class Reline::ANSI
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 @@ class Reline::ANSI
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 @@ class Reline::ANSI
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

View File

@ -51,6 +51,7 @@ class Reline::Config
@autocompletion = false
@convert_meta = true if seven_bit_encoding?(Reline::IOGate.encoding)
@loaded = false
@enable_bracketed_paste = true
end
def reset

View File

@ -283,7 +283,7 @@ class Reline::LineEditor
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 @@ class Reline::LineEditor
@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

View File

@ -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

View File

@ -543,15 +543,10 @@ begin
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.