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

Implement buffered output to Reline::ANSI #790

Merged
merged 1 commit into from
Dec 2, 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
5 changes: 1 addition & 4 deletions lib/reline.rb
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,7 @@ def input=(val)
def output=(val)
raise TypeError unless val.respond_to?(:write) or val.nil?
@output = val
if io_gate.respond_to?(:output=)
io_gate.output = val
end
io_gate.output = val
end

def vi_editing_mode
Expand Down Expand Up @@ -317,7 +315,6 @@ def readline(prompt = '', add_hist = false)
else
line_editor.multiline_off
end
line_editor.output = output
line_editor.completion_proc = completion_proc
line_editor.completion_append_character = completion_append_character
line_editor.output_modifier_proc = output_modifier_proc
Expand Down
49 changes: 30 additions & 19 deletions lib/reline/io/ansi.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,13 @@ class Reline::ANSI < Reline::IO
'H' => [:ed_move_to_beg, {}],
}

attr_writer :input, :output

def initialize
@input = STDIN
@output = STDOUT
@buf = []
@output_buffer = nil
@old_winch_handler = nil
end

Expand Down Expand Up @@ -114,14 +117,6 @@ def set_default_key_bindings_comprehensive_list(config)
end
end

def input=(val)
@input = val
end

def output=(val)
@output = val
end

def with_raw_input
if @input.tty?
@input.raw(intr: true) { yield }
Expand Down Expand Up @@ -238,49 +233,65 @@ def both_tty?
@input.tty? && @output.tty?
end

def write(string)
if @output_buffer
@output_buffer << string
else
@output.write(string)
end
end

def buffered_output
@output_buffer = +''
yield
@output.write(@output_buffer)
ensure
@output_buffer = nil
end

def move_cursor_column(x)
@output.write "\e[#{x + 1}G"
write "\e[#{x + 1}G"
end

def move_cursor_up(x)
if x > 0
@output.write "\e[#{x}A"
write "\e[#{x}A"
elsif x < 0
move_cursor_down(-x)
end
end

def move_cursor_down(x)
if x > 0
@output.write "\e[#{x}B"
write "\e[#{x}B"
elsif x < 0
move_cursor_up(-x)
end
end

def hide_cursor
@output.write "\e[?25l"
write "\e[?25l"
end

def show_cursor
@output.write "\e[?25h"
write "\e[?25h"
end

def erase_after_cursor
@output.write "\e[K"
write "\e[K"
end

# This only works when the cursor is at the bottom of the scroll range
# For more details, see https://github.com/ruby/reline/pull/577#issuecomment-1646679623
def scroll_down(x)
return if x.zero?
# We use `\n` instead of CSI + S because CSI + S would cause https://github.com/ruby/reline/issues/576
@output.write "\n" * x
write "\n" * x
end

def clear_screen
@output.write "\e[2J"
@output.write "\e[1;1H"
write "\e[2J"
write "\e[1;1H"
end

def set_winch_handler(&handler)
Expand All @@ -300,14 +311,14 @@ def set_winch_handler(&handler)

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

def deprep(otio)
# Disable bracketed paste
@output.write "\e[?2004l" if Reline.core.config.enable_bracketed_paste && both_tty?
write "\e[?2004l" if Reline.core.config.enable_bracketed_paste && both_tty?
Signal.trap('WINCH', @old_winch_handler) if @old_winch_handler
Signal.trap('CONT', @old_cont_handler) if @old_cont_handler
end
Expand Down
11 changes: 11 additions & 0 deletions lib/reline/io/dumb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
class Reline::Dumb < Reline::IO
RESET_COLOR = '' # Do not send color reset sequence

attr_writer :output

def initialize(encoding: nil)
@input = STDIN
@output = STDOUT
@buf = []
@pasting = false
@encoding = encoding
Expand Down Expand Up @@ -39,6 +42,14 @@ def with_raw_input
yield
end

def write(string)
@output.write(string)
end

def buffered_output
yield
end

def getc(_timeout_second)
unless @buf.empty?
return @buf.shift
Expand Down
11 changes: 11 additions & 0 deletions lib/reline/io/windows.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
require 'fiddle/import'

class Reline::Windows < Reline::IO

attr_writer :output

def initialize
@input_buf = []
@output_buf = []
Expand Down Expand Up @@ -308,6 +311,14 @@ def with_raw_input
yield
end

def write(string)
@output.write(string)
end

def buffered_output
yield
end

def getc(_timeout_second)
check_input_event
@output_buf.shift
Expand Down
25 changes: 14 additions & 11 deletions lib/reline/line_editor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ class Reline::LineEditor
attr_accessor :prompt_proc
attr_accessor :auto_indent_proc
attr_accessor :dig_perfect_match_proc
attr_writer :output

VI_MOTIONS = %i{
ed_prev_char
Expand Down Expand Up @@ -414,7 +413,7 @@ def render_line_differential(old_items, new_items)
# do nothing
elsif level == :blank
Reline::IOGate.move_cursor_column base_x
@output.write "#{Reline::IOGate.reset_color_sequence}#{' ' * width}"
Reline::IOGate.write "#{Reline::IOGate.reset_color_sequence}#{' ' * width}"
else
x, w, content = new_items[level]
cover_begin = base_x != 0 && new_levels[base_x - 1] == level
Expand All @@ -424,7 +423,7 @@ def render_line_differential(old_items, new_items)
content, pos = Reline::Unicode.take_mbchar_range(content, base_x - x, width, cover_begin: cover_begin, cover_end: cover_end, padding: true)
end
Reline::IOGate.move_cursor_column x + pos
@output.write "#{Reline::IOGate.reset_color_sequence}#{content}#{Reline::IOGate.reset_color_sequence}"
Reline::IOGate.write "#{Reline::IOGate.reset_color_sequence}#{content}#{Reline::IOGate.reset_color_sequence}"
end
base_x += width
end
Expand Down Expand Up @@ -460,19 +459,21 @@ def update_dialogs(key = nil)
end

def render_finished
render_differential([], 0, 0)
lines = @buffer_of_lines.size.times.map do |i|
line = Reline::Unicode.strip_non_printing_start_end(prompt_list[i]) + modified_lines[i]
wrapped_lines = split_line_by_width(line, screen_width)
wrapped_lines.last.empty? ? "#{line} " : line
Reline::IOGate.buffered_output do
render_differential([], 0, 0)
lines = @buffer_of_lines.size.times.map do |i|
line = Reline::Unicode.strip_non_printing_start_end(prompt_list[i]) + modified_lines[i]
wrapped_lines = split_line_by_width(line, screen_width)
wrapped_lines.last.empty? ? "#{line} " : line
end
Reline::IOGate.write lines.map { |l| "#{l}\r\n" }.join
end
@output.puts lines.map { |l| "#{l}\r\n" }.join
end

def print_nomultiline_prompt
Reline::IOGate.disable_auto_linewrap(true) if Reline::IOGate.win?
# Readline's test `TestRelineAsReadline#test_readline` requires first output to be prompt, not cursor reset escape sequence.
@output.write Reline::Unicode.strip_non_printing_start_end(@prompt) if @prompt && !@is_multiline
Reline::IOGate.write Reline::Unicode.strip_non_printing_start_end(@prompt) if @prompt && !@is_multiline
ensure
Reline::IOGate.disable_auto_linewrap(false) if Reline::IOGate.win?
end
Expand Down Expand Up @@ -503,7 +504,9 @@ def render
end
end

render_differential new_lines, wrapped_cursor_x, wrapped_cursor_y - screen_scroll_top
Reline::IOGate.buffered_output do
render_differential new_lines, wrapped_cursor_x, wrapped_cursor_y - screen_scroll_top
end
end

# Reflects lines to be rendered and new cursor position to the screen
Expand Down
5 changes: 4 additions & 1 deletion test/reline/test_line_editor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ def test_retrieve_completion_quote

class RenderLineDifferentialTest < Reline::TestCase
class TestIO < Reline::IO
def write(string)
@output << string
end

def move_cursor_column(col)
@output << "[COL_#{col}]"
end
Expand All @@ -76,7 +80,6 @@ def setup
@original_iogate = Reline::IOGate
@output = StringIO.new
@line_editor.instance_variable_set(:@screen_size, [24, 80])
@line_editor.instance_variable_set(:@output, @output)
Reline.send(:remove_const, :IOGate)
Reline.const_set(:IOGate, TestIO.new)
Reline::IOGate.instance_variable_set(:@output, @output)
Expand Down
2 changes: 1 addition & 1 deletion test/reline/test_macro.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ def setup
@config = Reline::Config.new
@encoding = Reline.core.encoding
@line_editor = Reline::LineEditor.new(@config)
@output = @line_editor.output = File.open(IO::NULL, "w")
@output = Reline::IOGate.output = File.open(IO::NULL, "w")
end

def teardown
Expand Down
Loading