Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions lib/reline.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
53 changes: 22 additions & 31 deletions lib/reline/ansi.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q: Bracketed paste doesn't seem like a keybinding since it's sent from the terminal, not the user. Why is it set here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bracketed paste start sequence \e[200~ is very similar to some keys: kf9=\E[20~, knp=\E[6~, kpp=\E[5~.
We can't distinguish them until we read the 5th or 6th bytes. So treating them as same as keybinding is one of the simple way to implement it.
We can think key bindings (processed in class KeyStroke) as a transformation layer of byte stream (includes many kind of escape sequences) to meaningful symbols.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. It's similar to the terminal capabilities. Your explanation that it deals with byte streams made sense. Thanks!

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
Expand Down Expand Up @@ -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?
Expand Down Expand Up @@ -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
Expand Up @@ -70,6 +70,7 @@ def initialize
@autocompletion = false
@convert_meta = true if seven_bit_encoding?(Reline::IOGate.encoding)
@loaded = false
@enable_bracketed_paste = true
end

def reset
Expand Down
12 changes: 11 additions & 1 deletion lib/reline/line_editor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,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
Expand Down Expand Up @@ -1331,6 +1331,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
Expand Down
10 changes: 6 additions & 4 deletions lib/reline/unicode.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 2 additions & 7 deletions test/reline/yamatanooroti/test_rendering.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down