Skip to content
Open
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
11 changes: 6 additions & 5 deletions lib/reline.rb
Original file line number Diff line number Diff line change
Expand Up @@ -247,14 +247,14 @@ def get_screen_size
} # :nodoc:
Reline::DEFAULT_DIALOG_CONTEXT = Array.new # :nodoc:

def readmultiline(prompt = '', add_hist = false, &confirm_multiline_termination)
def readmultiline(_prompt = '', _add_hist = false, prompt: _prompt, add_hist: _add_hist, rprompt: nil, &confirm_multiline_termination)
@mutex.synchronize do
unless confirm_multiline_termination
raise ArgumentError.new('#readmultiline needs block to confirm multiline termination')
end

io_gate.with_raw_input do
inner_readline(prompt, add_hist, true, &confirm_multiline_termination)
inner_readline(prompt, add_hist, true, rprompt: rprompt, &confirm_multiline_termination)
end

whole_buffer = line_editor.whole_buffer.dup
Expand All @@ -273,10 +273,10 @@ def readmultiline(prompt = '', add_hist = false, &confirm_multiline_termination)
end
end

def readline(prompt = '', add_hist = false)
def readline(_prompt = '', _add_hist = false, prompt: _prompt, add_hist: _add_hist, rprompt: nil)
@mutex.synchronize do
io_gate.with_raw_input do
inner_readline(prompt, add_hist, false)
inner_readline(prompt, add_hist, false, rprompt: rprompt)
end

line = line_editor.line.dup
Expand All @@ -290,7 +290,7 @@ def readline(prompt = '', add_hist = false)
end
end

private def inner_readline(prompt, add_hist, multiline, &confirm_multiline_termination)
private def inner_readline(prompt, add_hist, multiline, rprompt: nil, &confirm_multiline_termination)
if ENV['RELINE_STDERR_TTY']
if io_gate.win?
$stderr = File.open(ENV['RELINE_STDERR_TTY'], 'a')
Expand Down Expand Up @@ -323,6 +323,7 @@ def readline(prompt = '', add_hist = false)
line_editor.prompt_proc = prompt_proc
line_editor.auto_indent_proc = auto_indent_proc
line_editor.dig_perfect_match_proc = dig_perfect_match_proc
line_editor.rprompt = rprompt&.encode(encoding)

pre_input_hook&.call

Expand Down
19 changes: 17 additions & 2 deletions lib/reline/line_editor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class Reline::LineEditor
attr_accessor :prompt_proc
attr_accessor :auto_indent_proc
attr_accessor :dig_perfect_match_proc
attr_accessor :rprompt

VI_MOTIONS = %i{
ed_prev_char
Expand Down Expand Up @@ -476,6 +477,20 @@ def render
prompt_width = Reline::Unicode.calculate_width(prompt, true)
[[0, prompt_width, prompt], [prompt_width, Reline::Unicode.calculate_width(line, true), line]]
end

# Add rprompt to the first visible line if set and there's room
if @rprompt && !@rprompt.empty? && new_lines[0]
rprompt_width = Reline::Unicode.calculate_width(@rprompt, true)
right_col = screen_width - rprompt_width
first_line = new_lines[0]
# Calculate the end of the current content (prompt + input)
content_end = first_line.sum { |_, width, _| width }
# Only show rprompt if there's at least 1 char gap between content and rprompt
if right_col > content_end
first_line << [right_col, rprompt_width, @rprompt]
end
end

if @menu_info
@menu_info.lines(screen_width).each do |item|
new_lines << [[0, Reline::Unicode.calculate_width(item), item]]
Expand All @@ -491,8 +506,8 @@ def render
next if row < 0 || row >= screen_height

dialog_rows = new_lines[row] ||= []
# index 0 is for prompt, index 1 is for line, index 2.. is for dialog
dialog_rows[index + 2] = [x_range.begin, dialog.width, dialog.contents[row - y_range.begin]]
# index 0 is for prompt, index 1 is for line, index 2 is for rprompt, index 3.. is for dialog
dialog_rows[index + 3] = [x_range.begin, dialog.width, dialog.contents[row - y_range.begin]]
end
end

Expand Down
36 changes: 36 additions & 0 deletions test/reline/test_reline.rb
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,42 @@ def test_pre_input_hook
assert_equal(l, Reline.pre_input_hook)
end

def test_readline_with_rprompt
pend if win?
lib = File.expand_path("../../lib", __dir__)
code = "p result: Reline.readline('>', rprompt: '[TIME]')"
out = IO.popen([Reline.test_rubybin, "-I#{lib}", "-rreline", "-e", code], "r+") do |io|
io.write "a\n"
io.close_write
io.read
end
assert_include(out, { result: 'a' }.inspect)
end

def test_readline_with_keyword_arguments
pend if win?
lib = File.expand_path("../../lib", __dir__)
code = "p result: Reline.readline(prompt: '>', add_hist: true, rprompt: '[TIME]')"
out = IO.popen([Reline.test_rubybin, "-I#{lib}", "-rreline", "-e", code], "r+") do |io|
io.write "a\n"
io.close_write
io.read
end
assert_include(out, { result: 'a' }.inspect)
end

def test_readmultiline_with_keyword_arguments
pend if win?
lib = File.expand_path("../../lib", __dir__)
code = "p result: Reline.readmultiline(prompt: '>', add_hist: true, rprompt: '[TIME]') { true }"
out = IO.popen([Reline.test_rubybin, "-I#{lib}", "-rreline", "-e", code], "r+") do |io|
io.write "a\n"
io.close_write
io.read
end
assert_include(out, { result: 'a' }.inspect)
end

def test_dig_perfect_match_proc
assert_equal(nil, Reline.dig_perfect_match_proc)

Expand Down
6 changes: 5 additions & 1 deletion test/reline/yamatanooroti/multiline_repl
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,10 @@ opt.on('--autocomplete-width-long') {
}.select{ |c| c.start_with?(target) }
}
}
rprompt = nil
opt.on('--rprompt VAL') { |v|
rprompt = v
}
opt.parse!(ARGV)

begin
Expand All @@ -222,7 +226,7 @@ end
begin
prompt = ENV['RELINE_TEST_PROMPT'] || "\e[1mprompt>\e[m "
puts 'Multiline REPL.'
while code = Reline.readmultiline(prompt, true) { |code| TerminationChecker.terminated?(code) }
while code = Reline.readmultiline(prompt, true, rprompt: rprompt) { |code| TerminationChecker.terminated?(code) }
case code.chomp
when 'exit', 'quit', 'q'
exit 0
Expand Down
29 changes: 29 additions & 0 deletions test/reline/yamatanooroti/test_rendering.rb
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,35 @@ def test_prompt
close
end

def test_rprompt
start_terminal(5, 40, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --rprompt [RPROMPT]}, startup_message: 'Multiline REPL.')
assert_screen(<<~EOC)
Multiline REPL.
prompt> [RPROMPT]
EOC
close
end

def test_rprompt_with_input
start_terminal(5, 40, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --rprompt [RPROMPT]}, startup_message: 'Multiline REPL.')
write("hello")
assert_screen(<<~EOC)
Multiline REPL.
prompt> hello [RPROMPT]
EOC
close
end

def test_rprompt_hides_when_input_reaches_rprompt
start_terminal(5, 40, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --rprompt [RPROMPT]}, startup_message: 'Multiline REPL.')
write("a" * 30)
assert_screen(<<~EOC)
Multiline REPL.
prompt> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
EOC
close
end

def test_mode_string_emacs
write_inputrc <<~LINES
set show-mode-in-prompt on
Expand Down