Skip to content

Commit 8f4277f

Browse files
tompngmatzbot
authored andcommitted
[ruby/reline] Add a timeout to cursor_pos
(ruby/reline#750) ruby/reline@dd4a654e5d
1 parent 9f47f0e commit 8f4277f

File tree

5 files changed

+59
-33
lines changed

5 files changed

+59
-33
lines changed

lib/reline.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ def ambiguous_width
412412
end
413413

414414
private def may_req_ambiguous_char_width
415-
@ambiguous_width = 2 if io_gate.dumb? || !STDIN.tty? || !STDOUT.tty?
415+
@ambiguous_width = 1 if io_gate.dumb? || !STDIN.tty? || !STDOUT.tty?
416416
return if defined? @ambiguous_width
417417
io_gate.move_cursor_column(0)
418418
begin
@@ -421,7 +421,7 @@ def ambiguous_width
421421
# LANG=C
422422
@ambiguous_width = 1
423423
else
424-
@ambiguous_width = io_gate.cursor_pos.x
424+
@ambiguous_width = io_gate.cursor_pos.x == 2 ? 2 : 1
425425
end
426426
io_gate.move_cursor_column(0)
427427
io_gate.erase_after_cursor

lib/reline/io/ansi.rb

Lines changed: 20 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -245,39 +245,30 @@ def set_screen_size(rows, columns)
245245
self
246246
end
247247

248-
def cursor_pos
249-
if both_tty?
250-
res = +''
251-
m = nil
252-
@input.raw do |stdin|
253-
@output << "\e[6n"
254-
@output.flush
255-
loop do
256-
c = stdin.getc
257-
next if c.nil?
258-
res << c
259-
m = res.match(/\e\[(?<row>\d+);(?<column>\d+)R/)
260-
break if m
261-
end
262-
(m.pre_match + m.post_match).chars.reverse_each do |ch|
263-
stdin.ungetc ch
248+
private def cursor_pos_internal(timeout:)
249+
match = nil
250+
@input.raw do |stdin|
251+
@output << "\e[6n"
252+
@output.flush
253+
timeout_at = Time.now + timeout
254+
buf = +''
255+
while (wait = timeout_at - Time.now) > 0 && stdin.wait_readable(wait)
256+
buf << stdin.readpartial(1024)
257+
if (match = buf.match(/\e\[(?<row>\d+);(?<column>\d+)R/))
258+
buf = match.pre_match + match.post_match
259+
break
264260
end
265261
end
266-
column = m[:column].to_i - 1
267-
row = m[:row].to_i - 1
268-
else
269-
begin
270-
buf = @output.pread(@output.pos, 0)
271-
row = buf.count("\n")
272-
column = buf.rindex("\n") ? (buf.size - buf.rindex("\n")) - 1 : 0
273-
rescue Errno::ESPIPE, IOError
274-
# Just returns column 1 for ambiguous width because this I/O is not
275-
# tty and can't seek.
276-
row = 0
277-
column = 1
262+
buf.chars.reverse_each do |ch|
263+
stdin.ungetc ch
278264
end
279265
end
280-
Reline::CursorPos.new(column, row)
266+
[match[:column].to_i - 1, match[:row].to_i - 1] if match
267+
end
268+
269+
def cursor_pos
270+
col, row = cursor_pos_internal(timeout: 0.5) if both_tty?
271+
Reline::CursorPos.new(col || 0, row || 0)
281272
end
282273

283274
def both_tty?

lib/reline/io/dumb.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def get_screen_size
6060
end
6161

6262
def cursor_pos
63-
Reline::CursorPos.new(1, 1)
63+
Reline::CursorPos.new(0, 0)
6464
end
6565

6666
def hide_cursor

test/reline/test_reline.rb

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
require_relative 'helper'
22
require 'reline'
33
require 'stringio'
4+
begin
5+
require "pty"
6+
rescue LoadError # some platforms don't support PTY
7+
end
48

59
class Reline::Test < Reline::TestCase
610
class DummyCallbackObject
@@ -432,6 +436,37 @@ def win?
432436
/mswin|mingw/.match?(RUBY_PLATFORM)
433437
end
434438

439+
def test_tty_amibuous_width
440+
omit unless defined?(PTY)
441+
ruby_file = Tempfile.create('rubyfile')
442+
ruby_file.write(<<~RUBY)
443+
require 'reline'
444+
Thread.new { sleep 2; puts 'timeout'; exit }
445+
p [Reline.ambiguous_width, gets.chomp]
446+
RUBY
447+
ruby_file.close
448+
lib = File.expand_path('../../lib', __dir__)
449+
cmd = [{ 'TERM' => 'xterm' }, 'ruby', '-I', lib, ruby_file.to_path]
450+
451+
# Calculate ambiguous width from cursor position
452+
[1, 2].each do |ambiguous_width|
453+
PTY.spawn(*cmd) do |r, w, pid|
454+
loop { break if r.readpartial(1024).include?("\e[6n") }
455+
w.puts "hello\e[10;#{ambiguous_width + 1}Rworld"
456+
assert_include(r.gets, [ambiguous_width, 'helloworld'].inspect)
457+
Process.waitpid pid
458+
end
459+
end
460+
461+
# Ambiguous width = 1 when cursor pos timed out
462+
PTY.spawn(*cmd) do |r, w, pid|
463+
loop { break if r.readpartial(1024).include?("\e[6n") }
464+
w.puts "hello\e[10;2Sworld"
465+
assert_include(r.gets, [1, "hello\e[10;2Sworld"].inspect)
466+
Process.waitpid pid
467+
end
468+
end
469+
435470
def get_reline_encoding
436471
if encoding = Reline.core.encoding
437472
encoding

test/reline/test_unicode.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def test_get_mbchar_width
1515
end
1616

1717
def test_ambiguous_width
18-
assert_equal 2, Reline::Unicode.calculate_width('√', true)
18+
assert_equal 1, Reline::Unicode.calculate_width('√', true)
1919
end
2020

2121
def test_csi_regexp

0 commit comments

Comments
 (0)