Skip to content

Commit

Permalink
Add Reline as a fallback library for Readline
Browse files Browse the repository at this point in the history
* lib/reine.rb, lib/reline/*: Reline is a readline stdlib compatible
  library.
* lib/readline.rb: Readline uses a fallback to Reline when ext/readline
  doesn't exist.
* tool/sync_default_gems.rb: add ruby/reline as a default gem.
* appveyor.yml: add "set RELINE_TEST_ENCODING=Windows-31J" for test suit
  of Reline, and add "--exclude readline" to "nmake test-all" on Visual
  Studio builds because of strange behavior.
* spec/ruby/library/readline/spec_helper.rb: skip Reline as with
  RbReadline.
  • Loading branch information
aycabta committed Apr 30, 2019
1 parent eb45ba6 commit 17350c7
Show file tree
Hide file tree
Showing 26 changed files with 8,103 additions and 3 deletions.
4 changes: 3 additions & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,10 @@ for:
- if not "%GEMS_FOR_TEST%" == "" \usr\bin\gem install --no-document %GEMS_FOR_TEST%
test_script:
- set /a JOBS=%NUMBER_OF_PROCESSORS%
- set RELINE_TEST_ENCODING=Windows-31J
- nmake -l "TESTOPTS=-v -q" btest
- nmake -l "TESTOPTS=-v -q" test-basic
- nmake -l "TESTOPTS=-q --subprocess-timeout-scale=3.0 --excludes=../test/excludes/_appveyor -j%JOBS% --exclude win32ole --exclude test_bignum --exclude test_syntax --exclude test_open-uri --exclude test_bundled_ca --exclude test_gc_compact" test-all
- nmake -l "TESTOPTS=-q --subprocess-timeout-scale=3.0 --excludes=../test/excludes/_appveyor -j%JOBS% --exclude readline --exclude win32ole --exclude test_bignum --exclude test_syntax --exclude test_open-uri --exclude test_bundled_ca --exclude test_gc_compact" test-all
# separately execute tests without -j which may crash worker with -j.
- nmake -l "TESTOPTS=-v --subprocess-timeout-scale=3.0 --excludes=../test/excludes/_appveyor" test-all TESTS="../test/win32ole ../test/ruby/test_bignum.rb ../test/ruby/test_syntax.rb ../test/open-uri/test_open-uri.rb ../test/rubygems/test_bundled_ca.rb ../test/ruby/test_gc_compact.rb"
- nmake -l test-spec MSPECOPT=-fs # not using `-j` because sometimes `mspec -j` silently dies on Windows
Expand Down Expand Up @@ -108,6 +109,7 @@ for:
- mingw32-make DESTDIR=../install install-nodoc
- if not "%GEMS_FOR_TEST%" == "" ..\install\bin\gem install --no-document %GEMS_FOR_TEST%
test_script:
- set RELINE_TEST_ENCODING=Windows-31J
- mingw32-make test
- mingw32-make test-all TESTOPTS="--retry --job-status=normal --show-skip --subprocess-timeout-scale=1.5 --excludes=../ruby/test/excludes/_appveyor -j %JOBS% --exclude win32ole --exclude test_open-uri --exclude test_gc_compact"
# separately execute tests without -j which may crash worker with -j.
Expand Down
6 changes: 6 additions & 0 deletions lib/readline.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
begin
require 'readline.so'
rescue LoadError
require 'reline'
Readline = Reline
end
197 changes: 197 additions & 0 deletions lib/reline.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
require 'io/console'
require 'reline/version'
require 'reline/config'
require 'reline/key_actor'
require 'reline/key_stroke'
require 'reline/line_editor'

module Reline
extend self
FILENAME_COMPLETION_PROC = nil
USERNAME_COMPLETION_PROC = nil
HISTORY = Array.new

if RUBY_PLATFORM =~ /mswin|mingw/
IS_WINDOWS = true
else
IS_WINDOWS = false
end

CursorPos = Struct.new(:x, :y)

class << self
attr_accessor :basic_quote_characters
attr_accessor :completer_quote_characters
attr_accessor :completer_word_break_characters
attr_reader :completion_append_character
attr_accessor :completion_case_fold
attr_accessor :filename_quote_characters
attr_writer :input
attr_writer :output
end

@@ambiguous_width = nil
@@config = nil

@basic_quote_characters = '"\''
# TODO implement below
#@completer_quote_characters
#@completion_append_character
#@completion_case_fold
#@filename_quote_characters
def self.completion_append_character=(val)
if val.nil?
@completion_append_character = nil
elsif val.size == 1
@completion_append_character = val
elsif val.size > 1
@completion_append_character = val[0]
else
@completion_append_character = val
end
end

@@basic_word_break_characters = " \t\n`><=;|&{("
def self.basic_word_break_characters
@@basic_word_break_characters
end
def self.basic_word_break_characters=(v)
@@basic_word_break_characters = v
end

@@completer_word_break_characters = @@basic_word_break_characters.dup

@@completion_proc = nil
def self.completion_proc
@@completion_proc
end
def self.completion_proc=(p)
@@completion_proc = p
end

@@dig_perfect_match_proc = nil
def self.dig_perfect_match_proc
@@dig_perfect_match_proc
end
def self.dig_perfect_match_proc=(p)
@@dig_perfect_match_proc = p
end

if IS_WINDOWS
require 'reline/windows'
else
require 'reline/ansi'
end

def retrieve_completion_block(line, byte_pointer)
break_regexp = /[#{Regexp.escape(@@basic_word_break_characters)}]/
before_pointer = line.byteslice(0, byte_pointer)
break_point = before_pointer.rindex(break_regexp)
if break_point
preposing = before_pointer[0..(break_point)]
block = before_pointer[(break_point + 1)..-1]
else
preposing = ''
block = before_pointer
end
postposing = line.byteslice(byte_pointer, line.bytesize)
[preposing, block, postposing]
end

def readmultiline(prompt = '', add_hist = false, &confirm_multiline_termination)
if block_given?
inner_readline(prompt, add_hist, true, &confirm_multiline_termination)
else
inner_readline(prompt, add_hist, true)
end

if add_hist and @line_editor.whole_buffer and @line_editor.whole_buffer.chomp.size > 0
Reline::HISTORY << @line_editor.whole_buffer
end

@line_editor.whole_buffer
end

def readline(prompt = '', add_hist = false)
inner_readline(prompt, add_hist, false)

if add_hist and @line_editor.line and @line_editor.line.chomp.size > 0
Reline::HISTORY << @line_editor.line.chomp
end

@line_editor.line
end

def inner_readline(prompt, add_hist, multiline, &confirm_multiline_termination)
if @@config.nil?
@@config = Reline::Config.new
@@config.read
end
otio = prep

may_req_ambiguous_char_width
@line_editor = Reline::LineEditor.new(@@config, prompt)
if multiline
@line_editor.multiline_on
if block_given?
@line_editor.confirm_multiline_termination_proc = confirm_multiline_termination
end
end
@line_editor.completion_proc = @@completion_proc
@line_editor.dig_perfect_match_proc = @@dig_perfect_match_proc
@line_editor.retrieve_completion_block = method(:retrieve_completion_block)
@line_editor.rerender

if IS_WINDOWS
config = {
key_mapping: {
[224, 72] => :ed_prev_history, # ↑
[224, 80] => :ed_next_history, # ↓
[224, 77] => :ed_next_char, # →
[224, 75] => :ed_prev_char # ←
}
}
else
config = {
key_mapping: {
[27, 91, 65] => :ed_prev_history, # ↑
[27, 91, 66] => :ed_next_history, # ↓
[27, 91, 67] => :ed_next_char, # →
[27, 91, 68] => :ed_prev_char # ←
}
}
end

key_stroke = Reline::KeyStroke.new(config)
begin
while c = getc
key_stroke.input_to!(c)&.then { |inputs|
inputs.each { |c|
@line_editor.input_key(c)
@line_editor.rerender
}
}
break if @line_editor.finished?
end
Reline.move_cursor_column(0)
rescue StandardError => e
deprep(otio)
raise e
end

deprep(otio)
end

def may_req_ambiguous_char_width
return if @@ambiguous_width
Reline.move_cursor_column(0)
print "\u{25bd}"
@@ambiguous_width = Reline.cursor_pos.x
Reline.move_cursor_column(0)
Reline.erase_after_cursor
end

def self.ambiguous_width
@@ambiguous_width
end
end
87 changes: 87 additions & 0 deletions lib/reline/ansi.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
module Reline
def getc
c = nil
until c
return nil if @line_editor.finished?
result = select([$stdin], [], [], 0.1)
next if result.nil?
c = $stdin.read(1)
end
c.ord
end

def self.get_screen_size
$stdin.winsize
end

def self.set_screen_size(rows, columns)
$stdin.winsize = [rows, columns]
self
end

def self.cursor_pos
res = ''
$stdin.raw do |stdin|
$stdout << "\e[6n"
$stdout.flush
while (c = stdin.getc) != 'R'
res << c if c
end
end
m = res.match(/(?<row>\d+);(?<column>\d+)/)
CursorPos.new(m[:column].to_i - 1, m[:row].to_i - 1)
end

def self.move_cursor_column(x)
print "\e[#{x + 1}G"
end

def self.move_cursor_up(x)
if x > 0
print "\e[#{x}A" if x > 0
elsif x < 0
move_cursor_down(-x)
end
end

def self.move_cursor_down(x)
if x > 0
print "\e[#{x}B" if x > 0
elsif x < 0
move_cursor_up(-x)
end
end

def self.erase_after_cursor
print "\e[K"
end

def self.scroll_down(x)
return if x.zero?
print "\e[#{x}S"
end

def self.clear_screen
print "\e[2J"
print "\e[1;1H"
end

def prep
int_handle = Signal.trap('INT', 'IGNORE')
otio = `stty -g`.chomp
setting = ' -echo -icrnl cbreak'
if (`stty -a`.scan(/-parenb\b/).first == '-parenb')
setting << ' pass8'
end
setting << ' -ixoff'
`stty #{setting}`
Signal.trap('INT', int_handle)
otio
end

def deprep(otio)
int_handle = Signal.trap('INT', 'IGNORE')
`stty #{otio}`
Signal.trap('INT', int_handle)
end
end
Loading

0 comments on commit 17350c7

Please sign in to comment.