Ruby 2.7 backport about IRB (#2990)

* [ruby/reline] Sort completion list

#### Legacy mode:

  ```console
  $ irb --legacy
  irb(main):001:0> l[TAB][TAB]
  lambda           load             local_variables  loop
  ```

#### Before this patch:

  ```console
  $ irb
  irb(main):001:0> l[TAB][TAB]
  local_variables
  loop
  lambda
  load
  ```

#### After this patch:

  ```console
  $ irb
  irb(main):001:0> l[TAB][TAB]
  lambda
  load
  local_variables
  loop
  ```

6074069c7d

* Drop an invalid char as UTF-8

* Add test_completion_with_indent_and_completer_quote_characters

This is for 8a705245e5.

* [ruby/irb] Add tests for RubyLex

The set_auto_indent method calculates the correct number of spaces for
indenting a line. We think there might be a few bugs in this method so
we are testing the current functionality to make sure nothing breaks
when we address those bugs.

Example test failure:

```
  1) Failure:
TestIRB::TestRubyLex#test_auto_indent [/Users/Ben/Projects/irb/test/irb/test_ruby_lex.rb:75]:
Calculated the wrong number of spaces for:
 def each_top_level_statement
  initialize_input
  catch(:TERM_INPUT) do
    loop do
      begin
        prompt
        unless l = lex
          throw :TERM_INPUT if @line == ''
        else
.
<10> expected but was
<12>.
```

752d5597ab

* [ruby/reline] Degenerate the terminal size to [$LINES, $COLUMNS] if it is unknown

This is a workaround for https://github.com/ruby/irb/issues/50

5725677d1a

* [ruby/irb] Fix newline depth with multiple braces

This commit fixes the check_newline_depth_difference method to multiple
open braces on one line into account. Before this change we were
subtracting from the depth in check_newline_depth_difference on
every open brace. This is the right thing to do if the opening and
closing brace are on the same line. For example in a method definition we
have an opening and closing parentheses we want to add 1 to our depth,
and then remove it.

```
def foo()
end
```

However this isn't the correct behavior when the brace spans multiple
lines. If a brace spans multiple lines we don't want to subtract from
check_newline_depth_difference and we want to treat the braces the same
way as we do `end` and allow check_corresponding_token_depth to pop the
correct depth.

Example of bad behavior:

```
def foo()
  [
  ]
puts 'bar'
end
```

Example of desired behavior:

```
def foo()
  [
  ]
  puts 'bar'
end
```

7dc8af01e0

* text/readline/test_readline.rb - fix skip on Reline (#2743)

TestRelineAsReadline#test_input_metachar passes on MinGW

* Add "require 'openstruct'" what is forgotten

* [ruby/irb] Fix lib name of OpenStruct

1f3a84ab6b

* Add load path and require for ruby/ruby

* Rescue EOFError

If C-d is pressed before IRB is ready, IRB crashes because EOFError occurs.

* Complete indented and quoted string correctly

  def foo
    ''.upca[TAB]

This will be completed to be:

  def foo
  ''.upcase

The indent was gone. This commit fixes the bug.

* [ruby/irb] Fix crashing when multiple open braces per line

https://github.com/ruby/irb/issues/55

If we had put multiple open braces on a line the with no closing brace
spaces_of_nest array keeps getting '0' added to it. This means that when
we pop off of this array we are saying that we should be in position zero
for the next line. This is an issue because we don't always want to be
in position 0 after a closing brace.

Example:
```
[[[
]
]
]
```
In the above example the 'spaces_of_nest' array looks like this after
the first line is entered: [0,0,0]. We really want to be indented 4
spaces for the 1st closing brace 2 for the 2nd and 0 for the 3rd. i.e.
we want it to be: [0,2,4].

We also saw this issue with a heredoc inside of an array.

```
[<<FOO]
hello
FOO
```

80c69c8272

* Support history-size in .inputrc correctly

* Introduce an abstracted structure about the encoding of Reline

The command prompt on Windows always uses Unicode to take input and print
output but most Reline implementation depends on Encoding.default_external.
This commit introduces an abstracted structure about the encoding of Reline.

* Remove an unused setting variable

* Use Reline.encoding_system_needs if exists

* Add tests for vi_insert and vi_add

* Implement vi_insert_at_bol and vi_add_at_eol

* [ruby/reline] Implement vi_to_next_char

066ecb0a21

* [ruby/reline] Implement vi_prev_char and vi_to_prev_char

0ad3ee63fa

* [ruby/readline-ext] Include ruby/assert.h in ruby/ruby.h so that assertions can be there

4d44c12832

* Stop using minitest dependent methods

* Skip a test that uses assert_ruby_status if it doesn't exist

* Use omit instead of skip

* Check DONT_RUN_RELINE_TEST envvar

* [ruby/irb] Add newline_before_multiline_output

9eb1801a66

* [ruby/irb] Fix compatibility with rails before 5.2

Rails before 5.2 added Array#append as an alias to Array#<< ,
so that it expects only one argument.
However ruby-2.5 added Array#append as an alias to Array#push
which takes any number of arguments.

If irb completion is used in `rails c` (for example "IO.<tab>")
it fails with:
  irb/completion.rb:206:in `<<': wrong number of arguments (given 3, expected 1) (ArgumentError)

Using Array#push instead of Array#append fixes compatibility.

5b7bbf9c34

* Reline: Use a more robust detection of MinTTY

The previous detection per get_screen_size fails when stdout is passed
to a pipe. That is the case when running ruby tests in parallel ("-j" switch).
In this case Reline believes that it's running on MinTTY and the tests
are running with ANSI IOGate instead of the Windows adapter on MINGW.
So parallel test results were different to that of a single process.
This commit fixes these differencies.

The code is taken from git sources and translated to ruby.
NtQueryObject() is replaced by GetFileInformationByHandleEx(), because
NtQueryObject() is undocumented and is more difficult to use:
  c5a03b1e29/compat/winansi.c (L558)

* Reline: Fix changed test results due to change to UTF-8 on Windows

In commit f8ea2860b0 the Reline encoding
for native windows console was changed to hardcoded UTF-8.
This caused failures in reline and readline tests, but they were hidden,
because parallel ruby tests incorrectly used Reline::ANSI as IOGate.
Tests failures were raised in single process mode, but not with -j switch.

This patch corrects encodings on native Windows console.

* [ruby/irb] [ruby/irb] Rewrite an expression to detect multiline

ed5cf375a6

5b7bbf9c34

* [ruby/reline] Implement vi_change_meta

8538e0e10f

* Always refer to Reline::IOGate.encoding

* Always use UTF-8 for Reline::GeneralIO on Windows

* Use test_mode on Reline::History::Test for encoding

* [ruby/reline] Support GNOME style Home/End key sequences [Bug #16510]

788f0df845

* [ruby/irb] Add a new easter egg: dancing ruby

e37dc7e58e

* [ruby/irb] Exclude useless files from RDoc

8f1ab2400c

* [ruby/irb] Exclude useless files from RDoc

* Fix inaccuracy in encoding tests

These tests assume
  Encoding.find('locale') == Encoding.find('external')
and fail if they are distinct.

* [ruby/reline] Fix Reline::Windows#scroll_down

I mistook Right and Bottom.

8be401c5f5

* [ruby/reline] Bypass cursor down when a char is rendered at eol on Windows

A newline is automatically inserted if a character is rendered at eol on
Windows command prompt.

4bfea07e4a

* [ruby/reline] Organize special keys escape sequences

41deb1a3d9

* [ruby/readline-ext] Remove unnecessary -I$(top_srcdir) when it's an individual gem

efaca4a5f4

* [ruby/readline-ext] Check TestRelineAsReadline existance

c0a6303168

* [ruby/readline-ext] The ruby/assert.h is adopted by Ruby 2.7 or later

106c31fc1b

* Revert "[ruby/readline-ext] Include ruby/assert.h in ruby/ruby.h so that assertions can be there"

This reverts commit 425b2064d3.

This cherry-pick was a mistake.

* [ruby/readline-ext] Use require check instead of DONT_RUN_RELINE_TEST env

1df99d1481

* [ruby/readline-ext] Add spec.extensions

8c33abb13c

* [ruby/readline-ext] Use rake/extensiokntask to build

b0b5f709bd

* Fix readline build dependency

* [ruby/irb] Add test_complete_symbol

dbbf086c1f

* [ruby/irb] Check doc namespace correctly

IRB::InputCompletor::PerfectMatchedProc crashes when doc not found because a
variable name was incorrect.

889fd4928f

* [ruby/irb] Fix auto indent with closed brace

A closed brace in auto-indent shouldn't affect the next brace in the same line,
but it behaves like below:

  p() {
    }

It's a bug.

fbe59e344f

* [ruby/irb] Use 0.step instead of (..0).each for Ruby 2.5

5d628ca40e

* Revert "[ruby/irb] Add test_complete_symbol"

This reverts commit 3af3431c2c.

* [ruby/irb] fix reserved words and completion for them

6184b227ad

* Add test_complete_symbol

The previous version of the test method used a symbol, ":abcdefg" to complete
but longer symbols that can be completed are defined by other test methods of
other libs.

* test/irb/test_completion.rb: suppress a warning: unused literal ignored

* [ruby/reline] Use IO#write instead of IO#print

IO#print always adds a string of $\ automatically.

a93119c847

* [ruby/irb] Version 1.2.2

a71753f15a

* [ruby/reline] Version 0.1.3

ea2b182466

* [ruby/irb] Include easter-egg.rb in gemspec

`irb` doesn't run because this file isn't included in the gem.
73cda56d25

* [ruby/irb] Version 1.2.3

dd56e06df5

* support multi-run test for test_readline.rb

* [ruby/irb] `yield` outside method definition is a syntax error

dbc7b059c7

* test/readline - allow ENV control of test class creation

In ruby/ruby, the tests run on both readline & reline by creating four test classes:
```
TestReadline
TestReadlineHistory

TestRelineAsReadline
TestRelineAsReadlineHistory
```

Reline inports the test files and uses them in its CI.  Adding the ENV control allows it to only run the `TestRelineAsReadline` classes.

* Omit test_using_quoting_detection_proc_with_multibyte_input temporarily for random order test

* support random order test.

test_readline:
  HISTORY should be empty.

test_using_quoting_detection_proc:
test_using_quoting_detection_proc_with_multibyte_input:
  Readline.completer_quote_characters= and
  Readline.completer_word_break_characters= doesn't accept nil,
  so skip if previous values are nil.

* Set Readline.completion_append_character = nil always

GNU Readline add a white space when Readline.completion_append_character is
not initialized.

* Fix a typo [ci skip]

* skip test if Reline.completion_proc is nil.

Some other tests can set Reline.completion_proc, so if it is nil,
simply skip this test.

* Reset Reline.point

TestRelineAsReadline#test_insert_text expects Readline.point == 0
at the beginning of the test, but a test violate this assumption.

* Convert incompatible encoding symbol names

* Ignore incompatible convert of symbols

* Add workaround for test-bundler failure

500526558 (step):16:127
```
Failures:

  1) Bundler.setup when Bundler is bundled doesn't blow up
     Failure/Error: expect(err).to be_empty

       expected `"fatal: not a git repository (or any of the parent directories): .git\nfatal: not a git repository (o...the parent directories): .git\nfatal: not a git repository (or any of the parent directories): .git".empty?` to return true, got false

       Commands:
       $ /home/runner/work/actions/actions/snapshot-master/ruby \
         -I/home/runner/work/actions/actions/snapshot-master/lib:/home/runner/work/actions/actions/snapshot-master/spec/bundler \
         -rsupport/hax -rsupport/artifice/fail \
         /home/runner/work/actions/actions/snapshot-master/libexec/bundle install --retry 0
       Resolving dependencies...
       Using bundler 2.1.4
       Bundle complete! 1 Gemfile dependency, 1 gem now installed.
       Use `bundle info [gemname]` to see where a bundled gem is installed.
       fatal: not a git repository (or any of the parent directories): .git
       fatal: not a git repository (or any of the parent directories): .git
       fatal: not a git repository (or any of the parent directories): .git
       # $? => 0

       $ /home/runner/work/actions/actions/snapshot-master/ruby \
         -I/home/runner/work/actions/actions/snapshot-master/lib:/home/runner/work/actions/actions/snapshot-master/spec/bundler \
         -rsupport/hax -rsupport/artifice/fail \
         /home/runner/work/actions/actions/snapshot-master/libexec/bundle exec ruby -e \
         require\ \'bundler\'\;\ Bundler.setup
       fatal: not a git repository (or any of the parent directories): .git
       fatal: not a git repository (or any of the parent directories): .git
       fatal: not a git repository (or any of the parent directories): .git
       # $? => 0
     # ./spec/bundler/runtime/setup_spec.rb:1056:in `block (3 levels) in <top (required)>'
     # ./spec/bundler/spec_helper.rb:111:in `block (3 levels) in <top (required)>'
     # ./spec/bundler/spec_helper.rb:111:in `block (2 levels) in <top (required)>'
     # ./spec/bundler/spec_helper.rb:78:in `block (2 levels) in <top (required)>'
make: *** [yes-test-bundler] Error 1
```

* [ruby/irb] Unnamed groups are not captured when named groups are used

0a641a69b0

* [ruby/reline] Work with wrong $/ value correctly

962ebf5a1b

* [ruby/irb] Detect multiple lines output simplify

The old implementation performance test code:

    require 'objspace'
    puts "%.5g MB" % (ObjectSpace.memsize_of_all * 0.001 * 0.001)
    /\A.*\Z/ !~ ('abc' * 20_000_000)
    puts "%.5g MB" % (ObjectSpace.memsize_of_all * 0.001 * 0.001)

and run `time test.rb`:

    2.5868 MB
    62.226 MB

    real    0m1.307s
    user    0m0.452s
    sys     0m0.797s

The new implementation performance test code:

    require 'objspace'
    puts "%.5g MB" % (ObjectSpace.memsize_of_all * 0.001 * 0.001)
    ('abc' * 20_000_000).include?("\n")
    puts "%.5g MB" % (ObjectSpace.memsize_of_all * 0.001 * 0.001)

and run `time test.rb`:

    2.5861 MB
    62.226 MB

    real    0m0.132s
    user    0m0.088s
    sys     0m0.042s

40d6610baf

* [ruby/reline] Suppress error in case INPUTRC env is empty

bce7e7562b

* [ruby/reline] Add yamatanooroti rendering test

f092519525

* [ruby/reline] Rename test suite name of yamatanooroti test

b0f32f5de4

* [ruby/reline] Add a comment why rescue yamatanooroti loading error on the test

2a8061daec

* [ruby/irb] Suppress crashing when EncodingError has occurred without lineno

13572d8cdc

* [ruby/reline] Suppress error when check ambiguous char width in LANG=C

623dffdd75

* [ruby/io-console] Enable only interrupt bits on `intr: true`

baaf929041

* [ruby/io-console] bump up to 0.5.4

* [ruby/io-console] Update the minimum requirement of Ruby version

73e7b6318a

* [ruby/io-console] Filter Ruby engine name rather than just /ruby/

This breaks tests using this path on JRuby because the `jruby`
executable turns into `jjruby` after the sub.

e5951aa34c

* [ruby/io-console] bump up to 0.5.5

* [ruby/io-console] Prefer keyword arguments

5facbfc4c8

* [ruby/io-console] [DOC] Improved about `intr:`

82b630cd79

* [ruby/io-console] Just ignore the extension on other than CRuby

41b6f09574

* [ruby/io-console] bump up to 0.5.6

Co-authored-by: KOBAYASHI Shuji <shuujii@gmail.com>
Co-authored-by: Ben <kanobt61@gmail.com>
Co-authored-by: Yusuke Endoh <mame@ruby-lang.org>
Co-authored-by: MSP-Greg <MSP-Greg@users.noreply.github.com>
Co-authored-by: Nobuyoshi Nakada <nobu@ruby-lang.org>
Co-authored-by: Kenta Murata <mrkn@mrkn.jp>
Co-authored-by: Lars Kanis <lars@greiz-reinsdorf.de>
Co-authored-by: Lars Kanis <kanis@comcard.de>
Co-authored-by: Alan Wu <XrXr@users.noreply.github.com>
Co-authored-by: Hiroshi SHIBATA <hsbt@ruby-lang.org>
Co-authored-by: Nobuhiro IMAI <nov@yo.rim.or.jp>
Co-authored-by: Nick Lewis <nick@puppet.com>
Co-authored-by: S-H-GAMELINKS <gamelinks007@gmail.com>
Co-authored-by: Koichi Sasada <ko1@atdot.net>
Co-authored-by: Kazuhiro NISHIYAMA <zn@mbf.nifty.com>
Co-authored-by: Charles Oliver Nutter <headius@headius.com>
This commit is contained in:
aycabta 2020-03-30 19:09:50 +09:00 committed by GitHub
parent ecf874edea
commit 0057fe4063
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
57 changed files with 1169 additions and 260 deletions

View file

@ -111,6 +111,9 @@ rawmode_opt(int *argcp, VALUE *argv, int min_argc, int max_argc, rawmode_arg_t *
int argc = *argcp; int argc = *argcp;
rawmode_arg_t *optp = NULL; rawmode_arg_t *optp = NULL;
VALUE vopts = Qnil; VALUE vopts = Qnil;
#ifdef RB_SCAN_ARGS_PASS_CALLED_KEYWORDS
argc = rb_scan_args(argc, argv, "*:", NULL, &vopts);
#else
if (argc > min_argc) { if (argc > min_argc) {
vopts = rb_check_hash_type(argv[argc-1]); vopts = rb_check_hash_type(argv[argc-1]);
if (!NIL_P(vopts)) { if (!NIL_P(vopts)) {
@ -120,6 +123,7 @@ rawmode_opt(int *argcp, VALUE *argv, int min_argc, int max_argc, rawmode_arg_t *
if (!vopts) vopts = Qnil; if (!vopts) vopts = Qnil;
} }
} }
#endif
rb_check_arity(argc, min_argc, max_argc); rb_check_arity(argc, min_argc, max_argc);
if (!NIL_P(vopts)) { if (!NIL_P(vopts)) {
VALUE vmin = rb_hash_aref(vopts, ID2SYM(id_min)); VALUE vmin = rb_hash_aref(vopts, ID2SYM(id_min));
@ -188,7 +192,7 @@ set_rawmode(conmode *t, void *arg)
#endif #endif
#ifdef ISIG #ifdef ISIG
if (r->intr) { if (r->intr) {
t->c_iflag |= BRKINT|IXON; t->c_iflag |= BRKINT;
t->c_lflag |= ISIG; t->c_lflag |= ISIG;
t->c_oflag |= OPOST; t->c_oflag |= OPOST;
} }
@ -356,9 +360,9 @@ ttymode_with_io(VALUE io, VALUE (*func)(VALUE, VALUE), VALUE farg, void (*setter
/* /*
* call-seq: * call-seq:
* io.raw(min: nil, time: nil) {|io| } * io.raw(min: nil, time: nil, intr: nil) {|io| }
* *
* Yields +self+ within raw mode. * Yields +self+ within raw mode, and returns the result of the block.
* *
* STDIN.raw(&:gets) * STDIN.raw(&:gets)
* *
@ -370,6 +374,9 @@ ttymode_with_io(VALUE io, VALUE (*func)(VALUE, VALUE), VALUE farg, void (*setter
* The parameter +time+ specifies the timeout in _seconds_ with a * The parameter +time+ specifies the timeout in _seconds_ with a
* precision of 1/10 of a second. (default: 0) * precision of 1/10 of a second. (default: 0)
* *
* If the parameter +intr+ is +true+, enables break, interrupt, quit,
* and suspend special characters.
*
* Refer to the manual page of termios for further details. * Refer to the manual page of termios for further details.
* *
* You must require 'io/console' to use this method. * You must require 'io/console' to use this method.
@ -383,11 +390,11 @@ console_raw(int argc, VALUE *argv, VALUE io)
/* /*
* call-seq: * call-seq:
* io.raw!(min: nil, time: nil) * io.raw!(min: nil, time: nil, intr: nil) -> io
* *
* Enables raw mode. * Enables raw mode, and returns +io+.
* *
* If the terminal mode needs to be back, use io.raw { ... }. * If the terminal mode needs to be back, use <code>io.raw { ... }</code>.
* *
* See IO#raw for details on the parameters. * See IO#raw for details on the parameters.
* *
@ -483,7 +490,7 @@ nogvl_getch(void *p)
/* /*
* call-seq: * call-seq:
* io.getch(min: nil, time: nil) -> char * io.getch(min: nil, time: nil, intr: nil) -> char
* *
* Reads and returns a character in raw mode. * Reads and returns a character in raw mode.
* *
@ -1490,7 +1497,7 @@ console_dev(int argc, VALUE *argv, VALUE klass)
/* /*
* call-seq: * call-seq:
* io.getch(min: nil, time: nil) -> char * io.getch(min: nil, time: nil, intr: nil) -> char
* *
* See IO#getch. * See IO#getch.
*/ */

View file

@ -1,7 +1,7 @@
# frozen_string_literal: false # frozen_string_literal: false
require 'mkmf' require 'mkmf'
ok = true ok = true if RUBY_ENGINE == "ruby"
hdr = nil hdr = nil
case case
when macro_defined?("_WIN32", "") when macro_defined?("_WIN32", "")
@ -14,8 +14,9 @@ when have_header(hdr = "sgtty.h")
%w"stty gtty".each {|f| have_func(f, hdr)} %w"stty gtty".each {|f| have_func(f, hdr)}
else else
ok = false ok = false
end end if ok
if ok case ok
when true
have_header("sys/ioctl.h") if hdr have_header("sys/ioctl.h") if hdr
# rb_check_hash_type: 1.9.3 # rb_check_hash_type: 1.9.3
# rb_io_get_write_io: 1.9.1 # rb_io_get_write_io: 1.9.1
@ -27,4 +28,6 @@ if ok
create_makefile("io/console") {|conf| create_makefile("io/console") {|conf|
conf << "\n""VK_HEADER = #{vk_header}\n" conf << "\n""VK_HEADER = #{vk_header}\n"
} }
when nil
File.write("Makefile", dummy_makefile($srcdir).join(""))
end end

View file

@ -1,5 +1,5 @@
# -*- ruby -*- # -*- ruby -*-
_VERSION = "0.5.3" _VERSION = "0.5.6"
date = %w$Date:: $[1] date = %w$Date:: $[1]
Gem::Specification.new do |s| Gem::Specification.new do |s|
@ -9,7 +9,7 @@ Gem::Specification.new do |s|
s.summary = "Console interface" s.summary = "Console interface"
s.email = "nobu@ruby-lang.org" s.email = "nobu@ruby-lang.org"
s.description = "add console capabilities to IO instances." s.description = "add console capabilities to IO instances."
s.required_ruby_version = ">= 2.2.0" s.required_ruby_version = ">= 2.4.0"
s.homepage = "https://github.com/ruby/io-console" s.homepage = "https://github.com/ruby/io-console"
s.metadata["source_code_url"] = s.homepage s.metadata["source_code_url"] = s.homepage
s.authors = ["Nobu Nakada"] s.authors = ["Nobu Nakada"]

View file

@ -109,5 +109,4 @@ unless readline.have_type("rl_hook_func_t*")
$defs << "-Drl_hook_func_t=Function" $defs << "-Drl_hook_func_t=Function"
end end
$INCFLAGS << " -I$(top_srcdir)"
create_makefile("readline") create_makefile("readline")

View file

@ -8,14 +8,19 @@ Gem::Specification.new do |spec|
spec.description = %q{Provides an interface for GNU Readline and Edit Line (libedit).} spec.description = %q{Provides an interface for GNU Readline and Edit Line (libedit).}
spec.homepage = "https://github.com/ruby/readline-ext" spec.homepage = "https://github.com/ruby/readline-ext"
spec.license = "BSD-2-Clause" spec.license = "BSD-2-Clause"
spec.extensions = %w[ext/readline/extconf.rb]
spec.metadata["homepage_uri"] = spec.homepage spec.metadata["homepage_uri"] = spec.homepage
spec.metadata["source_code_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } `git ls-files -z 2>/dev/null`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
end end
spec.bindir = "exe" spec.bindir = "exe"
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"] spec.require_paths = ["lib"]
spec.add_development_dependency "bundler"
spec.add_development_dependency "rake"
spec.add_development_dependency "rake-compiler"
end end

View file

@ -21,7 +21,7 @@ Gem::Specification.new do |spec|
# Specify which files should be added to the gem when it is released. # Specify which files should be added to the gem when it is released.
# The `git ls-files -z` loads the files in the RubyGem that have been added into git. # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } `git ls-files -z 2>/dev/null`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
end end
spec.bindir = "exe" spec.bindir = "exe"
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }

View file

@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
spec.metadata["source_code_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } `git ls-files -z 2>/dev/null`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
end end
spec.bindir = "exe" spec.bindir = "exe"
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }

View file

@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
spec.metadata["source_code_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } `git ls-files -z 2>/dev/null`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
end end
spec.bindir = "exe" spec.bindir = "exe"
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }

View file

@ -21,7 +21,7 @@ Gem::Specification.new do |spec|
# Specify which files should be added to the gem when it is released. # Specify which files should be added to the gem when it is released.
# The `git ls-files -z` loads the files in the RubyGem that have been added into git. # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } `git ls-files -z 2>/dev/null`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
end end
spec.bindir = "exe" spec.bindir = "exe"
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }

View file

@ -21,6 +21,7 @@ require "irb/locale"
require "irb/color" require "irb/color"
require "irb/version" require "irb/version"
require "irb/easter-egg"
# IRB stands for "interactive Ruby" and is a tool to interactively execute Ruby # IRB stands for "interactive Ruby" and is a tool to interactively execute Ruby
# expressions read from the standard input. # expressions read from the standard input.
@ -553,7 +554,8 @@ module IRB
def handle_exception(exc) def handle_exception(exc)
if exc.backtrace && exc.backtrace[0] =~ /\/irb(2)?(\/.*|-.*|\.rb)?:/ && exc.class.to_s !~ /^IRB/ && if exc.backtrace && exc.backtrace[0] =~ /\/irb(2)?(\/.*|-.*|\.rb)?:/ && exc.class.to_s !~ /^IRB/ &&
!(SyntaxError === exc) !(SyntaxError === exc) && !(EncodingError === exc)
# The backtrace of invalid encoding hash (ex. {"\xAE": 1}) raises EncodingError without lineno.
irb_bug = true irb_bug = true
else else
irb_bug = false irb_bug = false
@ -736,7 +738,13 @@ module IRB
end end
def output_value # :nodoc: def output_value # :nodoc:
printf @context.return_format, @context.inspect_last_value str = @context.inspect_last_value
multiline_p = str.include?("\n")
if multiline_p && @context.newline_before_multiline_output?
printf @context.return_format, "\n#{str}"
else
printf @context.return_format, str
end
end end
# Outputs the local variables to this current session, including # Outputs the local variables to this current session, including

1
lib/irb/.document Normal file
View file

@ -0,0 +1 @@
**/*.rb

View file

@ -17,11 +17,12 @@ module IRB
# Set of reserved words used by Ruby, you should not use these for # Set of reserved words used by Ruby, you should not use these for
# constants or variables # constants or variables
ReservedWords = %w[ ReservedWords = %w[
__ENCODING__ __LINE__ __FILE__
BEGIN END BEGIN END
alias and alias and
begin break begin break
case class case class
def defined do def defined? do
else elsif end ensure else elsif end ensure
false for false for
if in if in
@ -98,7 +99,11 @@ module IRB
return nil if doc_namespace return nil if doc_namespace
if Symbol.respond_to?(:all_symbols) if Symbol.respond_to?(:all_symbols)
sym = $1 sym = $1
candidates = Symbol.all_symbols.collect{|s| ":" + s.id2name} candidates = Symbol.all_symbols.collect do |s|
":" + s.id2name.encode(Encoding.default_external)
rescue Encoding::UndefinedConversionError
# ignore
end
candidates.grep(/^#{Regexp.quote(sym)}/) candidates.grep(/^#{Regexp.quote(sym)}/)
else else
[] []
@ -143,7 +148,7 @@ module IRB
select_message(receiver, message, candidates, sep) select_message(receiver, message, candidates, sep)
end end
when /^(?<num>-?(0[dbo])?[0-9_]+(\.[0-9_]+)?(([eE][+-]?[0-9]+)?i?|r)?)(?<sep>\.|::)(?<mes>[^.]*)$/ when /^(?<num>-?(?:0[dbo])?[0-9_]+(?:\.[0-9_]+)?(?:(?:[eE][+-]?[0-9]+)?i?|r)?)(?<sep>\.|::)(?<mes>[^.]*)$/
# Numeric # Numeric
receiver = $~[:num] receiver = $~[:num]
sep = $~[:sep] sep = $~[:sep]
@ -203,7 +208,7 @@ module IRB
sep = $2 sep = $2
message = Regexp.quote($3) message = Regexp.quote($3)
gv = eval("global_variables", bind).collect{|m| m.to_s}.append("true", "false", "nil") gv = eval("global_variables", bind).collect{|m| m.to_s}.push("true", "false", "nil")
lv = eval("local_variables", bind).collect{|m| m.to_s} lv = eval("local_variables", bind).collect{|m| m.to_s}
iv = eval("instance_variables", bind).collect{|m| m.to_s} iv = eval("instance_variables", bind).collect{|m| m.to_s}
cv = eval("self.class.constants", bind).collect{|m| m.to_s} cv = eval("self.class.constants", bind).collect{|m| m.to_s}
@ -255,7 +260,7 @@ module IRB
else else
candidates = eval("methods | private_methods | local_variables | instance_variables | self.class.constants", bind).collect{|m| m.to_s} candidates = eval("methods | private_methods | local_variables | instance_variables | self.class.constants", bind).collect{|m| m.to_s}
conditions |= ReservedWords candidates |= ReservedWords
if doc_namespace if doc_namespace
candidates.find{ |i| i == input } candidates.find{ |i| i == input }
@ -265,18 +270,14 @@ module IRB
end end
end end
PerfectMatchedProc = ->(matched) { PerfectMatchedProc = ->(matched, bind: IRB.conf[:MAIN_CONTEXT].workspace.binding) {
RDocRIDriver ||= RDoc::RI::Driver.new RDocRIDriver ||= RDoc::RI::Driver.new
if matched =~ /\A(?:::)?RubyVM/ and not ENV['RUBY_YES_I_AM_NOT_A_NORMAL_USER'] if matched =~ /\A(?:::)?RubyVM/ and not ENV['RUBY_YES_I_AM_NOT_A_NORMAL_USER']
File.open(File.join(__dir__, 'ruby_logo.aa')) do |f| IRB.send(:easter_egg)
RDocRIDriver.page do |io|
IO.copy_stream(f, io)
end
end
return return
end end
namespace = retrieve_completion_data(matched, doc_namespace: true) namespace = retrieve_completion_data(matched, bind: bind, doc_namespace: true)
return unless matched return unless namespace
if namespace.is_a?(Array) if namespace.is_a?(Array)
out = RDoc::Markup::Document.new out = RDoc::Markup::Document.new
namespace.each do |m| namespace.each do |m|

View file

@ -133,6 +133,11 @@ module IRB
if @echo_on_assignment.nil? if @echo_on_assignment.nil?
@echo_on_assignment = false @echo_on_assignment = false
end end
@newline_before_multiline_output = IRB.conf[:NEWLINE_BEFORE_MULTILINE_OUTPUT]
if @newline_before_multiline_output.nil?
@newline_before_multiline_output = true
end
end end
# The top-level workspace, see WorkSpace#main # The top-level workspace, see WorkSpace#main
@ -253,6 +258,20 @@ module IRB
# a = "omg" # a = "omg"
# #=> omg # #=> omg
attr_accessor :echo_on_assignment attr_accessor :echo_on_assignment
# Whether a newline is put before multiline output.
#
# Uses IRB.conf[:NEWLINE_BEFORE_MULTILINE_OUTPUT] if available,
# or defaults to +true+.
#
# "abc\ndef"
# #=>
# abc
# def
# IRB.CurrentContext.newline_before_multiline_output = false
# "abc\ndef"
# #=> abc
# def
attr_accessor :newline_before_multiline_output
# Whether verbose messages are displayed or not. # Whether verbose messages are displayed or not.
# #
# A copy of the default <code>IRB.conf[:VERBOSE]</code> # A copy of the default <code>IRB.conf[:VERBOSE]</code>
@ -287,6 +306,7 @@ module IRB
alias ignore_eof? ignore_eof alias ignore_eof? ignore_eof
alias echo? echo alias echo? echo
alias echo_on_assignment? echo_on_assignment alias echo_on_assignment? echo_on_assignment
alias newline_before_multiline_output? newline_before_multiline_output
# Returns whether messages are displayed or not. # Returns whether messages are displayed or not.
def verbose? def verbose?

137
lib/irb/easter-egg.rb Normal file
View file

@ -0,0 +1,137 @@
require "reline"
module IRB
class << self
class Vec
def initialize(x, y, z)
@x, @y, @z = x, y, z
end
attr_reader :x, :y, :z
def sub(other)
Vec.new(@x - other.x, @y - other.y, @z - other.z)
end
def dot(other)
@x*other.x + @y*other.y + @z*other.z
end
def cross(other)
ox, oy, oz = other.x, other.y, other.z
Vec.new(@y*oz-@z*oy, @z*ox-@x*oz, @x*oy-@y*ox)
end
def normalize
r = Math.sqrt(self.dot(self))
Vec.new(@x / r, @y / r, @z / r)
end
end
class Canvas
def initialize((h, w))
@data = (0..h-2).map { [0] * w }
@scale = [w / 2.0, h-2].min
@center = Complex(w / 2, h-2)
end
def line((x1, y1), (x2, y2))
p1 = Complex(x1, y1) / 2 * @scale + @center
p2 = Complex(x2, y2) / 2 * @scale + @center
line0(p1, p2)
end
private def line0(p1, p2)
mid = (p1 + p2) / 2
if (p1 - p2).abs < 1
x, y = mid.rect
@data[y / 2][x] |= (y % 2 > 1 ? 2 : 1)
else
line0(p1, mid)
line0(p2, mid)
end
end
def draw
@data.each {|row| row.fill(0) }
yield
@data.map {|row| row.map {|n| " ',;"[n] }.join }.join("\n")
end
end
class RubyModel
def initialize
@faces = init_ruby_model
end
def init_ruby_model
cap_vertices = (0..5).map {|i| Vec.new(*Complex.polar(1, i * Math::PI / 3).rect, 1) }
middle_vertices = (0..5).map {|i| Vec.new(*Complex.polar(2, (i + 0.5) * Math::PI / 3).rect, 0) }
bottom_vertex = Vec.new(0, 0, -2)
faces = [cap_vertices]
6.times do |j|
i = j-1
faces << [cap_vertices[i], middle_vertices[i], cap_vertices[j]]
faces << [cap_vertices[j], middle_vertices[i], middle_vertices[j]]
faces << [middle_vertices[i], bottom_vertex, middle_vertices[j]]
end
faces
end
def render_frame(i)
angle = i / 10.0
dir = Vec.new(*Complex.polar(1, angle).rect, Math.sin(angle)).normalize
dir2 = Vec.new(*Complex.polar(1, angle - Math::PI/2).rect, 0)
up = dir.cross(dir2)
nm = dir.cross(up)
@faces.each do |vertices|
v0, v1, v2, = vertices
if v1.sub(v0).cross(v2.sub(v0)).dot(dir) > 0
points = vertices.map {|p| [nm.dot(p), up.dot(p)] }
(points + [points[0]]).each_cons(2) do |p1, p2|
yield p1, p2
end
end
end
end
end
private def easter_egg(type = nil)
type ||= [:logo, :dancing].sample
case type
when :logo
File.open(File.join(__dir__, 'ruby_logo.aa')) do |f|
require "rdoc"
RDoc::RI::Driver.new.page do |io|
IO.copy_stream(f, io)
end
end
when :dancing
begin
canvas = Canvas.new(Reline.get_screen_size)
Reline::IOGate.set_winch_handler do
canvas = Canvas.new(Reline.get_screen_size)
end
ruby_model = RubyModel.new
print "\e[?1049h"
0.step do |i| # TODO (0..).each needs Ruby 2.6 or later
buff = canvas.draw do
ruby_model.render_frame(i) do |p1, p2|
canvas.line(p1, p2)
end
end
buff[0, 20] = "\e[0mPress Ctrl+C to stop\e[31m\e[1m"
print "\e[H" + buff
sleep 0.05
end
ensure
print "\e[0m\e[?1049l"
end
end
end
end
end
IRB.send(:easter_egg, ARGV[0]&.to_sym) if $0 == __FILE__

View file

@ -72,7 +72,7 @@ module IRB
end end
history_file = IRB.rc_file("_history") unless history_file history_file = IRB.rc_file("_history") unless history_file
if File.exist?(history_file) if File.exist?(history_file)
open(history_file) do |f| open(history_file, "r:#{IRB.conf[:LC_MESSAGES].encoding}") do |f|
f.each { |l| f.each { |l|
l = l.chomp l = l.chomp
if self.class == ReidlineInputMethod and history.last&.end_with?("\\") if self.class == ReidlineInputMethod and history.last&.end_with?("\\")
@ -107,7 +107,7 @@ module IRB
raise raise
end end
open(history_file, 'w', 0600 ) do |f| open(history_file, "w:#{IRB.conf[:LC_MESSAGES].encoding}", 0600) do |f|
hist = history.map{ |l| l.split("\n").join("\\\n") } hist = history.map{ |l| l.split("\n").join("\\\n") }
f.puts(hist[-num..-1] || hist) f.puts(hist[-num..-1] || hist)
end end

View file

@ -296,15 +296,18 @@ module IRB # :nodoc:
DefaultEncodings = Struct.new(:external, :internal) DefaultEncodings = Struct.new(:external, :internal)
class << IRB class << IRB
private private
def set_encoding(extern, intern = nil) def set_encoding(extern, intern = nil, override: true)
verbose, $VERBOSE = $VERBOSE, nil verbose, $VERBOSE = $VERBOSE, nil
Encoding.default_external = extern unless extern.nil? || extern.empty? Encoding.default_external = extern unless extern.nil? || extern.empty?
Encoding.default_internal = intern unless intern.nil? || intern.empty? Encoding.default_internal = intern unless intern.nil? || intern.empty?
@CONF[:ENCODINGS] = IRB::DefaultEncodings.new(extern, intern)
[$stdin, $stdout, $stderr].each do |io| [$stdin, $stdout, $stderr].each do |io|
io.set_encoding(extern, intern) io.set_encoding(extern, intern)
end end
if override
@CONF[:LC_MESSAGES].instance_variable_set(:@override_encoding, extern)
else
@CONF[:LC_MESSAGES].instance_variable_set(:@encoding, extern) @CONF[:LC_MESSAGES].instance_variable_set(:@encoding, extern)
end
ensure ensure
$VERBOSE = verbose $VERBOSE = verbose
end end

View file

@ -133,6 +133,9 @@ module IRB
include Readline include Readline
# Creates a new input method object using Readline # Creates a new input method object using Readline
def initialize def initialize
if Readline.respond_to?(:encoding_system_needs)
IRB.__send__(:set_encoding, Readline.encoding_system_needs.name, override: false)
end
super super
@line_no = 0 @line_no = 0
@ -207,6 +210,7 @@ module IRB
include Reline include Reline
# Creates a new input method object using Readline # Creates a new input method object using Readline
def initialize def initialize
IRB.__send__(:set_encoding, Reline.encoding_system_needs.name, override: false)
super super
@line_no = 0 @line_no = 0

View file

@ -17,6 +17,7 @@ Gem::Specification.new do |spec|
spec.license = "BSD-2-Clause" spec.license = "BSD-2-Clause"
spec.files = [ spec.files = [
".document",
"Gemfile", "Gemfile",
"LICENSE.txt", "LICENSE.txt",
"README.md", "README.md",
@ -38,6 +39,7 @@ Gem::Specification.new do |spec|
"lib/irb/color.rb", "lib/irb/color.rb",
"lib/irb/completion.rb", "lib/irb/completion.rb",
"lib/irb/context.rb", "lib/irb/context.rb",
"lib/irb/easter-egg.rb",
"lib/irb/ext/change-ws.rb", "lib/irb/ext/change-ws.rb",
"lib/irb/ext/history.rb", "lib/irb/ext/history.rb",
"lib/irb/ext/loader.rb", "lib/irb/ext/loader.rb",
@ -52,7 +54,6 @@ Gem::Specification.new do |spec|
"lib/irb/init.rb", "lib/irb/init.rb",
"lib/irb/input-method.rb", "lib/irb/input-method.rb",
"lib/irb/inspector.rb", "lib/irb/inspector.rb",
"lib/irb/lc/.document",
"lib/irb/lc/error.rb", "lib/irb/lc/error.rb",
"lib/irb/lc/help-message", "lib/irb/lc/help-message",
"lib/irb/lc/ja/encoding_aliases.rb", "lib/irb/lc/ja/encoding_aliases.rb",

View file

@ -1,4 +0,0 @@
# hide help-message files which contain usage information
error.rb
ja/encoding_aliases.rb
ja/error.rb

View file

@ -24,6 +24,7 @@ module IRB # :nodoc:
@@loaded = [] @@loaded = []
def initialize(locale = nil) def initialize(locale = nil)
@override_encoding = nil
@lang = @territory = @encoding_name = @modifier = nil @lang = @territory = @encoding_name = @modifier = nil
@locale = locale || ENV["IRB_LANG"] || ENV["LC_MESSAGES"] || ENV["LC_ALL"] || ENV["LANG"] || "C" @locale = locale || ENV["IRB_LANG"] || ENV["LC_MESSAGES"] || ENV["LC_ALL"] || ENV["LANG"] || "C"
if m = LOCALE_NAME_RE.match(@locale) if m = LOCALE_NAME_RE.match(@locale)
@ -40,12 +41,16 @@ module IRB # :nodoc:
@encoding ||= (Encoding.find('locale') rescue Encoding::ASCII_8BIT) @encoding ||= (Encoding.find('locale') rescue Encoding::ASCII_8BIT)
end end
attr_reader :lang, :territory, :encoding, :modifier attr_reader :lang, :territory, :modifier
def encoding
@override_encoding || @encoding
end
def String(mes) def String(mes)
mes = super(mes) mes = super(mes)
if @encoding if encoding
mes.encode(@encoding, undef: :replace) mes.encode(encoding, undef: :replace)
else else
mes mes
end end

View file

@ -211,6 +211,8 @@ class RubyLex
else else
RubyVM::InstructionSequence.compile(code) RubyVM::InstructionSequence.compile(code)
end end
rescue EncodingError
# This is for a hash with invalid encoding symbol, {"\xAE": 1}
rescue SyntaxError => e rescue SyntaxError => e
case e.message case e.message
when /unterminated (?:string|regexp) meets end of file/ when /unterminated (?:string|regexp) meets end of file/
@ -317,11 +319,13 @@ class RubyLex
def check_newline_depth_difference def check_newline_depth_difference
depth_difference = 0 depth_difference = 0
open_brace_on_line = 0
@tokens.each_with_index do |t, index| @tokens.each_with_index do |t, index|
case t[1] case t[1]
when :on_ignored_nl, :on_nl, :on_comment when :on_ignored_nl, :on_nl, :on_comment
if index != (@tokens.size - 1) if index != (@tokens.size - 1)
depth_difference = 0 depth_difference = 0
open_brace_on_line = 0
end end
next next
when :on_sp when :on_sp
@ -330,8 +334,9 @@ class RubyLex
case t[1] case t[1]
when :on_lbracket, :on_lbrace, :on_lparen when :on_lbracket, :on_lbrace, :on_lparen
depth_difference += 1 depth_difference += 1
open_brace_on_line += 1
when :on_rbracket, :on_rbrace, :on_rparen when :on_rbracket, :on_rbrace, :on_rparen
depth_difference -= 1 depth_difference -= 1 if open_brace_on_line > 0
when :on_kw when :on_kw
next if index > 0 and @tokens[index - 1][3].allbits?(Ripper::EXPR_FNAME) next if index > 0 and @tokens[index - 1][3].allbits?(Ripper::EXPR_FNAME)
case t[2] case t[2]
@ -365,6 +370,7 @@ class RubyLex
is_first_printable_of_line = true is_first_printable_of_line = true
spaces_of_nest = [] spaces_of_nest = []
spaces_at_line_head = 0 spaces_at_line_head = 0
open_brace_on_line = 0
@tokens.each_with_index do |t, index| @tokens.each_with_index do |t, index|
case t[1] case t[1]
when :on_ignored_nl, :on_nl, :on_comment when :on_ignored_nl, :on_nl, :on_comment
@ -372,6 +378,7 @@ class RubyLex
spaces_at_line_head = 0 spaces_at_line_head = 0
is_first_spaces_of_line = true is_first_spaces_of_line = true
is_first_printable_of_line = true is_first_printable_of_line = true
open_brace_on_line = 0
next next
when :on_sp when :on_sp
spaces_at_line_head = t[2].count(' ') if is_first_spaces_of_line spaces_at_line_head = t[2].count(' ') if is_first_spaces_of_line
@ -380,7 +387,8 @@ class RubyLex
end end
case t[1] case t[1]
when :on_lbracket, :on_lbrace, :on_lparen when :on_lbracket, :on_lbrace, :on_lparen
spaces_of_nest.push(spaces_at_line_head) spaces_of_nest.push(spaces_at_line_head + open_brace_on_line * 2)
open_brace_on_line += 1
when :on_rbracket, :on_rbrace, :on_rparen when :on_rbracket, :on_rbrace, :on_rparen
if is_first_printable_of_line if is_first_printable_of_line
corresponding_token_depth = spaces_of_nest.pop corresponding_token_depth = spaces_of_nest.pop
@ -388,6 +396,7 @@ class RubyLex
spaces_of_nest.pop spaces_of_nest.pop
corresponding_token_depth = nil corresponding_token_depth = nil
end end
open_brace_on_line -= 1
when :on_kw when :on_kw
next if index > 0 and @tokens[index - 1][3].allbits?(Ripper::EXPR_FNAME) next if index > 0 and @tokens[index - 1][3].allbits?(Ripper::EXPR_FNAME)
case t[2] case t[2]

View file

@ -11,7 +11,7 @@
# #
module IRB # :nodoc: module IRB # :nodoc:
VERSION = "1.2.1" VERSION = "1.2.3"
@RELEASE_VERSION = VERSION @RELEASE_VERSION = VERSION
@LAST_UPDATE_DATE = "2019-12-24" @LAST_UPDATE_DATE = "2020-02-15"
end end

View file

@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
spec.metadata["source_code_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } `git ls-files -z 2>/dev/null`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
end end
spec.bindir = "exe" spec.bindir = "exe"
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }

View file

@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
spec.metadata["source_code_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } `git ls-files -z 2>/dev/null`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
end end
spec.bindir = "exe" spec.bindir = "exe"
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }

View file

@ -21,7 +21,7 @@ Gem::Specification.new do |spec|
# Specify which files should be added to the gem when it is released. # Specify which files should be added to the gem when it is released.
# The `git ls-files -z` loads the files in the RubyGem that have been added into git. # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } `git ls-files -z 2>/dev/null`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
end end
spec.bindir = "exe" spec.bindir = "exe"
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }

View file

@ -21,7 +21,7 @@ Gem::Specification.new do |spec|
# Specify which files should be added to the gem when it is released. # Specify which files should be added to the gem when it is released.
# The `git ls-files -z` loads the files in the RubyGem that have been added into git. # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } `git ls-files -z 2>/dev/null`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
end end
spec.bindir = "exe" spec.bindir = "exe"
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }

View file

@ -21,7 +21,7 @@ Gem::Specification.new do |spec|
# Specify which files should be added to the gem when it is released. # Specify which files should be added to the gem when it is released.
# The `git ls-files -z` loads the files in the RubyGem that have been added into git. # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } `git ls-files -z 2>/dev/null`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
end end
spec.bindir = "exe" spec.bindir = "exe"
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }

View file

@ -45,40 +45,44 @@ module Reline
@completion_quote_character = nil @completion_quote_character = nil
end end
def encoding
Reline::IOGate.encoding
end
def completion_append_character=(val) def completion_append_character=(val)
if val.nil? if val.nil?
@completion_append_character = nil @completion_append_character = nil
elsif val.size == 1 elsif val.size == 1
@completion_append_character = val.encode(Encoding::default_external) @completion_append_character = val.encode(Reline::IOGate.encoding)
elsif val.size > 1 elsif val.size > 1
@completion_append_character = val[0].encode(Encoding::default_external) @completion_append_character = val[0].encode(Reline::IOGate.encoding)
else else
@completion_append_character = nil @completion_append_character = nil
end end
end end
def basic_word_break_characters=(v) def basic_word_break_characters=(v)
@basic_word_break_characters = v.encode(Encoding::default_external) @basic_word_break_characters = v.encode(Reline::IOGate.encoding)
end end
def completer_word_break_characters=(v) def completer_word_break_characters=(v)
@completer_word_break_characters = v.encode(Encoding::default_external) @completer_word_break_characters = v.encode(Reline::IOGate.encoding)
end end
def basic_quote_characters=(v) def basic_quote_characters=(v)
@basic_quote_characters = v.encode(Encoding::default_external) @basic_quote_characters = v.encode(Reline::IOGate.encoding)
end end
def completer_quote_characters=(v) def completer_quote_characters=(v)
@completer_quote_characters = v.encode(Encoding::default_external) @completer_quote_characters = v.encode(Reline::IOGate.encoding)
end end
def filename_quote_characters=(v) def filename_quote_characters=(v)
@filename_quote_characters = v.encode(Encoding::default_external) @filename_quote_characters = v.encode(Reline::IOGate.encoding)
end end
def special_prefixes=(v) def special_prefixes=(v)
@special_prefixes = v.encode(Encoding::default_external) @special_prefixes = v.encode(Reline::IOGate.encoding)
end end
def completion_case_fold=(v) def completion_case_fold=(v)
@ -171,7 +175,7 @@ module Reline
whole_buffer = line_editor.whole_buffer.dup whole_buffer = line_editor.whole_buffer.dup
whole_buffer.taint if RUBY_VERSION < '2.7' whole_buffer.taint if RUBY_VERSION < '2.7'
if add_hist and whole_buffer and whole_buffer.chomp.size > 0 if add_hist and whole_buffer and whole_buffer.chomp("\n").size > 0
Reline::HISTORY << whole_buffer Reline::HISTORY << whole_buffer
end end
@ -184,8 +188,8 @@ module Reline
line = line_editor.line.dup line = line_editor.line.dup
line.taint if RUBY_VERSION < '2.7' line.taint if RUBY_VERSION < '2.7'
if add_hist and line and line.chomp.size > 0 if add_hist and line and line.chomp("\n").size > 0
Reline::HISTORY << line.chomp Reline::HISTORY << line.chomp("\n")
end end
line_editor.reset_line if line_editor.line.nil? line_editor.reset_line if line_editor.line.nil?
@ -201,7 +205,7 @@ module Reline
otio = Reline::IOGate.prep otio = Reline::IOGate.prep
may_req_ambiguous_char_width may_req_ambiguous_char_width
line_editor.reset(prompt) line_editor.reset(prompt, encoding: Reline::IOGate.encoding)
if multiline if multiline
line_editor.multiline_on line_editor.multiline_on
if block_given? if block_given?
@ -332,8 +336,14 @@ module Reline
@ambiguous_width = 2 if Reline::IOGate == Reline::GeneralIO or STDOUT.is_a?(File) @ambiguous_width = 2 if Reline::IOGate == Reline::GeneralIO or STDOUT.is_a?(File)
return if ambiguous_width return if ambiguous_width
Reline::IOGate.move_cursor_column(0) Reline::IOGate.move_cursor_column(0)
print "\u{25bd}" begin
output.write "\u{25bd}"
rescue Encoding::UndefinedConversionError
# LANG=C
@ambiguous_width = 1
else
@ambiguous_width = Reline::IOGate.cursor_pos.x @ambiguous_width = Reline::IOGate.cursor_pos.x
end
Reline::IOGate.move_cursor_column(0) Reline::IOGate.move_cursor_column(0)
Reline::IOGate.erase_after_cursor Reline::IOGate.erase_after_cursor
end end
@ -387,11 +397,15 @@ module Reline
def_instance_delegators self, :readmultiline def_instance_delegators self, :readmultiline
private :readmultiline private :readmultiline
def self.encoding_system_needs
self.core.encoding
end
def self.core def self.core
@core ||= Core.new { |core| @core ||= Core.new { |core|
core.config = Reline::Config.new core.config = Reline::Config.new
core.key_stroke = Reline::KeyStroke.new(core.config) core.key_stroke = Reline::KeyStroke.new(core.config)
core.line_editor = Reline::LineEditor.new(core.config) core.line_editor = Reline::LineEditor.new(core.config, Reline::IOGate.encoding)
core.basic_word_break_characters = " \t\n`><=;|&{(" core.basic_word_break_characters = " \t\n`><=;|&{("
core.completer_word_break_characters = " \t\n`><=;|&{(" core.completer_word_break_characters = " \t\n`><=;|&{("
@ -405,14 +419,11 @@ module Reline
def self.line_editor def self.line_editor
core.line_editor core.line_editor
end end
HISTORY = History.new(core.config)
end end
if RbConfig::CONFIG['host_os'] =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/ if RbConfig::CONFIG['host_os'] =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/
require 'reline/windows' require 'reline/windows'
if Reline::Windows.get_screen_size == [0, 0] if Reline::Windows.msys_tty?
# Maybe Mintty on Cygwin
require 'reline/ansi' require 'reline/ansi'
Reline::IOGate = Reline::ANSI Reline::IOGate = Reline::ANSI
else else
@ -422,4 +433,5 @@ else
require 'reline/ansi' require 'reline/ansi'
Reline::IOGate = Reline::ANSI Reline::IOGate = Reline::ANSI
end end
Reline::HISTORY = Reline::History.new(Reline.core.config)
require 'reline/general_io' require 'reline/general_io'

View file

@ -1,20 +1,49 @@
require 'io/console' require 'io/console'
class Reline::ANSI class Reline::ANSI
def self.encoding
Encoding.default_external
end
def self.win?
false
end
RAW_KEYSTROKE_CONFIG = { RAW_KEYSTROKE_CONFIG = {
# Console (80x25)
[27, 91, 49, 126] => :ed_move_to_beg, # Home
[27, 91, 52, 126] => :ed_move_to_end, # End
[27, 91, 51, 126] => :key_delete, # Del
[27, 91, 65] => :ed_prev_history, # ↑ [27, 91, 65] => :ed_prev_history, # ↑
[27, 91, 66] => :ed_next_history, # ↓ [27, 91, 66] => :ed_next_history, # ↓
[27, 91, 67] => :ed_next_char, # → [27, 91, 67] => :ed_next_char, # →
[27, 91, 68] => :ed_prev_char, # ← [27, 91, 68] => :ed_prev_char, # ←
[27, 91, 51, 126] => :key_delete, # Del
[27, 91, 49, 126] => :ed_move_to_beg, # Home # KDE
[27, 91, 52, 126] => :ed_move_to_end, # End
[27, 91, 72] => :ed_move_to_beg, # Home [27, 91, 72] => :ed_move_to_beg, # Home
[27, 91, 70] => :ed_move_to_end, # End [27, 91, 70] => :ed_move_to_end, # End
# Del is 0x08
[27, 71, 65] => :ed_prev_history, # ↑
[27, 71, 66] => :ed_next_history, # ↓
[27, 71, 67] => :ed_next_char, # →
[27, 71, 68] => :ed_prev_char, # ←
# GNOME
[27, 79, 72] => :ed_move_to_beg, # Home
[27, 79, 70] => :ed_move_to_end, # End
# Del is 0x08
# Arrow keys are the same of KDE
# others
[27, 32] => :em_set_mark, # M-<space> [27, 32] => :em_set_mark, # M-<space>
[24, 24] => :em_exchange_mark, # C-x C-x TODO also add Windows [24, 24] => :em_exchange_mark, # C-x C-x TODO also add Windows
[27, 91, 49, 59, 53, 67] => :em_next_word, # Ctrl+→ [27, 91, 49, 59, 53, 67] => :em_next_word, # Ctrl+→
[27, 91, 49, 59, 53, 68] => :ed_prev_word, # Ctrl+← [27, 91, 49, 59, 53, 68] => :ed_prev_word, # Ctrl+←
[27, 79, 65] => :ed_prev_history, # ↑
[27, 79, 66] => :ed_next_history, # ↓
[27, 79, 67] => :ed_next_char, # →
[27, 79, 68] => :ed_prev_char, # ←
} }
@@input = STDIN @@input = STDIN
@ -41,16 +70,23 @@ class Reline::ANSI
end end
def self.retrieve_keybuffer def self.retrieve_keybuffer
begin
result = select([@@input], [], [], 0.001) result = select([@@input], [], [], 0.001)
return if result.nil? return if result.nil?
str = @@input.read_nonblock(1024) str = @@input.read_nonblock(1024)
str.bytes.each do |c| str.bytes.each do |c|
@@buf.push(c) @@buf.push(c)
end end
rescue EOFError
end
end end
def self.get_screen_size def self.get_screen_size
@@input.winsize s = @@input.winsize
return s if s[0] > 0 && s[1] > 0
s = [ENV["LINES"].to_i, ENV["COLUMNS"].to_i]
return s if s[0] > 0 && s[1] > 0
[24, 80]
rescue Errno::ENOTTY rescue Errno::ENOTTY
[24, 80] [24, 80]
end end
@ -88,12 +124,12 @@ class Reline::ANSI
end end
def self.move_cursor_column(x) def self.move_cursor_column(x)
print "\e[#{x + 1}G" @@output.write "\e[#{x + 1}G"
end end
def self.move_cursor_up(x) def self.move_cursor_up(x)
if x > 0 if x > 0
print "\e[#{x}A" if x > 0 @@output.write "\e[#{x}A" if x > 0
elsif x < 0 elsif x < 0
move_cursor_down(-x) move_cursor_down(-x)
end end
@ -101,24 +137,24 @@ class Reline::ANSI
def self.move_cursor_down(x) def self.move_cursor_down(x)
if x > 0 if x > 0
print "\e[#{x}B" if x > 0 @@output.write "\e[#{x}B" if x > 0
elsif x < 0 elsif x < 0
move_cursor_up(-x) move_cursor_up(-x)
end end
end end
def self.erase_after_cursor def self.erase_after_cursor
print "\e[K" @@output.write "\e[K"
end end
def self.scroll_down(x) def self.scroll_down(x)
return if x.zero? return if x.zero?
print "\e[#{x}S" @@output.write "\e[#{x}S"
end end
def self.clear_screen def self.clear_screen
print "\e[2J" @@output.write "\e[2J"
print "\e[1;1H" @@output.write "\e[1;1H"
end end
@@old_winch_handler = nil @@old_winch_handler = nil

View file

@ -83,8 +83,17 @@ class Reline::Config
@key_actors[@keymap_label] @key_actors[@keymap_label]
end end
def inputrc_path
case ENV['INPUTRC']
when nil, ''
DEFAULT_PATH
else
ENV['INPUTRC']
end
end
def read(file = nil) def read(file = nil)
file ||= File.expand_path(ENV['INPUTRC'] || DEFAULT_PATH) file ||= File.expand_path(inputrc_path)
begin begin
if file.respond_to?(:readlines) if file.respond_to?(:readlines)
lines = file.readlines lines = file.readlines
@ -184,9 +193,8 @@ class Reline::Config
def bind_variable(name, value) def bind_variable(name, value)
case name case name
when *VARIABLE_NAMES then when 'history-size'
variable_name = :"@#{name.tr(?-, ?_)}" @history_size = value.to_i
instance_variable_set(variable_name, value.nil? || value == '1' || value == 'on')
when 'bell-style' when 'bell-style'
@bell_style = @bell_style =
case value case value
@ -225,6 +233,9 @@ class Reline::Config
end end
when 'keyseq-timeout' when 'keyseq-timeout'
@keyseq_timeout = value.to_i @keyseq_timeout = value.to_i
when *VARIABLE_NAMES then
variable_name = :"@#{name.tr(?-, ?_)}"
instance_variable_set(variable_name, value.nil? || value == '1' || value == 'on')
end end
end end

View file

@ -1,6 +1,14 @@
require 'timeout' require 'timeout'
class Reline::GeneralIO class Reline::GeneralIO
def self.encoding
RUBY_PLATFORM =~ /mswin|mingw/ ? Encoding::UTF_8 : Encoding::default_external
end
def self.win?
false
end
RAW_KEYSTROKE_CONFIG = {} RAW_KEYSTROKE_CONFIG = {}
@@buf = [] @@buf = []

View file

@ -19,7 +19,7 @@ class Reline::History < Array
def []=(index, val) def []=(index, val)
index = check_index(index) index = check_index(index)
super(index, String.new(val, encoding: Encoding::default_external)) super(index, String.new(val, encoding: Reline.encoding_system_needs))
end end
def concat(*val) def concat(*val)
@ -39,12 +39,12 @@ class Reline::History < Array
val.shift(diff) val.shift(diff)
end end
end end
super(*(val.map{ |v| String.new(v, encoding: Encoding::default_external) })) super(*(val.map{ |v| String.new(v, encoding: Reline.encoding_system_needs) }))
end end
def <<(val) def <<(val)
shift if size + 1 > @config.history_size shift if size + 1 > @config.history_size
super(String.new(val, encoding: Encoding::default_external)) super(String.new(val, encoding: Reline.encoding_system_needs))
end end
private def check_index(index) private def check_index(index)

View file

@ -57,10 +57,10 @@ class Reline::LineEditor
NON_PRINTING_END = "\2" NON_PRINTING_END = "\2"
WIDTH_SCANNER = /\G(?:#{NON_PRINTING_START}|#{NON_PRINTING_END}|#{CSI_REGEXP}|#{OSC_REGEXP}|\X)/ WIDTH_SCANNER = /\G(?:#{NON_PRINTING_START}|#{NON_PRINTING_END}|#{CSI_REGEXP}|#{OSC_REGEXP}|\X)/
def initialize(config) def initialize(config, encoding)
@config = config @config = config
@completion_append_character = '' @completion_append_character = ''
reset_variables reset_variables(encoding: encoding)
end end
private def check_multiline_prompt(buffer, prompt) private def check_multiline_prompt(buffer, prompt)
@ -85,10 +85,10 @@ class Reline::LineEditor
end end
end end
def reset(prompt = '', encoding = Encoding.default_external) def reset(prompt = '', encoding:)
@rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
@screen_size = Reline::IOGate.get_screen_size @screen_size = Reline::IOGate.get_screen_size
reset_variables(prompt, encoding) reset_variables(prompt, encoding: encoding)
@old_trap = Signal.trap('SIGINT') { @old_trap = Signal.trap('SIGINT') {
@old_trap.call if @old_trap.respond_to?(:call) # can also be string, ex: "DEFAULT" @old_trap.call if @old_trap.respond_to?(:call) # can also be string, ex: "DEFAULT"
raise Interrupt raise Interrupt
@ -139,7 +139,7 @@ class Reline::LineEditor
@eof @eof
end end
def reset_variables(prompt = '', encoding = Encoding.default_external) def reset_variables(prompt = '', encoding:)
@prompt = prompt @prompt = prompt
@mark_pointer = nil @mark_pointer = nil
@encoding = encoding @encoding = encoding
@ -317,9 +317,9 @@ class Reline::LineEditor
if @menu_info if @menu_info
scroll_down(@highest_in_all - @first_line_started_from) scroll_down(@highest_in_all - @first_line_started_from)
@rerender_all = true @rerender_all = true
@menu_info.list.each do |item| @menu_info.list.sort!.each do |item|
Reline::IOGate.move_cursor_column(0) Reline::IOGate.move_cursor_column(0)
@output.print item @output.write item
@output.flush @output.flush
scroll_down(1) scroll_down(1)
end end
@ -507,12 +507,20 @@ class Reline::LineEditor
Reline::IOGate.move_cursor_column(0) Reline::IOGate.move_cursor_column(0)
visual_lines.each_with_index do |line, index| visual_lines.each_with_index do |line, index|
if line.nil? if line.nil?
if Reline::IOGate.win? and calculate_width(visual_lines[index - 1], true) == Reline::IOGate.get_screen_size.last
# A newline is automatically inserted if a character is rendered at eol on command prompt.
else
Reline::IOGate.erase_after_cursor Reline::IOGate.erase_after_cursor
move_cursor_down(1) move_cursor_down(1)
Reline::IOGate.move_cursor_column(0) Reline::IOGate.move_cursor_column(0)
end
next next
end end
@output.print line @output.write line
if Reline::IOGate.win? and calculate_width(line, true) == Reline::IOGate.get_screen_size.last
# A newline is automatically inserted if a character is rendered at eol on command prompt.
@rest_height -= 1 if @rest_height > 0
end
@output.flush @output.flush
if @first_prompt if @first_prompt
@first_prompt = false @first_prompt = false
@ -535,7 +543,7 @@ class Reline::LineEditor
return before if before.nil? || before.empty? return before if before.nil? || before.empty?
if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: finished?) if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: finished?)
after.lines(chomp: true) after.lines("\n", chomp: true)
else else
before before
end end
@ -905,7 +913,6 @@ class Reline::LineEditor
quote = nil quote = nil
i += 1 i += 1
rest = nil rest = nil
break_pointer = nil
elsif quote and slice.start_with?(escaped_quote) elsif quote and slice.start_with?(escaped_quote)
# skip # skip
i += 2 i += 2
@ -915,7 +922,7 @@ class Reline::LineEditor
closing_quote = /(?!\\)#{Regexp.escape(quote)}/ closing_quote = /(?!\\)#{Regexp.escape(quote)}/
escaped_quote = /\\#{Regexp.escape(quote)}/ escaped_quote = /\\#{Regexp.escape(quote)}/
i += 1 i += 1
break_pointer = i break_pointer = i - 1
elsif not quote and slice =~ word_break_regexp elsif not quote and slice =~ word_break_regexp
rest = $' rest = $'
i += 1 i += 1
@ -937,6 +944,11 @@ class Reline::LineEditor
end end
else else
preposing = '' preposing = ''
if break_pointer
preposing = @line.byteslice(0, break_pointer)
else
preposing = ''
end
target = before target = before
end end
[preposing.encode(@encoding), target.encode(@encoding), postposing.encode(@encoding)] [preposing.encode(@encoding), target.encode(@encoding), postposing.encode(@encoding)]
@ -1091,6 +1103,11 @@ class Reline::LineEditor
private def ed_insert(key) private def ed_insert(key)
if key.instance_of?(String) if key.instance_of?(String)
begin
key.encode(Encoding::UTF_8)
rescue Encoding::UndefinedConversionError
return
end
width = Reline::Unicode.get_mbchar_width(key) width = Reline::Unicode.get_mbchar_width(key)
if @cursor == @cursor_max if @cursor == @cursor_max
@line += key @line += key
@ -1101,6 +1118,11 @@ class Reline::LineEditor
@cursor += width @cursor += width
@cursor_max += width @cursor_max += width
else else
begin
key.chr.encode(Encoding::UTF_8)
rescue Encoding::UndefinedConversionError
return
end
if @cursor == @cursor_max if @cursor == @cursor_max
@line += key.chr @line += key.chr
else else
@ -1876,6 +1898,16 @@ class Reline::LineEditor
end end
end end
private def vi_insert_at_bol(key)
ed_move_to_beg(key)
@config.editing_mode = :vi_insert
end
private def vi_add_at_eol(key)
ed_move_to_end(key)
@config.editing_mode = :vi_insert
end
private def ed_delete_prev_char(key, arg: 1) private def ed_delete_prev_char(key, arg: 1)
deleted = '' deleted = ''
arg.times do arg.times do
@ -1898,6 +1930,18 @@ class Reline::LineEditor
end end
private def vi_change_meta(key) private def vi_change_meta(key)
@waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
if byte_pointer_diff > 0
@line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
elsif byte_pointer_diff < 0
@line, cut = byteslice!(@line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
end
copy_for_vi(cut)
@cursor += cursor_diff if cursor_diff < 0
@cursor_max -= cursor_diff.abs
@byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
@config.editing_mode = :vi_insert
}
end end
private def vi_delete_meta(key) private def vi_delete_meta(key)
@ -2063,12 +2107,17 @@ class Reline::LineEditor
@waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg) } @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg) }
end end
private def search_next_char(key, arg) private def vi_to_next_char(key, arg: 1)
@waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, true) }
end
private def search_next_char(key, arg, need_prev_char = false)
if key.instance_of?(String) if key.instance_of?(String)
inputed_char = key inputed_char = key
else else
inputed_char = key.chr inputed_char = key.chr
end end
prev_total = nil
total = nil total = nil
found = false found = false
@line.byteslice(@byte_pointer..-1).grapheme_clusters.each do |mbchar| @line.byteslice(@byte_pointer..-1).grapheme_clusters.each do |mbchar|
@ -2086,13 +2135,66 @@ class Reline::LineEditor
end end
end end
width = Reline::Unicode.get_mbchar_width(mbchar) width = Reline::Unicode.get_mbchar_width(mbchar)
prev_total = total
total = [total.first + mbchar.bytesize, total.last + width] total = [total.first + mbchar.bytesize, total.last + width]
end end
end end
if found and total if not need_prev_char and found and total
byte_size, width = total byte_size, width = total
@byte_pointer += byte_size @byte_pointer += byte_size
@cursor += width @cursor += width
elsif need_prev_char and found and prev_total
byte_size, width = prev_total
@byte_pointer += byte_size
@cursor += width
end
@waiting_proc = nil
end
private def vi_prev_char(key, arg: 1)
@waiting_proc = ->(key_for_proc) { search_prev_char(key_for_proc, arg) }
end
private def vi_to_prev_char(key, arg: 1)
@waiting_proc = ->(key_for_proc) { search_prev_char(key_for_proc, arg, true) }
end
private def search_prev_char(key, arg, need_next_char = false)
if key.instance_of?(String)
inputed_char = key
else
inputed_char = key.chr
end
prev_total = nil
total = nil
found = false
@line.byteslice(0..@byte_pointer).grapheme_clusters.reverse_each do |mbchar|
# total has [byte_size, cursor]
unless total
# skip cursor point
width = Reline::Unicode.get_mbchar_width(mbchar)
total = [mbchar.bytesize, width]
else
if inputed_char == mbchar
arg -= 1
if arg.zero?
found = true
break
end
end
width = Reline::Unicode.get_mbchar_width(mbchar)
prev_total = total
total = [total.first + mbchar.bytesize, total.last + width]
end
end
if not need_next_char and found and total
byte_size, width = total
@byte_pointer -= byte_size
@cursor -= width
elsif need_next_char and found and prev_total
byte_size, width = prev_total
@byte_pointer -= byte_size
@cursor -= width
end end
@waiting_proc = nil @waiting_proc = nil
end end

View file

@ -1,3 +1,3 @@
module Reline module Reline
VERSION = '0.1.2' VERSION = '0.1.3'
end end

View file

@ -1,6 +1,14 @@
require 'fiddle/import' require 'fiddle/import'
class Reline::Windows class Reline::Windows
def self.encoding
Encoding::UTF_8
end
def self.win?
true
end
RAW_KEYSTROKE_CONFIG = { RAW_KEYSTROKE_CONFIG = {
[224, 72] => :ed_prev_history, # ↑ [224, 72] => :ed_prev_history, # ↑
[224, 80] => :ed_next_history, # ↓ [224, 80] => :ed_next_history, # ↓
@ -68,6 +76,8 @@ class Reline::Windows
STD_INPUT_HANDLE = -10 STD_INPUT_HANDLE = -10
STD_OUTPUT_HANDLE = -11 STD_OUTPUT_HANDLE = -11
WINDOW_BUFFER_SIZE_EVENT = 0x04 WINDOW_BUFFER_SIZE_EVENT = 0x04
FILE_TYPE_PIPE = 0x0003
FILE_NAME_INFO = 2
@@getwch = Win32API.new('msvcrt', '_getwch', [], 'I') @@getwch = Win32API.new('msvcrt', '_getwch', [], 'I')
@@kbhit = Win32API.new('msvcrt', '_kbhit', [], 'I') @@kbhit = Win32API.new('msvcrt', '_kbhit', [], 'I')
@@GetKeyState = Win32API.new('user32', 'GetKeyState', ['L'], 'L') @@GetKeyState = Win32API.new('user32', 'GetKeyState', ['L'], 'L')
@ -80,9 +90,36 @@ class Reline::Windows
@@hConsoleInputHandle = @@GetStdHandle.call(STD_INPUT_HANDLE) @@hConsoleInputHandle = @@GetStdHandle.call(STD_INPUT_HANDLE)
@@GetNumberOfConsoleInputEvents = Win32API.new('kernel32', 'GetNumberOfConsoleInputEvents', ['L', 'P'], 'L') @@GetNumberOfConsoleInputEvents = Win32API.new('kernel32', 'GetNumberOfConsoleInputEvents', ['L', 'P'], 'L')
@@ReadConsoleInput = Win32API.new('kernel32', 'ReadConsoleInput', ['L', 'P', 'L', 'P'], 'L') @@ReadConsoleInput = Win32API.new('kernel32', 'ReadConsoleInput', ['L', 'P', 'L', 'P'], 'L')
@@GetFileType = Win32API.new('kernel32', 'GetFileType', ['L'], 'L')
@@GetFileInformationByHandleEx = Win32API.new('kernel32', 'GetFileInformationByHandleEx', ['L', 'I', 'P', 'L'], 'I')
@@input_buf = [] @@input_buf = []
@@output_buf = [] @@output_buf = []
def self.msys_tty?(io=@@hConsoleInputHandle)
# check if fd is a pipe
if @@GetFileType.call(io) != FILE_TYPE_PIPE
return false
end
bufsize = 1024
p_buffer = "\0" * bufsize
res = @@GetFileInformationByHandleEx.call(io, FILE_NAME_INFO, p_buffer, bufsize - 2)
return false if res == 0
# get pipe name: p_buffer layout is:
# struct _FILE_NAME_INFO {
# DWORD FileNameLength;
# WCHAR FileName[1];
# } FILE_NAME_INFO
len = p_buffer[0, 4].unpack("L")[0]
name = p_buffer[4, len].encode(Encoding::UTF_8, Encoding::UTF_16LE, invalid: :replace)
# Check if this could be a MSYS2 pty pipe ('\msys-XXXX-ptyN-XX')
# or a cygwin pty pipe ('\cygwin-XXXX-ptyN-XX')
name =~ /(msys-|cygwin-).*-pty/ ? true : false
end
def self.getwch def self.getwch
unless @@input_buf.empty? unless @@input_buf.empty?
return @@input_buf.shift return @@input_buf.shift
@ -99,7 +136,7 @@ class Reline::Windows
return @@input_buf.shift return @@input_buf.shift
end end
begin begin
bytes = ret.chr(Encoding::UTF_8).encode(Encoding.default_external).bytes bytes = ret.chr(Encoding::UTF_8).bytes
@@input_buf.push(*bytes) @@input_buf.push(*bytes)
rescue Encoding::UndefinedConversionError rescue Encoding::UndefinedConversionError
@@input_buf << ret @@input_buf << ret
@ -205,7 +242,7 @@ class Reline::Windows
def self.scroll_down(val) def self.scroll_down(val)
return if val.zero? return if val.zero?
scroll_rectangle = [0, val, get_screen_size.first, get_screen_size.last].pack('s4') scroll_rectangle = [0, val, get_screen_size.last, get_screen_size.first].pack('s4')
destination_origin = 0 # y * 65536 + x destination_origin = 0 # y * 65536 + x
fill = [' '.ord, 0].pack('SS') fill = [' '.ord, 0].pack('SS')
@@ScrollConsoleScreenBuffer.call(@@hConsoleHandle, scroll_rectangle, nil, destination_origin, fill) @@ScrollConsoleScreenBuffer.call(@@hConsoleHandle, scroll_rectangle, nil, destination_origin, fill)
@ -213,8 +250,8 @@ class Reline::Windows
def self.clear_screen def self.clear_screen
# TODO: Use FillConsoleOutputCharacter and FillConsoleOutputAttribute # TODO: Use FillConsoleOutputCharacter and FillConsoleOutputAttribute
print "\e[2J" write "\e[2J"
print "\e[1;1H" write "\e[1;1H"
end end
def self.set_screen_size(rows, columns) def self.set_screen_size(rows, columns)

View file

@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
spec.metadata["source_code_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } `git ls-files -z 2>/dev/null`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
end end
spec.bindir = "exe" spec.bindir = "exe"
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }

View file

@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
spec.metadata["source_code_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } `git ls-files -z 2>/dev/null`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
end end
spec.bindir = "exe" spec.bindir = "exe"
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }

View file

@ -21,7 +21,7 @@ Gem::Specification.new do |spec|
# Specify which files should be added to the gem when it is released. # Specify which files should be added to the gem when it is released.
# The `git ls-files -z` loads the files in the RubyGem that have been added into git. # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } `git ls-files -z 2>/dev/null`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
end end
spec.bindir = "exe" spec.bindir = "exe"
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }

View file

@ -15,7 +15,7 @@ Gem::Specification.new do |spec|
# Specify which files should be added to the gem when it is released. # Specify which files should be added to the gem when it is released.
# The `git ls-files -z` loads the files in the RubyGem that have been added into git. # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } `git ls-files -z 2>/dev/null`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
end end
spec.bindir = "exe" spec.bindir = "exe"
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }

View file

@ -364,6 +364,11 @@ defined?(PTY) and defined?(IO.console) and TestIO_Console.class_eval do
assert_ctrl("#{cc.ord}", cc, r, w) assert_ctrl("#{cc.ord}", cc, r, w)
assert_ctrl("#{cc.ord}", cc, r, w) assert_ctrl("#{cc.ord}", cc, r, w)
end end
if cc = ctrl["stop"]
assert_ctrl("#{cc.ord}", cc, r, w)
assert_ctrl("#{cc.ord}", cc, r, w)
assert_ctrl("#{cc.ord}", cc, r, w)
end
end end
end end
@ -457,7 +462,7 @@ defined?(IO.console) and TestIO_Console.class_eval do
noctty = [EnvUtil.rubybin, "-e", "Process.daemon(true)"] noctty = [EnvUtil.rubybin, "-e", "Process.daemon(true)"]
when !(rubyw = RbConfig::CONFIG["RUBYW_INSTALL_NAME"]).empty? when !(rubyw = RbConfig::CONFIG["RUBYW_INSTALL_NAME"]).empty?
dir, base = File.split(EnvUtil.rubybin) dir, base = File.split(EnvUtil.rubybin)
noctty = [File.join(dir, base.sub(/ruby/, rubyw))] noctty = [File.join(dir, base.sub(RUBY_ENGINE, rubyw))]
end end
if noctty if noctty

View file

@ -29,7 +29,7 @@ module TestIRB
"def self.foo; bar; end" => "#{GREEN}def#{CLEAR} #{CYAN}#{BOLD}self#{CLEAR}.#{BLUE}#{BOLD}foo#{CLEAR}; bar; #{GREEN}end#{CLEAR}", "def self.foo; bar; end" => "#{GREEN}def#{CLEAR} #{CYAN}#{BOLD}self#{CLEAR}.#{BLUE}#{BOLD}foo#{CLEAR}; bar; #{GREEN}end#{CLEAR}",
'erb = ERB.new("a#{nil}b", trim_mode: "-")' => "erb = #{BLUE}#{BOLD}#{UNDERLINE}ERB#{CLEAR}.new(#{RED}#{BOLD}\"#{CLEAR}#{RED}a#{CLEAR}#{RED}\#{#{CLEAR}#{CYAN}#{BOLD}nil#{CLEAR}#{RED}}#{CLEAR}#{RED}b#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}, #{MAGENTA}trim_mode:#{CLEAR} #{RED}#{BOLD}\"#{CLEAR}#{RED}-#{CLEAR}#{RED}#{BOLD}\"#{CLEAR})", 'erb = ERB.new("a#{nil}b", trim_mode: "-")' => "erb = #{BLUE}#{BOLD}#{UNDERLINE}ERB#{CLEAR}.new(#{RED}#{BOLD}\"#{CLEAR}#{RED}a#{CLEAR}#{RED}\#{#{CLEAR}#{CYAN}#{BOLD}nil#{CLEAR}#{RED}}#{CLEAR}#{RED}b#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}, #{MAGENTA}trim_mode:#{CLEAR} #{RED}#{BOLD}\"#{CLEAR}#{RED}-#{CLEAR}#{RED}#{BOLD}\"#{CLEAR})",
"# comment" => "#{BLUE}#{BOLD}# comment#{CLEAR}", "# comment" => "#{BLUE}#{BOLD}# comment#{CLEAR}",
"yield(hello)" => "#{GREEN}yield#{CLEAR}(hello)", "def f;yield(hello);end" => "#{GREEN}def#{CLEAR} #{BLUE}#{BOLD}f#{CLEAR};#{GREEN}yield#{CLEAR}(hello);#{GREEN}end#{CLEAR}",
'"##@var]"' => "#{RED}#{BOLD}\"#{CLEAR}#{RED}\##{CLEAR}#{RED}\##{CLEAR}@var#{RED}]#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}", '"##@var]"' => "#{RED}#{BOLD}\"#{CLEAR}#{RED}\##{CLEAR}#{RED}\##{CLEAR}@var#{RED}]#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}",
'"foo#{a} #{b}"' => "#{RED}#{BOLD}\"#{CLEAR}#{RED}foo#{CLEAR}#{RED}\#{#{CLEAR}a#{RED}}#{CLEAR}#{RED} #{CLEAR}#{RED}\#{#{CLEAR}b#{RED}}#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}", '"foo#{a} #{b}"' => "#{RED}#{BOLD}\"#{CLEAR}#{RED}foo#{CLEAR}#{RED}\#{#{CLEAR}a#{RED}}#{CLEAR}#{RED} #{CLEAR}#{RED}\#{#{CLEAR}b#{RED}}#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}",
'/r#{e}g/' => "#{RED}#{BOLD}/#{CLEAR}#{RED}r#{CLEAR}#{RED}\#{#{CLEAR}e#{RED}}#{CLEAR}#{RED}g#{CLEAR}#{RED}#{BOLD}/#{CLEAR}", '/r#{e}g/' => "#{RED}#{BOLD}/#{CLEAR}#{RED}r#{CLEAR}#{RED}\#{#{CLEAR}e#{RED}}#{CLEAR}#{RED}g#{CLEAR}#{RED}#{BOLD}/#{CLEAR}",

View file

@ -25,5 +25,27 @@ module TestIRB
assert_include(IRB::InputCompletor.retrieve_completion_data("1r.positi", bind: binding), "1r.positive?") assert_include(IRB::InputCompletor.retrieve_completion_data("1r.positi", bind: binding), "1r.positive?")
assert_empty(IRB::InputCompletor.retrieve_completion_data("1i.positi", bind: binding)) assert_empty(IRB::InputCompletor.retrieve_completion_data("1i.positi", bind: binding))
end end
def test_complete_symbol
_ = :aiueo
assert_include(IRB::InputCompletor.retrieve_completion_data(":a", bind: binding), ":aiueo")
assert_empty(IRB::InputCompletor.retrieve_completion_data(":irb_unknown_symbol_abcdefg", bind: binding))
end
def test_complete_symbol_failure
assert_nil(IRB::InputCompletor::PerfectMatchedProc.(":aiueo", bind: binding))
end
def test_complete_reserved_words
candidates = IRB::InputCompletor.retrieve_completion_data("de", bind: binding)
%w[def defined?].each do |word|
assert_include candidates, word
end
candidates = IRB::InputCompletor.retrieve_completion_data("__", bind: binding)
%w[__ENCODING__ __LINE__ __FILE__].each do |word|
assert_include candidates, word
end
end
end end
end end

View file

@ -63,6 +63,13 @@ module TestIRB
assert_not_match(/rescue _\.class/, e.message) assert_not_match(/rescue _\.class/, e.message)
end end
def test_evaluate_with_encoding_error_without_lineno
assert_raise_with_message(EncodingError, /invalid symbol/) {
@context.evaluate(%q[{"\xAE": 1}], 1)
# The backtrace of this invalid encoding hash doesn't contain lineno.
}
end
def test_evaluate_with_onigmo_warning def test_evaluate_with_onigmo_warning
assert_warning("(irb):1: warning: character class has duplicated range: /[aa]/\n") do assert_warning("(irb):1: warning: character class has duplicated range: /[aa]/\n") do
@context.evaluate('/[aa]/', 1) @context.evaluate('/[aa]/', 1)
@ -216,5 +223,36 @@ module TestIRB
assert(irb.context.echo?, "echo? should be true by default") assert(irb.context.echo?, "echo? should be true by default")
assert(irb.context.echo_on_assignment?, "echo_on_assignment? should be true when IRB.conf[:ECHO_ON_ASSIGNMENT] is set to true") assert(irb.context.echo_on_assignment?, "echo_on_assignment? should be true when IRB.conf[:ECHO_ON_ASSIGNMENT] is set to true")
end end
def test_multiline_output_on_default_inspector
main = Object.new
def main.inspect
"abc\ndef"
end
input = TestInputMethod.new([
"self"
])
irb = IRB::Irb.new(IRB::WorkSpace.new(main), input)
irb.context.return_format = "=> %s\n"
# The default
irb.context.newline_before_multiline_output = true
out, err = capture_io do
irb.eval_input
end
assert_empty err
assert_equal("=> \nabc\ndef\n",
out)
# No newline before multiline output
input.reset
irb.context.newline_before_multiline_output = false
out, err = capture_io do
irb.eval_input
end
assert_empty err
assert_equal("=> abc\ndef\n",
out)
end
end end
end end

130
test/irb/test_ruby_lex.rb Normal file
View file

@ -0,0 +1,130 @@
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
require 'irb/ruby-lex'
require 'test/unit'
require 'ostruct'
module TestIRB
class TestRubyLex < Test::Unit::TestCase
Row = Struct.new(:content, :current_line_spaces, :new_line_spaces)
class MockIO
def initialize(params, &assertion)
@params = params
@assertion = assertion
end
def auto_indent(&block)
result = block.call(*@params)
@assertion.call(result)
end
end
def assert_indenting(lines, correct_space_count, add_new_line)
lines = lines + [""] if add_new_line
last_line_index = lines.length - 1
byte_pointer = lines.last.length
ruby_lex = RubyLex.new()
io = MockIO.new([lines, last_line_index, byte_pointer, add_new_line]) do |auto_indent|
error_message = "Calculated the wrong number of spaces for:\n #{lines.join("\n")}"
assert_equal(correct_space_count, auto_indent, error_message)
end
ruby_lex.set_input(io)
context = OpenStruct.new(auto_indent_mode: true)
ruby_lex.set_auto_indent(context)
end
def test_auto_indent
input_with_correct_indents = [
Row.new(%q(def each_top_level_statement), nil, 2),
Row.new(%q( initialize_input), nil, 2),
Row.new(%q( catch(:TERM_INPUT) do), nil, 4),
Row.new(%q( loop do), nil, 6),
Row.new(%q( begin), nil, 8),
Row.new(%q( prompt), nil, 8),
Row.new(%q( unless l = lex), nil, 10),
Row.new(%q( throw :TERM_INPUT if @line == ''), nil, 10),
Row.new(%q( else), 8, 10),
Row.new(%q( @line_no += l.count("\n")), nil, 10),
Row.new(%q( next if l == "\n"), nil, 10),
Row.new(%q( @line.concat l), nil, 10),
Row.new(%q( if @code_block_open or @ltype or @continue or @indent > 0), nil, 12),
Row.new(%q( next), nil, 12),
Row.new(%q( end), 10, 10),
Row.new(%q( end), 8, 8),
Row.new(%q( if @line != "\n"), nil, 10),
Row.new(%q( @line.force_encoding(@io.encoding)), nil, 10),
Row.new(%q( yield @line, @exp_line_no), nil, 10),
Row.new(%q( end), 8, 8),
Row.new(%q( break if @io.eof?), nil, 8),
Row.new(%q( @line = ''), nil, 8),
Row.new(%q( @exp_line_no = @line_no), nil, 8),
Row.new(%q( ), nil, 8),
Row.new(%q( @indent = 0), nil, 8),
Row.new(%q( rescue TerminateLineInput), 6, 8),
Row.new(%q( initialize_input), nil, 8),
Row.new(%q( prompt), nil, 8),
Row.new(%q( end), 6, 6),
Row.new(%q( end), 4, 4),
Row.new(%q( end), 2, 2),
Row.new(%q(end), 0, 0),
]
lines = []
input_with_correct_indents.each do |row|
lines << row.content
assert_indenting(lines, row.current_line_spaces, false)
assert_indenting(lines, row.new_line_spaces, true)
end
end
def test_braces_on_their_own_line
input_with_correct_indents = [
Row.new(%q(if true), nil, 2),
Row.new(%q( [), nil, 4),
Row.new(%q( ]), 2, 2),
Row.new(%q(end), 0, 0),
]
lines = []
input_with_correct_indents.each do |row|
lines << row.content
assert_indenting(lines, row.current_line_spaces, false)
assert_indenting(lines, row.new_line_spaces, true)
end
end
def test_multiple_braces_in_a_line
input_with_correct_indents = [
Row.new(%q([[[), nil, 6),
Row.new(%q( ]), 4, 4),
Row.new(%q( ]), 2, 2),
Row.new(%q(]), 0, 0),
Row.new(%q([<<FOO]), nil, 0),
Row.new(%q(hello), nil, 0),
Row.new(%q(FOO), nil, 0),
]
lines = []
input_with_correct_indents.each do |row|
lines << row.content
assert_indenting(lines, row.current_line_spaces, false)
assert_indenting(lines, row.new_line_spaces, true)
end
end
def test_a_closed_brace_and_not_closed_brace_in_a_line
input_with_correct_indents = [
Row.new(%q(p() {), nil, 2),
Row.new(%q(}), 0, 0),
]
lines = []
input_with_correct_indents.each do |row|
lines << row.content
assert_indenting(lines, row.current_line_spaces, false)
assert_indenting(lines, row.new_line_spaces, true)
end
end
end
end

View file

@ -3,18 +3,22 @@ begin
ReadlineSo = Readline ReadlineSo = Readline
rescue LoadError rescue LoadError
end end
require "reline"
def use_ext_readline # Use ext/readline as Readline def use_ext_readline # Use ext/readline as Readline
Object.send(:remove_const, :Readline) if Object.const_defined?(:Readline) Object.send(:remove_const, :Readline) if Object.const_defined?(:Readline)
Object.const_set(:Readline, ReadlineSo) Object.const_set(:Readline, ReadlineSo)
end end
def use_lib_reline # Use lib/reline as Readline begin
require "reline"
rescue LoadError
else
def use_lib_reline # Use lib/reline as Readline
Reline.send(:remove_const, 'IOGate') if Reline.const_defined?('IOGate') Reline.send(:remove_const, 'IOGate') if Reline.const_defined?('IOGate')
Reline.const_set('IOGate', Reline::GeneralIO) Reline.const_set('IOGate', Reline::GeneralIO)
Reline.send(:core).config.instance_variable_set(:@test_mode, true) Reline.send(:core).config.instance_variable_set(:@test_mode, true)
Reline.send(:core).config.reset Reline.send(:core).config.reset
Object.send(:remove_const, :Readline) if Object.const_defined?(:Readline) Object.send(:remove_const, :Readline) if Object.const_defined?(:Readline)
Object.const_set(:Readline, Reline) Object.const_set(:Readline, Reline)
end
end end

View file

@ -21,13 +21,16 @@ module BasetestReadline
Readline.point = 0 Readline.point = 0
rescue NotImplementedError rescue NotImplementedError
end end
Readline.special_prefixes = ""
Readline.completion_append_character = nil
Readline.input = nil Readline.input = nil
Readline.output = nil Readline.output = nil
SAVED_ENV.each_with_index {|k, i| ENV[k] = @saved_env[i] } SAVED_ENV.each_with_index {|k, i| ENV[k] = @saved_env[i] }
end end
def test_readline def test_readline
skip "Skip Editline" if /EditLine/n.match(Readline::VERSION) Readline::HISTORY.clear
omit "Skip Editline" if /EditLine/n.match(Readline::VERSION)
with_temp_stdio do |stdin, stdout| with_temp_stdio do |stdin, stdout|
stdin.write("hello\n") stdin.write("hello\n")
stdin.close stdin.close
@ -45,7 +48,7 @@ module BasetestReadline
# Work around lack of SecurityError in Reline # Work around lack of SecurityError in Reline
# test mode with tainted prompt. # test mode with tainted prompt.
# Also skip test on Ruby 2.7+, where $SAFE/taint is deprecated. # Also skip test on Ruby 2.7+, where $SAFE/taint is deprecated.
if RUBY_VERSION < '2.7' && !kind_of?(TestRelineAsReadline) if RUBY_VERSION < '2.7' && defined?(TestRelineAsReadline) && !kind_of?(TestRelineAsReadline)
begin begin
Thread.start { Thread.start {
$SAFE = 1 $SAFE = 1
@ -65,8 +68,8 @@ module BasetestReadline
# line_buffer # line_buffer
# point # point
def test_line_buffer__point def test_line_buffer__point
skip "Skip Editline" if /EditLine/n.match(Readline::VERSION) omit "Skip Editline" if /EditLine/n.match(Readline::VERSION)
skip "GNU Readline has special behaviors" if defined?(Reline) and Readline == Reline omit "GNU Readline has special behaviors" if defined?(Reline) and Readline == Reline
begin begin
Readline.line_buffer Readline.line_buffer
Readline.point Readline.point
@ -154,7 +157,7 @@ module BasetestReadline
end end
def test_completion_proc_empty_result def test_completion_proc_empty_result
skip "Skip Editline" if /EditLine/n.match(Readline::VERSION) omit "Skip Editline" if /EditLine/n.match(Readline::VERSION)
with_temp_stdio do |stdin, stdout| with_temp_stdio do |stdin, stdout|
stdin.write("first\t") stdin.write("first\t")
stdin.flush stdin.flush
@ -233,12 +236,12 @@ module BasetestReadline
end end
def test_completion_encoding def test_completion_encoding
skip "Skip Editline" if /EditLine/n.match(Readline::VERSION) omit "Skip Editline" if /EditLine/n.match(Readline::VERSION)
bug5941 = '[Bug #5941]' bug5941 = '[Bug #5941]'
append_character = Readline.completion_append_character append_character = Readline.completion_append_character
Readline.completion_append_character = "" Readline.completion_append_character = ""
completion_case_fold = Readline.completion_case_fold completion_case_fold = Readline.completion_case_fold
locale = Encoding.find("locale") locale = get_default_internal_encoding
if locale == Encoding::UTF_8 if locale == Encoding::UTF_8
enc1 = Encoding::EUC_JP enc1 = Encoding::EUC_JP
else else
@ -261,7 +264,7 @@ module BasetestReadline
end or end or
begin begin
return if assert_under_utf8 return if assert_under_utf8
skip("missing test for locale #{locale.name}") omit("missing test for locale #{locale.name}")
end end
expected = results[0][0...1] expected = results[0][0...1]
Readline.completion_case_fold = false Readline.completion_case_fold = false
@ -285,32 +288,6 @@ module BasetestReadline
# filename_quote_characters # filename_quote_characters
# special_prefixes # special_prefixes
def test_some_characters_methods def test_some_characters_methods
method_names = [
"basic_word_break_characters",
"completer_word_break_characters",
"basic_quote_characters",
"completer_quote_characters",
"filename_quote_characters",
"special_prefixes",
]
method_names.each do |method_name|
begin
begin
enc = get_default_internal_encoding
saved = Readline.send(method_name.to_sym)
expecteds = [" ", " .,|\t", ""]
expecteds.each do |e|
Readline.send((method_name + "=").to_sym, e)
res = Readline.send(method_name.to_sym)
assert_equal(e, res)
assert_equal(enc, res.encoding, "Readline.#{method_name} should be #{enc.name}")
end
ensure
Readline.send((method_name + "=").to_sym, saved) if saved
end
rescue NotImplementedError
end
end
end end
def test_closed_outstream def test_closed_outstream
@ -335,7 +312,7 @@ module BasetestReadline
end end
def test_point def test_point
skip "Skip Editline" if /EditLine/n.match(Readline::VERSION) omit "Skip Editline" if /EditLine/n.match(Readline::VERSION)
assert_equal(0, Readline.point) assert_equal(0, Readline.point)
Readline.insert_text('12345') Readline.insert_text('12345')
assert_equal(5, Readline.point) assert_equal(5, Readline.point)
@ -350,7 +327,7 @@ module BasetestReadline
end end
def test_insert_text def test_insert_text
skip "Skip Editline" if /EditLine/n.match(Readline::VERSION) omit "Skip Editline" if /EditLine/n.match(Readline::VERSION)
str = "test_insert_text" str = "test_insert_text"
assert_equal(0, Readline.point) assert_equal(0, Readline.point)
assert_equal(Readline, Readline.insert_text(str)) assert_equal(Readline, Readline.insert_text(str))
@ -381,7 +358,7 @@ module BasetestReadline
end end
def test_delete_text def test_delete_text
skip "Skip Editline" if /EditLine/n.match(Readline::VERSION) omit "Skip Editline" if /EditLine/n.match(Readline::VERSION)
str = "test_insert_text" str = "test_insert_text"
assert_equal(0, Readline.point) assert_equal(0, Readline.point)
assert_equal(Readline, Readline.insert_text(str)) assert_equal(Readline, Readline.insert_text(str))
@ -401,7 +378,7 @@ module BasetestReadline
end end
def test_modify_text_in_pre_input_hook def test_modify_text_in_pre_input_hook
skip "Skip Editline" if /EditLine/n.match(Readline::VERSION) omit "Skip Editline" if /EditLine/n.match(Readline::VERSION)
with_temp_stdio {|stdin, stdout| with_temp_stdio {|stdin, stdout|
begin begin
stdin.write("world\n") stdin.write("world\n")
@ -432,9 +409,10 @@ module BasetestReadline
end end
def test_input_metachar def test_input_metachar
skip "Skip Editline" if /EditLine/n.match(Readline::VERSION) omit "Skip Editline" if /EditLine/n.match(Readline::VERSION)
skip("Won't pass on mingw w/readline 7.0.005 [ruby-core:45682]") if mingw? # test will pass on Windows reline, but not readline
skip 'Needs GNU Readline 6 or later' if windows? and defined?(TestReadline) and kind_of?(TestReadline) and Readline::VERSION < '6.0' omit "Won't pass on mingw readline.so using 8.0.001" if /mingw/ =~ RUBY_PLATFORM and defined?(TestReadline) and kind_of?(TestReadline)
omit 'Needs GNU Readline 6 or later' if /mswin|mingw/ =~ RUBY_PLATFORM and defined?(TestReadline) and kind_of?(TestReadline) and Readline::VERSION < '6.0'
bug6601 = '[ruby-core:45682]' bug6601 = '[ruby-core:45682]'
Readline::HISTORY << "hello" Readline::HISTORY << "hello"
wo = nil wo = nil
@ -451,10 +429,10 @@ module BasetestReadline
end end
def test_input_metachar_multibyte def test_input_metachar_multibyte
skip "Skip Editline" if /EditLine/n.match(Readline::VERSION) omit "Skip Editline" if /EditLine/n.match(Readline::VERSION)
unless Encoding.find("locale") == Encoding::UTF_8 unless Encoding.find("locale") == Encoding::UTF_8
return if assert_under_utf8 return if assert_under_utf8
skip 'this test needs UTF-8 locale' omit 'this test needs UTF-8 locale'
end end
bug6602 = '[ruby-core:45683]' bug6602 = '[ruby-core:45683]'
Readline::HISTORY << "\u3042\u3093" Readline::HISTORY << "\u3042\u3093"
@ -481,7 +459,8 @@ module BasetestReadline
end end
def test_refresh_line def test_refresh_line
skip "Only when refresh_line exists" unless Readline.respond_to?(:refresh_line) omit "Only when refresh_line exists" unless Readline.respond_to?(:refresh_line)
omit unless respond_to?(:assert_ruby_status)
bug6232 = '[ruby-core:43957] [Bug #6232] refresh_line after set_screen_size' bug6232 = '[ruby-core:43957] [Bug #6232] refresh_line after set_screen_size'
with_temp_stdio do |stdin, stdout| with_temp_stdio do |stdin, stdout|
replace_stdio(stdin.path, stdout.path) do replace_stdio(stdin.path, stdout.path) do
@ -508,8 +487,14 @@ module BasetestReadline
def test_using_quoting_detection_proc def test_using_quoting_detection_proc
saved_completer_quote_characters = Readline.completer_quote_characters saved_completer_quote_characters = Readline.completer_quote_characters
saved_completer_word_break_characters = Readline.completer_word_break_characters saved_completer_word_break_characters = Readline.completer_word_break_characters
# skip if previous value is nil because Readline... = nil is not allowed.
skip unless saved_completer_quote_characters
skip unless saved_completer_word_break_characters
return unless Readline.respond_to?(:quoting_detection_proc=) return unless Readline.respond_to?(:quoting_detection_proc=)
begin
passed_text = nil passed_text = nil
line = nil line = nil
@ -539,16 +524,24 @@ module BasetestReadline
Readline.completer_quote_characters = saved_completer_quote_characters Readline.completer_quote_characters = saved_completer_quote_characters
Readline.completer_word_break_characters = saved_completer_word_break_characters Readline.completer_word_break_characters = saved_completer_word_break_characters
end end
def test_using_quoting_detection_proc_with_multibyte_input
saved_completer_quote_characters = Readline.completer_quote_characters
saved_completer_word_break_characters = Readline.completer_word_break_characters
return unless Readline.respond_to?(:quoting_detection_proc=)
unless Encoding.find("locale") == Encoding::UTF_8
return if assert_under_utf8
skip 'this test needs UTF-8 locale'
end end
def test_using_quoting_detection_proc_with_multibyte_input
Readline.completion_append_character = nil
saved_completer_quote_characters = Readline.completer_quote_characters
saved_completer_word_break_characters = Readline.completer_word_break_characters
# skip if previous value is nil because Readline... = nil is not allowed.
skip unless saved_completer_quote_characters
skip unless saved_completer_word_break_characters
return unless Readline.respond_to?(:quoting_detection_proc=)
unless get_default_internal_encoding == Encoding::UTF_8
return if assert_under_utf8
omit 'this test needs UTF-8 locale'
end
begin
passed_text = nil passed_text = nil
escaped_char_indexes = [] escaped_char_indexes = []
line = nil line = nil
@ -577,14 +570,15 @@ module BasetestReadline
assert_equal([10], escaped_char_indexes) assert_equal([10], escaped_char_indexes)
assert_equal('second\\ third', passed_text) assert_equal('second\\ third', passed_text)
assert_equal("\u3042\u3093 completion", line) assert_equal("\u3042\u3093 completion#{Readline.completion_append_character}", line)
ensure ensure
Readline.completer_quote_characters = saved_completer_quote_characters Readline.completer_quote_characters = saved_completer_quote_characters
Readline.completer_word_break_characters = saved_completer_word_break_characters Readline.completer_word_break_characters = saved_completer_word_break_characters
end end
end
def test_simple_completion def test_simple_completion
skip "Skip Editline" if /EditLine/n.match(Readline::VERSION) omit "Skip Editline" if /EditLine/n.match(Readline::VERSION)
line = nil line = nil
@ -594,7 +588,7 @@ module BasetestReadline
Readline.output = null Readline.output = null
Readline.completion_proc = ->(text) do Readline.completion_proc = ->(text) do
['abcde', 'abc12'].map { |i| ['abcde', 'abc12'].map { |i|
i.encode(Encoding.default_external) i.encode(get_default_internal_encoding)
} }
end end
w.write("a\t\n") w.write("a\t\n")
@ -607,8 +601,8 @@ module BasetestReadline
end end
def test_completion_with_completion_append_character def test_completion_with_completion_append_character
skip "Skip Editline" if /EditLine/n.match(Readline::VERSION) omit "Skip Editline" if /EditLine/n.match(Readline::VERSION)
skip "Readline.completion_append_character is not implemented" unless Readline.respond_to?(:completion_append_character=) omit "Readline.completion_append_character is not implemented" unless Readline.respond_to?(:completion_append_character=)
line = nil line = nil
append_character = Readline.completion_append_character append_character = Readline.completion_append_character
@ -619,7 +613,7 @@ module BasetestReadline
Readline.completion_append_character = '!' Readline.completion_append_character = '!'
Readline.completion_proc = ->(text) do Readline.completion_proc = ->(text) do
['abcde'].map { |i| ['abcde'].map { |i|
i.encode(Encoding.default_external) i.encode(get_default_internal_encoding)
} }
end end
w.write("a\t\n") w.write("a\t\n")
@ -681,9 +675,9 @@ module BasetestReadline
return unless Readline.respond_to?(:completion_quote_character) return unless Readline.respond_to?(:completion_quote_character)
if /solaris/i =~ RUBY_PLATFORM if /solaris/i =~ RUBY_PLATFORM
# http://rubyci.s3.amazonaws.com/solaris11s-sunc/ruby-trunk/log/20181228T102505Z.fail.html.gz # http://rubyci.s3.amazonaws.com/solaris11s-sunc/ruby-trunk/log/20181228T102505Z.fail.html.gz
skip 'This test does not succeed on Oracle Developer Studio for now' omit 'This test does not succeed on Oracle Developer Studio for now'
end end
skip 'Needs GNU Readline 6 or later' if windows? and defined?(TestReadline) and kind_of?(TestReadline) and Readline::VERSION < '6.0' omit 'Needs GNU Readline 6 or later' if /mswin|mingw/ =~ RUBY_PLATFORM and defined?(TestReadline) and kind_of?(TestReadline) and Readline::VERSION < '6.0'
Readline.completion_proc = -> (_) { [] } Readline.completion_proc = -> (_) { [] }
Readline.completer_quote_characters = "'\"" Readline.completer_quote_characters = "'\""
@ -730,7 +724,7 @@ module BasetestReadline
Tempfile.create("test_readline_stdin") {|stdin| Tempfile.create("test_readline_stdin") {|stdin|
Tempfile.create("test_readline_stdout") {|stdout| Tempfile.create("test_readline_stdout") {|stdout|
yield stdin, stdout yield stdin, stdout
if windows? if /mswin|mingw/ =~ RUBY_PLATFORM
# needed since readline holds refs to tempfiles, can't delete on Windows # needed since readline holds refs to tempfiles, can't delete on Windows
Readline.input = STDIN Readline.input = STDIN
Readline.output = STDOUT Readline.output = STDOUT
@ -766,7 +760,7 @@ module BasetestReadline
return false if ENV['LC_ALL'] == 'UTF-8' return false if ENV['LC_ALL'] == 'UTF-8'
loc = caller_locations(1, 1)[0].base_label.to_s loc = caller_locations(1, 1)[0].base_label.to_s
assert_separately([{"LC_ALL"=>"UTF-8"}, "-r", __FILE__], <<SRC) assert_separately([{"LC_ALL"=>"UTF-8"}, "-r", __FILE__], <<SRC)
#skip "test \#{ENV['LC_ALL']}" #omit "test \#{ENV['LC_ALL']}"
#{self.class.name}.new(#{loc.dump}).run(Test::Unit::Runner.new) #{self.class.name}.new(#{loc.dump}).run(Test::Unit::Runner.new)
SRC SRC
return true return true
@ -780,7 +774,7 @@ class TestReadline < Test::Unit::TestCase
use_ext_readline use_ext_readline
super super
end end
end if defined?(ReadlineSo) end if defined?(ReadlineSo) && ENV["TEST_READLINE_OR_RELINE"] != "Reline"
class TestRelineAsReadline < Test::Unit::TestCase class TestRelineAsReadline < Test::Unit::TestCase
include BasetestReadline include BasetestReadline
@ -789,4 +783,12 @@ class TestRelineAsReadline < Test::Unit::TestCase
use_lib_reline use_lib_reline
super super
end end
end
def get_default_internal_encoding
if RUBY_PLATFORM =~ /mswin|mingw/
Encoding.default_internal || Encoding::UTF_8
else
super
end
end
end if defined?(Reline) && ENV["TEST_READLINE_OR_RELINE"] != "Readline"

View file

@ -260,6 +260,7 @@ class TestReadlineHistory < Test::Unit::TestCase
super super
end end
end if defined?(::ReadlineSo) && defined?(::ReadlineSo::HISTORY) && end if defined?(::ReadlineSo) && defined?(::ReadlineSo::HISTORY) &&
ENV["TEST_READLINE_OR_RELINE"] != "Reline" &&
( (
begin begin
ReadlineSo::HISTORY.clear ReadlineSo::HISTORY.clear
@ -275,4 +276,12 @@ class TestRelineAsReadlineHistory < Test::Unit::TestCase
use_lib_reline use_lib_reline
super super
end end
end
def get_default_internal_encoding
if RUBY_PLATFORM =~ /mswin|mingw/
Encoding.default_internal || Encoding::UTF_8
else
super
end
end
end if defined?(Reline) && ENV["TEST_READLINE_OR_RELINE"] != "Readline"

View file

@ -195,4 +195,24 @@ class Reline::Config::Test < Reline::TestCase
expected = { 'ef'.bytes => 'EF'.bytes, 'gh'.bytes => 'GH'.bytes } expected = { 'ef'.bytes => 'EF'.bytes, 'gh'.bytes => 'GH'.bytes }
assert_equal expected, @config.key_bindings assert_equal expected, @config.key_bindings
end end
def test_history_size
@config.read_lines(<<~LINES.lines)
set history-size 5000
LINES
assert_equal 5000, @config.instance_variable_get(:@history_size)
history = Reline::History.new(@config)
history << "a\n"
assert_equal 1, history.size
end
def test_empty_inputrc_env
inputrc_backup = ENV['INPUTRC']
ENV['INPUTRC'] = ''
assert_nothing_raised do
@config.read
end
ENV['INPUTRC'] = inputrc_backup
end
end end

View file

@ -2,6 +2,10 @@ require_relative 'helper'
require "reline/history" require "reline/history"
class Reline::History::Test < Reline::TestCase class Reline::History::Test < Reline::TestCase
def setup
Reline.send(:test_mode)
end
def test_ancestors def test_ancestors
assert_equal(Reline::History.ancestors.include?(Array), true) assert_equal(Reline::History.ancestors.include?(Array), true)
end end
@ -268,6 +272,10 @@ class Reline::History::Test < Reline::TestCase
end end
def get_default_internal_encoding def get_default_internal_encoding
return Encoding.default_internal || Encoding.find("locale") if RUBY_PLATFORM =~ /mswin|mingw/
Encoding.default_internal || Encoding::UTF_8
else
Encoding.default_internal || Encoding.find("locale")
end
end end
end end

View file

@ -8,8 +8,8 @@ class Reline::KeyActor::Emacs::Test < Reline::TestCase
Reline::HISTORY.instance_variable_set(:@config, @config) Reline::HISTORY.instance_variable_set(:@config, @config)
Reline::HISTORY.clear Reline::HISTORY.clear
@encoding = (RELINE_TEST_ENCODING rescue Encoding.default_external) @encoding = (RELINE_TEST_ENCODING rescue Encoding.default_external)
@line_editor = Reline::LineEditor.new(@config) @line_editor = Reline::LineEditor.new(@config, @encoding)
@line_editor.reset(@prompt, @encoding) @line_editor.reset(@prompt, encoding: @encoding)
end end
def test_ed_insert_one def test_ed_insert_one
@ -1325,6 +1325,68 @@ class Reline::KeyActor::Emacs::Test < Reline::TestCase
assert_line('foo_ba') assert_line('foo_ba')
end end
def test_completion_with_indent
@line_editor.completion_proc = proc { |word|
%w{
foo_foo
foo_bar
foo_baz
qux
}.map { |i|
i.encode(@encoding)
}
}
input_keys(' fo')
assert_byte_pointer_size(' fo')
assert_cursor(4)
assert_cursor_max(4)
assert_line(' fo')
assert_equal(nil, @line_editor.instance_variable_get(:@menu_info))
input_keys("\C-i", false)
assert_byte_pointer_size(' foo_')
assert_cursor(6)
assert_cursor_max(6)
assert_line(' foo_')
assert_equal(nil, @line_editor.instance_variable_get(:@menu_info))
input_keys("\C-i", false)
assert_byte_pointer_size(' foo_')
assert_cursor(6)
assert_cursor_max(6)
assert_line(' foo_')
assert_equal(%w{foo_foo foo_bar foo_baz}, @line_editor.instance_variable_get(:@menu_info).list)
end
def test_completion_with_indent_and_completer_quote_characters
@line_editor.completion_proc = proc { |word|
%w{
"".foo_foo
"".foo_bar
"".foo_baz
"".qux
}.map { |i|
i.encode(@encoding)
}
}
input_keys(' "".fo')
assert_byte_pointer_size(' "".fo')
assert_cursor(7)
assert_cursor_max(7)
assert_line(' "".fo')
assert_equal(nil, @line_editor.instance_variable_get(:@menu_info))
input_keys("\C-i", false)
assert_byte_pointer_size(' "".foo_')
assert_cursor(9)
assert_cursor_max(9)
assert_line(' "".foo_')
assert_equal(nil, @line_editor.instance_variable_get(:@menu_info))
input_keys("\C-i", false)
assert_byte_pointer_size(' "".foo_')
assert_cursor(9)
assert_cursor_max(9)
assert_line(' "".foo_')
assert_equal(%w{"".foo_foo "".foo_bar "".foo_baz}, @line_editor.instance_variable_get(:@menu_info).list)
end
def test_completion_with_perfect_match def test_completion_with_perfect_match
@line_editor.completion_proc = proc { |word| @line_editor.completion_proc = proc { |word|
%w{ %w{
@ -1834,6 +1896,15 @@ class Reline::KeyActor::Emacs::Test < Reline::TestCase
assert_equal([0, 0], @line_editor.instance_variable_get(:@mark_pointer)) assert_equal([0, 0], @line_editor.instance_variable_get(:@mark_pointer))
end end
def test_modify_lines_with_wrong_rs
original_global_slash = $/
$/ = 'b'
@line_editor.output_modifier_proc = proc { |output| Reline::Unicode.escape_for_print(output) }
input_keys("abcdef\n")
assert_equal(['abcdef'], @line_editor.__send__(:modify_lines, @line_editor.whole_lines))
$/ = original_global_slash
end
=begin # TODO: move KeyStroke instance from Reline to LineEditor =begin # TODO: move KeyStroke instance from Reline to LineEditor
def test_key_delete def test_key_delete
input_keys('ab') input_keys('ab')

View file

@ -9,8 +9,8 @@ class Reline::KeyActor::ViInsert::Test < Reline::TestCase
set editing-mode vi set editing-mode vi
LINES LINES
@encoding = (RELINE_TEST_ENCODING rescue Encoding.default_external) @encoding = (RELINE_TEST_ENCODING rescue Encoding.default_external)
@line_editor = Reline::LineEditor.new(@config) @line_editor = Reline::LineEditor.new(@config, @encoding)
@line_editor.reset(@prompt, @encoding) @line_editor.reset(@prompt, encoding: @encoding)
end end
def test_vi_command_mode def test_vi_command_mode
@ -24,6 +24,74 @@ class Reline::KeyActor::ViInsert::Test < Reline::TestCase
assert_line('abc') assert_line('abc')
end end
def test_vi_insert
assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode)
input_keys('i')
assert_line('i')
assert_cursor(1)
assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode)
input_keys("\C-[")
assert_line('i')
assert_cursor(0)
assert_instance_of(Reline::KeyActor::ViCommand, @config.editing_mode)
input_keys('i')
assert_line('i')
assert_cursor(0)
assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode)
end
def test_vi_add
assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode)
input_keys('a')
assert_line('a')
assert_cursor(1)
assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode)
input_keys("\C-[")
assert_line('a')
assert_cursor(0)
assert_instance_of(Reline::KeyActor::ViCommand, @config.editing_mode)
input_keys('a')
assert_line('a')
assert_cursor(1)
assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode)
end
def test_vi_insert_at_bol
input_keys('I')
assert_line('I')
assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode)
input_keys("12345\C-[hh")
assert_line('I12345')
assert_byte_pointer_size('I12')
assert_cursor(3)
assert_cursor_max(6)
assert_instance_of(Reline::KeyActor::ViCommand, @config.editing_mode)
input_keys('I')
assert_line('I12345')
assert_byte_pointer_size('')
assert_cursor(0)
assert_cursor_max(6)
assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode)
end
def test_vi_add_at_eol
input_keys('A')
assert_line('A')
assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode)
input_keys("12345\C-[hh")
assert_line('A12345')
assert_byte_pointer_size('A12')
assert_cursor(3)
assert_cursor_max(6)
assert_instance_of(Reline::KeyActor::ViCommand, @config.editing_mode)
input_keys('A')
assert_line('A12345')
assert_byte_pointer_size('A12345')
assert_cursor(6)
assert_cursor_max(6)
assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode)
end
def test_ed_insert_one def test_ed_insert_one
input_keys('a') input_keys('a')
assert_line('a') assert_line('a')
@ -565,6 +633,60 @@ class Reline::KeyActor::ViInsert::Test < Reline::TestCase
assert_cursor_max(6) assert_cursor_max(6)
end end
def test_vi_to_next_char
input_keys("abcdef\C-[0")
assert_line('abcdef')
assert_byte_pointer_size('')
assert_cursor(0)
assert_cursor_max(6)
input_keys('tz')
assert_line('abcdef')
assert_byte_pointer_size('')
assert_cursor(0)
assert_cursor_max(6)
input_keys('te')
assert_line('abcdef')
assert_byte_pointer_size('abc')
assert_cursor(3)
assert_cursor_max(6)
end
def test_vi_prev_char
input_keys("abcdef\C-[")
assert_line('abcdef')
assert_byte_pointer_size('abcde')
assert_cursor(5)
assert_cursor_max(6)
input_keys('Fz')
assert_line('abcdef')
assert_byte_pointer_size('abcde')
assert_cursor(5)
assert_cursor_max(6)
input_keys('Fa')
assert_line('abcdef')
assert_byte_pointer_size('')
assert_cursor(0)
assert_cursor_max(6)
end
def test_vi_to_prev_char
input_keys("abcdef\C-[")
assert_line('abcdef')
assert_byte_pointer_size('abcde')
assert_cursor(5)
assert_cursor_max(6)
input_keys('Tz')
assert_line('abcdef')
assert_byte_pointer_size('abcde')
assert_cursor(5)
assert_cursor_max(6)
input_keys('Ta')
assert_line('abcdef')
assert_byte_pointer_size('a')
assert_cursor(1)
assert_cursor_max(6)
end
def test_vi_delete_next_char def test_vi_delete_next_char
input_keys("abc\C-[h") input_keys("abc\C-[h")
assert_byte_pointer_size('a') assert_byte_pointer_size('a')
@ -1092,4 +1214,27 @@ class Reline::KeyActor::ViInsert::Test < Reline::TestCase
assert_cursor_max(11) assert_cursor_max(11)
assert_line('aaa ddd eee') assert_line('aaa ddd eee')
end end
def test_vi_change_meta
input_keys("aaa bbb ccc ddd eee\C-[02w")
assert_byte_pointer_size('aaa bbb ')
assert_cursor(8)
assert_cursor_max(19)
assert_line('aaa bbb ccc ddd eee')
input_keys('cwaiueo ')
assert_byte_pointer_size('aaa bbb aiueo ')
assert_cursor(14)
assert_cursor_max(21)
assert_line('aaa bbb aiueo ddd eee')
input_keys("\C-[")
assert_byte_pointer_size('aaa bbb aiueo')
assert_cursor(13)
assert_cursor_max(21)
assert_line('aaa bbb aiueo ddd eee')
input_keys('cb')
assert_byte_pointer_size('aaa bbb ')
assert_cursor(8)
assert_cursor_max(16)
assert_line('aaa bbb ddd eee')
end
end end

View file

@ -3,7 +3,8 @@ require_relative 'helper'
class Reline::MacroTest < Reline::TestCase class Reline::MacroTest < Reline::TestCase
def setup def setup
@config = Reline::Config.new @config = Reline::Config.new
@line_editor = Reline::LineEditor.new(@config) @encoding = (RELINE_TEST_ENCODING rescue Encoding.default_external)
@line_editor = Reline::LineEditor.new(@config, @encoding)
@line_editor.instance_variable_set(:@screen_size, [24, 80]) @line_editor.instance_variable_set(:@screen_size, [24, 80])
@output = @line_editor.output = File.open(IO::NULL, "w") @output = @line_editor.output = File.open(IO::NULL, "w")
end end

View file

@ -21,15 +21,15 @@ class Reline::Test < Reline::TestCase
Reline.completion_append_character = "a".encode(Encoding::ASCII) Reline.completion_append_character = "a".encode(Encoding::ASCII)
assert_equal("a", Reline.completion_append_character) assert_equal("a", Reline.completion_append_character)
assert_equal(Encoding::default_external, Reline.completion_append_character.encoding) assert_equal(get_reline_encoding, Reline.completion_append_character.encoding)
Reline.completion_append_character = "ba".encode(Encoding::ASCII) Reline.completion_append_character = "ba".encode(Encoding::ASCII)
assert_equal("b", Reline.completion_append_character) assert_equal("b", Reline.completion_append_character)
assert_equal(Encoding::default_external, Reline.completion_append_character.encoding) assert_equal(get_reline_encoding, Reline.completion_append_character.encoding)
Reline.completion_append_character = "cba".encode(Encoding::ASCII) Reline.completion_append_character = "cba".encode(Encoding::ASCII)
assert_equal("c", Reline.completion_append_character) assert_equal("c", Reline.completion_append_character)
assert_equal(Encoding::default_external, Reline.completion_append_character.encoding) assert_equal(get_reline_encoding, Reline.completion_append_character.encoding)
Reline.completion_append_character = nil Reline.completion_append_character = nil
assert_equal(nil, Reline.completion_append_character) assert_equal(nil, Reline.completion_append_character)
@ -40,7 +40,7 @@ class Reline::Test < Reline::TestCase
Reline.basic_word_break_characters = "[".encode(Encoding::ASCII) Reline.basic_word_break_characters = "[".encode(Encoding::ASCII)
assert_equal("[", Reline.basic_word_break_characters) assert_equal("[", Reline.basic_word_break_characters)
assert_equal(Encoding::default_external, Reline.basic_word_break_characters.encoding) assert_equal(get_reline_encoding, Reline.basic_word_break_characters.encoding)
end end
def test_completer_word_break_characters def test_completer_word_break_characters
@ -48,7 +48,7 @@ class Reline::Test < Reline::TestCase
Reline.completer_word_break_characters = "[".encode(Encoding::ASCII) Reline.completer_word_break_characters = "[".encode(Encoding::ASCII)
assert_equal("[", Reline.completer_word_break_characters) assert_equal("[", Reline.completer_word_break_characters)
assert_equal(Encoding::default_external, Reline.completer_word_break_characters.encoding) assert_equal(get_reline_encoding, Reline.completer_word_break_characters.encoding)
end end
def test_basic_quote_characters def test_basic_quote_characters
@ -56,7 +56,7 @@ class Reline::Test < Reline::TestCase
Reline.basic_quote_characters = "`".encode(Encoding::ASCII) Reline.basic_quote_characters = "`".encode(Encoding::ASCII)
assert_equal("`", Reline.basic_quote_characters) assert_equal("`", Reline.basic_quote_characters)
assert_equal(Encoding::default_external, Reline.basic_quote_characters.encoding) assert_equal(get_reline_encoding, Reline.basic_quote_characters.encoding)
end end
def test_completer_quote_characters def test_completer_quote_characters
@ -64,7 +64,7 @@ class Reline::Test < Reline::TestCase
Reline.completer_quote_characters = "`".encode(Encoding::ASCII) Reline.completer_quote_characters = "`".encode(Encoding::ASCII)
assert_equal("`", Reline.completer_quote_characters) assert_equal("`", Reline.completer_quote_characters)
assert_equal(Encoding::default_external, Reline.completer_quote_characters.encoding) assert_equal(get_reline_encoding, Reline.completer_quote_characters.encoding)
end end
def test_filename_quote_characters def test_filename_quote_characters
@ -72,7 +72,7 @@ class Reline::Test < Reline::TestCase
Reline.filename_quote_characters = "\'".encode(Encoding::ASCII) Reline.filename_quote_characters = "\'".encode(Encoding::ASCII)
assert_equal("\'", Reline.filename_quote_characters) assert_equal("\'", Reline.filename_quote_characters)
assert_equal(Encoding::default_external, Reline.filename_quote_characters.encoding) assert_equal(get_reline_encoding, Reline.filename_quote_characters.encoding)
end end
def test_special_prefixes def test_special_prefixes
@ -80,7 +80,7 @@ class Reline::Test < Reline::TestCase
Reline.special_prefixes = "\'".encode(Encoding::ASCII) Reline.special_prefixes = "\'".encode(Encoding::ASCII)
assert_equal("\'", Reline.special_prefixes) assert_equal("\'", Reline.special_prefixes)
assert_equal(Encoding::default_external, Reline.special_prefixes.encoding) assert_equal(get_reline_encoding, Reline.special_prefixes.encoding)
end end
def test_completion_case_fold def test_completion_case_fold
@ -94,7 +94,10 @@ class Reline::Test < Reline::TestCase
end end
def test_completion_proc def test_completion_proc
assert_equal(nil, Reline.completion_proc) skip unless Reline.completion_proc == nil
# Another test can set Reline.completion_proc
# assert_equal(nil, Reline.completion_proc)
p = proc {} p = proc {}
Reline.completion_proc = p Reline.completion_proc = p
@ -267,4 +270,8 @@ class Reline::Test < Reline::TestCase
def test_may_req_ambiguous_char_width def test_may_req_ambiguous_char_width
# TODO in Reline::Core # TODO in Reline::Core
end end
def get_reline_encoding
RUBY_PLATFORM =~ /mswin|mingw/ ? Encoding::UTF_8 : Encoding::default_external
end
end end

View file

@ -7,8 +7,8 @@ class Reline::LineEditor::StringProcessingTest < Reline::TestCase
@config = Reline::Config.new @config = Reline::Config.new
Reline::HISTORY.instance_variable_set(:@config, @config) Reline::HISTORY.instance_variable_set(:@config, @config)
@encoding = (RELINE_TEST_ENCODING rescue Encoding.default_external) @encoding = (RELINE_TEST_ENCODING rescue Encoding.default_external)
@line_editor = Reline::LineEditor.new(@config) @line_editor = Reline::LineEditor.new(@config, @encoding)
@line_editor.reset(@prompt, @encoding) @line_editor.reset(@prompt, encoding: @encoding)
end end
def test_calculate_width def test_calculate_width

View file

@ -13,6 +13,7 @@ class Reline::WithinPipeTest < Reline::TestCase
def teardown def teardown
Reline.input = STDIN Reline.input = STDIN
Reline.output = STDOUT Reline.output = STDOUT
Reline.point = 0
@reader.close @reader.close
@writer.close @writer.close
@output.close @output.close

View file

@ -0,0 +1,41 @@
require 'reline'
begin
require 'yamatanooroti'
class Reline::TestRendering < Yamatanooroti::TestCase
def setup
inputrc_backup = ENV['INPUTRC']
ENV['INPUTRC'] = 'nonexistent_file'
start_terminal(5, 30, %w{ruby -Ilib bin/multiline_repl})
sleep 0.5
ENV['INPUTRC'] = inputrc_backup
end
def test_history_back
write(":a\n")
write("\C-p")
close
assert_screen(<<~EOC)
Multiline REPL.
prompt> :a
=> :a
prompt> :a
EOC
end
def test_backspace
write(":abc\C-h\n")
close
assert_screen(<<~EOC)
Multiline REPL.
prompt> :ab
=> :ab
prompt>
EOC
end
end
rescue LoadError, NameError
# On Ruby repository, this test suit doesn't run because Ruby repo doesn't
# have the yamatanooroti gem.
end

View file

@ -285,7 +285,7 @@ class TestRubyOptions < Test::Unit::TestCase
/unknown encoding name - test_ruby_test_rubyoptions_foobarbazqux \(RuntimeError\)/) /unknown encoding name - test_ruby_test_rubyoptions_foobarbazqux \(RuntimeError\)/)
if /mswin|mingw|aix|android/ =~ RUBY_PLATFORM && if /mswin|mingw|aix|android/ =~ RUBY_PLATFORM &&
(str = "\u3042".force_encoding(Encoding.find("locale"))).valid_encoding? (str = "\u3042".force_encoding(Encoding.find("external"))).valid_encoding?
# This result depends on locale because LANG=C doesn't affect locale # This result depends on locale because LANG=C doesn't affect locale
# on Windows. # on Windows.
# On AIX, the source encoding of stdin with LANG=C is ISO-8859-1, # On AIX, the source encoding of stdin with LANG=C is ISO-8859-1,
@ -836,11 +836,11 @@ class TestRubyOptions < Test::Unit::TestCase
def test_command_line_glob_nonascii def test_command_line_glob_nonascii
bug10555 = '[ruby-dev:48752] [Bug #10555]' bug10555 = '[ruby-dev:48752] [Bug #10555]'
name = "\u{3042}.txt" name = "\u{3042}.txt"
expected = name.encode("locale") rescue "?.txt" expected = name.encode("external") rescue "?.txt"
with_tmpchdir do |dir| with_tmpchdir do |dir|
open(name, "w") {} open(name, "w") {}
assert_in_out_err(["-e", "puts ARGV", "?.txt"], "", [expected], [], assert_in_out_err(["-e", "puts ARGV", "?.txt"], "", [expected], [],
bug10555, encoding: "locale") bug10555, encoding: "external")
end end
end end
@ -875,7 +875,7 @@ class TestRubyOptions < Test::Unit::TestCase
with_tmpchdir do |dir| with_tmpchdir do |dir|
Ougai.each {|f| open(f, "w") {}} Ougai.each {|f| open(f, "w") {}}
assert_in_out_err(["-Eutf-8", "-e", "puts ARGV", "*"], "", Ougai, encoding: "utf-8") assert_in_out_err(["-Eutf-8", "-e", "puts ARGV", "*"], "", Ougai, encoding: "utf-8")
ougai = Ougai.map {|f| f.encode("locale", replace: "?")} ougai = Ougai.map {|f| f.encode("external", replace: "?")}
assert_in_out_err(["-e", "puts ARGV", "*.txt"], "", ougai) assert_in_out_err(["-e", "puts ARGV", "*.txt"], "", ougai)
end end
end end