Skip to content

Commit

Permalink
Add a timeout to cursor_pos
Browse files Browse the repository at this point in the history
  • Loading branch information
tompng committed Sep 12, 2024
1 parent 5353924 commit b598d0a
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 33 deletions.
4 changes: 2 additions & 2 deletions lib/reline.rb
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ def ambiguous_width
end

private def may_req_ambiguous_char_width
@ambiguous_width = 2 if io_gate.dumb? || !STDIN.tty? || !STDOUT.tty?
@ambiguous_width = 1 if io_gate.dumb? || !STDIN.tty? || !STDOUT.tty?
return if defined? @ambiguous_width
io_gate.move_cursor_column(0)
begin
Expand All @@ -421,7 +421,7 @@ def ambiguous_width
# LANG=C
@ambiguous_width = 1
else
@ambiguous_width = io_gate.cursor_pos.x
@ambiguous_width = io_gate.cursor_pos.x == 2 ? 2 : 1
end
io_gate.move_cursor_column(0)
io_gate.erase_after_cursor
Expand Down
49 changes: 20 additions & 29 deletions lib/reline/io/ansi.rb
Original file line number Diff line number Diff line change
Expand Up @@ -245,39 +245,30 @@ def set_screen_size(rows, columns)
self
end

def cursor_pos
if both_tty?
res = +''
m = nil
@input.raw do |stdin|
@output << "\e[6n"
@output.flush
loop do
c = stdin.getc
next if c.nil?
res << c
m = res.match(/\e\[(?<row>\d+);(?<column>\d+)R/)
break if m
end
(m.pre_match + m.post_match).chars.reverse_each do |ch|
stdin.ungetc ch
private def cursor_pos_internal(timeout:)
match = nil
@input.raw do |stdin|
@output << "\e[6n"
@output.flush
timeout_at = Time.now + timeout
buf = +''
while (wait = timeout_at - Time.now) > 0 && stdin.wait_readable(wait)
buf << stdin.readpartial(1024)
if (match = buf.match(/\e\[(?<row>\d+);(?<column>\d+)R/))
buf = match.pre_match + match.post_match
break
end
end
column = m[:column].to_i - 1
row = m[:row].to_i - 1
else
begin
buf = @output.pread(@output.pos, 0)
row = buf.count("\n")
column = buf.rindex("\n") ? (buf.size - buf.rindex("\n")) - 1 : 0
rescue Errno::ESPIPE, IOError
# Just returns column 1 for ambiguous width because this I/O is not
# tty and can't seek.
row = 0
column = 1
buf.chars.reverse_each do |ch|
stdin.ungetc ch
end
end
Reline::CursorPos.new(column, row)
[match[:column].to_i - 1, match[:row].to_i - 1] if match
end

def cursor_pos
col, row = cursor_pos_internal(timeout: 0.5) if both_tty?
Reline::CursorPos.new(col || 0, row || 0)
end

def both_tty?
Expand Down
2 changes: 1 addition & 1 deletion lib/reline/io/dumb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def get_screen_size
end

def cursor_pos
Reline::CursorPos.new(1, 1)
Reline::CursorPos.new(0, 0)
end

def hide_cursor
Expand Down
35 changes: 35 additions & 0 deletions test/reline/test_reline.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
require_relative 'helper'
require 'reline'
require 'stringio'
begin
require "pty"
rescue LoadError # some platforms don't support PTY
end

class Reline::Test < Reline::TestCase
class DummyCallbackObject
Expand Down Expand Up @@ -432,6 +436,37 @@ def win?
/mswin|mingw/.match?(RUBY_PLATFORM)
end

def test_tty_amibuous_width
omit unless defined?(PTY)
ruby_file = Tempfile.create('rubyfile')
ruby_file.write(<<~RUBY)
require 'reline'
Thread.new { sleep 2; puts 'timeout'; exit }
p [Reline.ambiguous_width, gets.chomp]
RUBY
ruby_file.close
lib = File.expand_path('../../lib', __dir__)
cmd = [{ 'TERM' => 'xterm' }, 'ruby', '-I', lib, ruby_file.to_path]

# Calculate ambiguous width from cursor position
[1, 2].each do |ambiguous_width|
PTY.spawn(*cmd) do |r, w, pid|
loop { break if r.readpartial(1024).include?("\e[6n") }
w.puts "hello\e[10;#{ambiguous_width + 1}Rworld"
assert_include(r.gets, [ambiguous_width, 'helloworld'].inspect)
Process.waitpid pid
end
end

# Ambiguous width = 1 when cursor pos timed out
PTY.spawn(*cmd) do |r, w, pid|
loop { break if r.readpartial(1024).include?("\e[6n") }
w.puts "hello\e[10;2Sworld"
assert_include(r.gets, [1, "hello\e[10;2Sworld"].inspect)
Process.waitpid pid
end
end

def get_reline_encoding
if encoding = Reline.core.encoding
encoding
Expand Down
2 changes: 1 addition & 1 deletion test/reline/test_unicode.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def test_get_mbchar_width
end

def test_ambiguous_width
assert_equal 2, Reline::Unicode.calculate_width('√', true)
assert_equal 1, Reline::Unicode.calculate_width('√', true)
end

def test_csi_regexp
Expand Down

0 comments on commit b598d0a

Please sign in to comment.