diff --git a/lib/reline.rb b/lib/reline.rb index ddb0224180..4ba74d2cb2 100644 --- a/lib/reline.rb +++ b/lib/reline.rb @@ -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 diff --git a/lib/reline/io/ansi.rb b/lib/reline/io/ansi.rb index a730a953f7..82d2ee2371 100644 --- a/lib/reline/io/ansi.rb +++ b/lib/reline/io/ansi.rb @@ -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\[(?\d+);(?\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\[(?\d+);(?\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? diff --git a/lib/reline/io/dumb.rb b/lib/reline/io/dumb.rb index 6ed69ffdfa..6a10af4fef 100644 --- a/lib/reline/io/dumb.rb +++ b/lib/reline/io/dumb.rb @@ -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 diff --git a/test/reline/test_reline.rb b/test/reline/test_reline.rb index 515805467d..22437eef9b 100644 --- a/test/reline/test_reline.rb +++ b/test/reline/test_reline.rb @@ -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 diff --git a/test/reline/test_unicode.rb b/test/reline/test_unicode.rb index 688d25e238..6aea8df9bb 100644 --- a/test/reline/test_unicode.rb +++ b/test/reline/test_unicode.rb @@ -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