[ruby/reline] Add a timeout to cursor_pos

(https://github.com/ruby/reline/pull/750)

https://github.com/ruby/reline/commit/dd4a654e5d
This commit is contained in:
tomoya ishida 2024-10-03 02:36:30 +09:00 committed by git
parent 9f47f0eb3c
commit 8f4277f405
5 changed files with 59 additions and 33 deletions

View File

@ -412,7 +412,7 @@ module Reline
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
@ -421,7 +421,7 @@ module Reline
# 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

View File

@ -245,39 +245,30 @@ class Reline::ANSI < Reline::IO
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?

View File

@ -60,7 +60,7 @@ class Reline::Dumb < Reline::IO
end
def cursor_pos
Reline::CursorPos.new(1, 1)
Reline::CursorPos.new(0, 0)
end
def hide_cursor

View File

@ -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
@ -432,6 +436,37 @@ class Reline::Test < Reline::TestCase
/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

View File

@ -15,7 +15,7 @@ class Reline::Unicode::Test < Reline::TestCase
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