Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle INT signal correctly #646

Merged
merged 1 commit into from
Apr 3, 2024
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
35 changes: 19 additions & 16 deletions lib/reline.rb
Original file line number Diff line number Diff line change
Expand Up @@ -264,32 +264,35 @@ def get_screen_size
Reline::DEFAULT_DIALOG_CONTEXT = Array.new

def readmultiline(prompt = '', add_hist = false, &confirm_multiline_termination)
unless confirm_multiline_termination
tompng marked this conversation as resolved.
Show resolved Hide resolved
raise ArgumentError.new('#readmultiline needs block to confirm multiline termination')
end

Reline.update_iogate
io_gate.with_raw_input do
unless confirm_multiline_termination
raise ArgumentError.new('#readmultiline needs block to confirm multiline termination')
end
inner_readline(prompt, add_hist, true, &confirm_multiline_termination)
end

whole_buffer = line_editor.whole_buffer.dup
whole_buffer.taint if RUBY_VERSION < '2.7'
if add_hist and whole_buffer and whole_buffer.chomp("\n").size > 0
Reline::HISTORY << whole_buffer
end
whole_buffer = line_editor.whole_buffer.dup
whole_buffer.taint if RUBY_VERSION < '2.7'
Copy link
Member

Choose a reason for hiding this comment

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

Is the taint call necessary for Ruby 2.6? If not, I think it's fine to remove it now as well.

Copy link
Member Author

Choose a reason for hiding this comment

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

Kernel#gets and other string read from IO are tainted in Ruby 2.6 https://docs.ruby-lang.org/en/2.6.0/Object.html#method-i-taint
Reline should return tainted string to align with Kernel.gets.
I'm not sure of the impact of removing taint. Perhaps nobody is using tainted?, but I'd like to keep this until dropping 2.6 support.

# Code relying on taint security model
Foo::Settings.map do |s|
  if s.tainted?
    s=~/\A\d+\z/ ? s.to_i : s
  else
    eval s
  end
end

if add_hist and whole_buffer and whole_buffer.chomp("\n").size > 0
Reline::HISTORY << whole_buffer
end

if line_editor.eof?
line_editor.reset_line
# Return nil if the input is aborted by C-d.
nil
else
whole_buffer
end
if line_editor.eof?
line_editor.reset_line
# Return nil if the input is aborted by C-d.
nil
else
whole_buffer
end
end

def readline(prompt = '', add_hist = false)
Reline.update_iogate
inner_readline(prompt, add_hist, false)
io_gate.with_raw_input do
inner_readline(prompt, add_hist, false)
end

line = line_editor.line.dup
line.taint if RUBY_VERSION < '2.7'
Expand Down
14 changes: 10 additions & 4 deletions lib/reline/ansi.rb
Original file line number Diff line number Diff line change
Expand Up @@ -151,19 +151,25 @@ def self.output=(val)
end

def self.with_raw_input
@@input.raw { yield }
if @@input.tty?
@@input.raw(intr: true) { yield }
else
yield
end
end

@@buf = []
def self.inner_getc(timeout_second)
unless @@buf.empty?
return @@buf.shift
end
until c = @@input.raw(intr: true) { @@input.wait_readable(0.1) && @@input.getbyte }
timeout_second -= 0.1
until @@input.wait_readable(0.01)
timeout_second -= 0.01
return nil if timeout_second <= 0
Reline.core.line_editor.resize

Reline.core.line_editor.handle_signal
end
c = @@input.getbyte
(c == 0x16 && @@input.raw(min: 0, time: 0, &:getbyte)) || c
rescue Errno::EIO
# Maybe the I/O has been closed.
Expand Down
1 change: 1 addition & 0 deletions lib/reline/general_io.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def self.getc(_timeout_second)
end
c = nil
loop do
Reline.core.line_editor.handle_signal
result = @@input.wait_readable(0.1)
next if result.nil?
c = @@input.read(1)
Expand Down
76 changes: 40 additions & 36 deletions lib/reline/line_editor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,6 @@ def reset(prompt = '', encoding:)
@screen_size = Reline::IOGate.get_screen_size
reset_variables(prompt, encoding: encoding)
@rendered_screen.base_y = Reline::IOGate.cursor_pos.y
Reline::IOGate.set_winch_handler do
@resized = true
end
if ENV.key?('RELINE_ALT_SCROLLBAR')
@full_block = '::'
@upper_half_block = "''"
Expand All @@ -143,7 +140,12 @@ def reset(prompt = '', encoding:)
end
end

def resize
def handle_signal
handle_interrupted
handle_resized
end

private def handle_resized
return unless @resized

@screen_size = Reline::IOGate.get_screen_size
Expand All @@ -156,25 +158,35 @@ def resize
render_differential
end

private def handle_interrupted
return unless @interrupted

@interrupted = false
clear_dialogs
scrolldown = render_differential
Reline::IOGate.scroll_down scrolldown
Reline::IOGate.move_cursor_column 0
@rendered_screen.lines = []
@rendered_screen.cursor_y = 0
case @old_trap
when 'DEFAULT', 'SYSTEM_DEFAULT'
raise Interrupt
when 'IGNORE'
# Do nothing
when 'EXIT'
exit
else
@old_trap.call if @old_trap.respond_to?(:call)
end
end

def set_signal_handlers
@old_trap = Signal.trap('INT') {
clear_dialogs
scrolldown = render_differential
Reline::IOGate.scroll_down scrolldown
Reline::IOGate.move_cursor_column 0
@rendered_screen.lines = []
@rendered_screen.cursor_y = 0
case @old_trap
when 'DEFAULT', 'SYSTEM_DEFAULT'
raise Interrupt
when 'IGNORE'
# Do nothing
when 'EXIT'
exit
else
@old_trap.call if @old_trap.respond_to?(:call)
end
}
Reline::IOGate.set_winch_handler do
@resized = true
end
@old_trap = Signal.trap('INT') do
@interrupted = true
end
end

def finalize
Expand All @@ -191,7 +203,6 @@ def reset_variables(prompt = '', encoding:)
@encoding = encoding
@is_multiline = false
@finished = false
@cleared = false
@history_pointer = nil
@kill_ring ||= Reline::KillRing.new
@vi_clipboard = ''
Expand All @@ -213,6 +224,7 @@ def reset_variables(prompt = '', encoding:)
@in_pasting = false
@auto_indent_proc = nil
@dialogs = []
@interrupted = false
@resized = false
@cache = {}
@rendered_screen = RenderedScreen.new(base_y: 0, lines: [], cursor_y: 0)
Expand Down Expand Up @@ -520,19 +532,7 @@ def rest_height(wrapped_cursor_y)
screen_height - wrapped_cursor_y + screen_scroll_top - @rendered_screen.base_y - 1
end

def handle_cleared
return unless @cleared

@cleared = false
Reline::IOGate.clear_screen
@screen_size = Reline::IOGate.get_screen_size
@rendered_screen.lines = []
@rendered_screen.base_y = 0
@rendered_screen.cursor_y = 0
end

def rerender
handle_cleared
render_differential unless @in_pasting
end

Expand Down Expand Up @@ -2046,7 +2046,11 @@ def finish
alias_method :yank_pop, :em_yank_pop

private def ed_clear_screen(key)
@cleared = true
Reline::IOGate.clear_screen
@screen_size = Reline::IOGate.get_screen_size
@rendered_screen.lines = []
@rendered_screen.base_y = 0
@rendered_screen.cursor_y = 0
end
alias_method :clear_screen, :ed_clear_screen

Expand Down
2 changes: 1 addition & 1 deletion lib/reline/windows.rb
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ def self.process_key_event(repeat_count, virtual_key_code, virtual_scan_code, ch
def self.check_input_event
num_of_events = 0.chr * 8
while @@output_buf.empty?
Reline.core.line_editor.resize
Reline.core.line_editor.handle_signal
if @@WaitForSingleObject.(@@hConsoleInputHandle, 100) != 0 # max 0.1 sec
# prevent for background consolemode change
@@legacy_console = (getconsolemode() & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0)
Expand Down
8 changes: 4 additions & 4 deletions test/reline/test_key_actor_emacs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -255,18 +255,18 @@ def test_em_delete_ends_editing
end

def test_ed_clear_screen
refute(@line_editor.instance_variable_get(:@cleared))
@line_editor.instance_variable_get(:@rendered_screen).lines = [[]]
input_keys("\C-l", false)
assert(@line_editor.instance_variable_get(:@cleared))
assert_empty(@line_editor.instance_variable_get(:@rendered_screen).lines)
end

def test_ed_clear_screen_with_inputed
input_keys('abc')
input_keys("\C-b", false)
refute(@line_editor.instance_variable_get(:@cleared))
@line_editor.instance_variable_get(:@rendered_screen).lines = [[]]
assert_line_around_cursor('ab', 'c')
input_keys("\C-l", false)
assert(@line_editor.instance_variable_get(:@cleared))
assert_empty(@line_editor.instance_variable_get(:@rendered_screen).lines)
assert_line_around_cursor('ab', 'c')
end

Expand Down
Loading