diff options
author | aycabta <aycabta@gmail.com> | 2020-03-30 19:09:50 +0900 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-03-30 19:09:50 +0900 |
commit | 0057fe4063b57b445d6ffc893e442f362e3a19e0 (patch) | |
tree | 4471ba18ce89ff86c6874897bf2fbb06b79d9728 /lib | |
parent | ecf874edea92796c63cc2b50b36b1179de5114c6 (diff) |
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
```
https://github.com/ruby/reline/commit/6074069c7d
* Drop an invalid char as UTF-8
* Add test_completion_with_indent_and_completer_quote_characters
This is for 8a705245e55575d4d310a2e956b89a36a5931971.
* [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>.
```
https://github.com/ruby/irb/commit/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
https://github.com/ruby/reline/commit/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
```
https://github.com/ruby/irb/commit/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
https://github.com/ruby/irb/commit/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
```
https://github.com/ruby/irb/commit/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
https://github.com/ruby/reline/commit/066ecb0a21
* [ruby/reline] Implement vi_prev_char and vi_to_prev_char
https://github.com/ruby/reline/commit/0ad3ee63fa
* [ruby/readline-ext] Include ruby/assert.h in ruby/ruby.h so that assertions can be there
https://github.com/ruby/readline-ext/commit/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
https://github.com/ruby/irb/commit/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.
https://github.com/ruby/irb/commit/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:
https://github.com/git-for-windows/git/blob/c5a03b1e29c69f3f06c8fabd92493edb73469176/compat/winansi.c#L558
* Reline: Fix changed test results due to change to UTF-8 on Windows
In commit f8ea2860b0cac1aec79978e6c44168802958e8af 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
https://github.com/ruby/irb/commit/ed5cf375a6
https://github.com/ruby/irb/commit/5b7bbf9c34
* [ruby/reline] Implement vi_change_meta
https://github.com/ruby/reline/commit/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]
https://github.com/ruby/reline/commit/788f0df845
* [ruby/irb] Add a new easter egg: dancing ruby
https://github.com/ruby/irb/commit/e37dc7e58e
* [ruby/irb] Exclude useless files from RDoc
https://github.com/ruby/irb/commit/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.
https://github.com/ruby/reline/commit/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.
https://github.com/ruby/reline/commit/4bfea07e4a
* [ruby/reline] Organize special keys escape sequences
https://github.com/ruby/reline/commit/41deb1a3d9
* [ruby/readline-ext] Remove unnecessary -I$(top_srcdir) when it's an individual gem
https://github.com/ruby/readline-ext/commit/efaca4a5f4
* [ruby/readline-ext] Check TestRelineAsReadline existance
https://github.com/ruby/readline-ext/commit/c0a6303168
* [ruby/readline-ext] The ruby/assert.h is adopted by Ruby 2.7 or later
https://github.com/ruby/readline-ext/commit/106c31fc1b
* Revert "[ruby/readline-ext] Include ruby/assert.h in ruby/ruby.h so that assertions can be there"
This reverts commit 425b2064d394639101854c83a061a0918b33b857.
This cherry-pick was a mistake.
* [ruby/readline-ext] Use require check instead of DONT_RUN_RELINE_TEST env
https://github.com/ruby/readline-ext/commit/1df99d1481
* [ruby/readline-ext] Add spec.extensions
https://github.com/ruby/readline-ext/commit/8c33abb13c
* [ruby/readline-ext] Use rake/extensiokntask to build
https://github.com/ruby/readline-ext/commit/b0b5f709bd
* Fix readline build dependency
* [ruby/irb] Add test_complete_symbol
https://github.com/ruby/irb/commit/dbbf086c1f
* [ruby/irb] Check doc namespace correctly
IRB::InputCompletor::PerfectMatchedProc crashes when doc not found because a
variable name was incorrect.
https://github.com/ruby/irb/commit/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.
https://github.com/ruby/irb/commit/fbe59e344f
* [ruby/irb] Use 0.step instead of (..0).each for Ruby 2.5
https://github.com/ruby/irb/commit/5d628ca40e
* Revert "[ruby/irb] Add test_complete_symbol"
This reverts commit 3af3431c2c145134996e66f3d8d9ade8ad81bde0.
* [ruby/irb] fix reserved words and completion for them
https://github.com/ruby/irb/commit/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.
https://github.com/ruby/reline/commit/a93119c847
* [ruby/irb] Version 1.2.2
https://github.com/ruby/irb/commit/a71753f15a
* [ruby/reline] Version 0.1.3
https://github.com/ruby/reline/commit/ea2b182466
* [ruby/irb] Include easter-egg.rb in gemspec
`irb` doesn't run because this file isn't included in the gem.
https://github.com/ruby/irb/commit/73cda56d25
* [ruby/irb] Version 1.2.3
https://github.com/ruby/irb/commit/dd56e06df5
* support multi-run test for test_readline.rb
* [ruby/irb] `yield` outside method definition is a syntax error
https://github.com/ruby/irb/commit/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
https://github.com/ruby/actions/runs/500526558?check_suite_focus=true#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
https://github.com/ruby/irb/commit/0a641a69b0
* [ruby/reline] Work with wrong $/ value correctly
https://github.com/ruby/reline/commit/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
https://github.com/ruby/irb/commit/40d6610baf
* [ruby/reline] Suppress error in case INPUTRC env is empty
https://github.com/ruby/reline/commit/bce7e7562b
* [ruby/reline] Add yamatanooroti rendering test
https://github.com/ruby/reline/commit/f092519525
* [ruby/reline] Rename test suite name of yamatanooroti test
https://github.com/ruby/reline/commit/b0f32f5de4
* [ruby/reline] Add a comment why rescue yamatanooroti loading error on the test
https://github.com/ruby/reline/commit/2a8061daec
* [ruby/irb] Suppress crashing when EncodingError has occurred without lineno
https://github.com/ruby/irb/commit/13572d8cdc
* [ruby/reline] Suppress error when check ambiguous char width in LANG=C
https://github.com/ruby/reline/commit/623dffdd75
* [ruby/io-console] Enable only interrupt bits on `intr: true`
https://github.com/ruby/io-console/commit/baaf929041
* [ruby/io-console] bump up to 0.5.4
* [ruby/io-console] Update the minimum requirement of Ruby version
https://github.com/ruby/io-console/commit/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.
https://github.com/ruby/io-console/commit/e5951aa34c
* [ruby/io-console] bump up to 0.5.5
* [ruby/io-console] Prefer keyword arguments
https://github.com/ruby/io-console/commit/5facbfc4c8
* [ruby/io-console] [DOC] Improved about `intr:`
https://github.com/ruby/io-console/commit/82b630cd79
* [ruby/io-console] Just ignore the extension on other than CRuby
https://github.com/ruby/io-console/commit/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>
Diffstat (limited to 'lib')
34 files changed, 494 insertions, 103 deletions
diff --git a/lib/benchmark/benchmark.gemspec b/lib/benchmark/benchmark.gemspec index aad5205f8d..773cab19b0 100644 --- a/lib/benchmark/benchmark.gemspec +++ b/lib/benchmark/benchmark.gemspec @@ -21,7 +21,7 @@ Gem::Specification.new do |spec| # 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. 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 spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } diff --git a/lib/cgi/cgi.gemspec b/lib/cgi/cgi.gemspec index 403d31c978..58bd77027d 100644 --- a/lib/cgi/cgi.gemspec +++ b/lib/cgi/cgi.gemspec @@ -19,7 +19,7 @@ Gem::Specification.new do |spec| spec.metadata["source_code_uri"] = spec.homepage 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 spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } diff --git a/lib/delegate/delegate.gemspec b/lib/delegate/delegate.gemspec index e51b50a98f..268cc5a817 100644 --- a/lib/delegate/delegate.gemspec +++ b/lib/delegate/delegate.gemspec @@ -19,7 +19,7 @@ Gem::Specification.new do |spec| spec.metadata["source_code_uri"] = spec.homepage 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 spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } diff --git a/lib/getoptlong/getoptlong.gemspec b/lib/getoptlong/getoptlong.gemspec index 198bba83ac..5e218b8e93 100644 --- a/lib/getoptlong/getoptlong.gemspec +++ b/lib/getoptlong/getoptlong.gemspec @@ -21,7 +21,7 @@ Gem::Specification.new do |spec| # 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. 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 spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } diff --git a/lib/irb.rb b/lib/irb.rb index bcd6599af9..ee6979c6d0 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -21,6 +21,7 @@ require "irb/locale" require "irb/color" require "irb/version" +require "irb/easter-egg" # IRB stands for "interactive Ruby" and is a tool to interactively execute Ruby # expressions read from the standard input. @@ -553,7 +554,8 @@ module IRB def handle_exception(exc) 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 else irb_bug = false @@ -736,7 +738,13 @@ module IRB end 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 # Outputs the local variables to this current session, including diff --git a/lib/irb/.document b/lib/irb/.document new file mode 100644 index 0000000000..3b0d6fa4ed --- /dev/null +++ b/lib/irb/.document @@ -0,0 +1 @@ +**/*.rb diff --git a/lib/irb/completion.rb b/lib/irb/completion.rb index 3536e8ec87..c44aa9039e 100644 --- a/lib/irb/completion.rb +++ b/lib/irb/completion.rb @@ -17,11 +17,12 @@ module IRB # Set of reserved words used by Ruby, you should not use these for # constants or variables ReservedWords = %w[ + __ENCODING__ __LINE__ __FILE__ BEGIN END alias and begin break case class - def defined do + def defined? do else elsif end ensure false for if in @@ -98,7 +99,11 @@ module IRB return nil if doc_namespace if Symbol.respond_to?(:all_symbols) 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)}/) else [] @@ -143,7 +148,7 @@ module IRB select_message(receiver, message, candidates, sep) 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 receiver = $~[:num] sep = $~[:sep] @@ -203,7 +208,7 @@ module IRB sep = $2 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} iv = eval("instance_variables", bind).collect{|m| m.to_s} cv = eval("self.class.constants", bind).collect{|m| m.to_s} @@ -255,7 +260,7 @@ module IRB else 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 candidates.find{ |i| i == input } @@ -265,18 +270,14 @@ module IRB end end - PerfectMatchedProc = ->(matched) { + PerfectMatchedProc = ->(matched, bind: IRB.conf[:MAIN_CONTEXT].workspace.binding) { RDocRIDriver ||= RDoc::RI::Driver.new 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| - RDocRIDriver.page do |io| - IO.copy_stream(f, io) - end - end + IRB.send(:easter_egg) return end - namespace = retrieve_completion_data(matched, doc_namespace: true) - return unless matched + namespace = retrieve_completion_data(matched, bind: bind, doc_namespace: true) + return unless namespace if namespace.is_a?(Array) out = RDoc::Markup::Document.new namespace.each do |m| diff --git a/lib/irb/context.rb b/lib/irb/context.rb index 686738cd40..218f7c6037 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -133,6 +133,11 @@ module IRB if @echo_on_assignment.nil? @echo_on_assignment = false end + + @newline_before_multiline_output = IRB.conf[:NEWLINE_BEFORE_MULTILINE_OUTPUT] + if @newline_before_multiline_output.nil? + @newline_before_multiline_output = true + end end # The top-level workspace, see WorkSpace#main @@ -253,6 +258,20 @@ module IRB # a = "omg" # #=> omg 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. # # A copy of the default <code>IRB.conf[:VERBOSE]</code> @@ -287,6 +306,7 @@ module IRB alias ignore_eof? ignore_eof alias echo? echo alias echo_on_assignment? echo_on_assignment + alias newline_before_multiline_output? newline_before_multiline_output # Returns whether messages are displayed or not. def verbose? diff --git a/lib/irb/easter-egg.rb b/lib/irb/easter-egg.rb new file mode 100644 index 0000000000..64869d85fa --- /dev/null +++ b/lib/irb/easter-egg.rb @@ -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__ diff --git a/lib/irb/ext/save-history.rb b/lib/irb/ext/save-history.rb index c0d4d372b7..edc4f234cc 100644 --- a/lib/irb/ext/save-history.rb +++ b/lib/irb/ext/save-history.rb @@ -72,7 +72,7 @@ module IRB end history_file = IRB.rc_file("_history") unless 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| l = l.chomp if self.class == ReidlineInputMethod and history.last&.end_with?("\\") @@ -107,7 +107,7 @@ module IRB raise 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") } f.puts(hist[-num..-1] || hist) end diff --git a/lib/irb/init.rb b/lib/irb/init.rb index 2af872fd03..37d1f8d609 100644 --- a/lib/irb/init.rb +++ b/lib/irb/init.rb @@ -296,15 +296,18 @@ module IRB # :nodoc: DefaultEncodings = Struct.new(:external, :internal) class << IRB private - def set_encoding(extern, intern = nil) + def set_encoding(extern, intern = nil, override: true) verbose, $VERBOSE = $VERBOSE, nil Encoding.default_external = extern unless extern.nil? || extern.empty? Encoding.default_internal = intern unless intern.nil? || intern.empty? - @CONF[:ENCODINGS] = IRB::DefaultEncodings.new(extern, intern) [$stdin, $stdout, $stderr].each do |io| io.set_encoding(extern, intern) end - @CONF[:LC_MESSAGES].instance_variable_set(:@encoding, extern) + if override + @CONF[:LC_MESSAGES].instance_variable_set(:@override_encoding, extern) + else + @CONF[:LC_MESSAGES].instance_variable_set(:@encoding, extern) + end ensure $VERBOSE = verbose end diff --git a/lib/irb/input-method.rb b/lib/irb/input-method.rb index a1777d7904..9fbbaeb0f3 100644 --- a/lib/irb/input-method.rb +++ b/lib/irb/input-method.rb @@ -133,6 +133,9 @@ module IRB include Readline # Creates a new input method object using Readline def initialize + if Readline.respond_to?(:encoding_system_needs) + IRB.__send__(:set_encoding, Readline.encoding_system_needs.name, override: false) + end super @line_no = 0 @@ -207,6 +210,7 @@ module IRB include Reline # Creates a new input method object using Readline def initialize + IRB.__send__(:set_encoding, Reline.encoding_system_needs.name, override: false) super @line_no = 0 diff --git a/lib/irb/irb.gemspec b/lib/irb/irb.gemspec index c3208954c5..658fe813f1 100644 --- a/lib/irb/irb.gemspec +++ b/lib/irb/irb.gemspec @@ -17,6 +17,7 @@ Gem::Specification.new do |spec| spec.license = "BSD-2-Clause" spec.files = [ + ".document", "Gemfile", "LICENSE.txt", "README.md", @@ -38,6 +39,7 @@ Gem::Specification.new do |spec| "lib/irb/color.rb", "lib/irb/completion.rb", "lib/irb/context.rb", + "lib/irb/easter-egg.rb", "lib/irb/ext/change-ws.rb", "lib/irb/ext/history.rb", "lib/irb/ext/loader.rb", @@ -52,7 +54,6 @@ Gem::Specification.new do |spec| "lib/irb/init.rb", "lib/irb/input-method.rb", "lib/irb/inspector.rb", - "lib/irb/lc/.document", "lib/irb/lc/error.rb", "lib/irb/lc/help-message", "lib/irb/lc/ja/encoding_aliases.rb", diff --git a/lib/irb/lc/.document b/lib/irb/lc/.document deleted file mode 100644 index 524bb9430b..0000000000 --- a/lib/irb/lc/.document +++ /dev/null @@ -1,4 +0,0 @@ -# hide help-message files which contain usage information -error.rb -ja/encoding_aliases.rb -ja/error.rb diff --git a/lib/irb/locale.rb b/lib/irb/locale.rb index ba833eced4..bb44b41002 100644 --- a/lib/irb/locale.rb +++ b/lib/irb/locale.rb @@ -24,6 +24,7 @@ module IRB # :nodoc: @@loaded = [] def initialize(locale = nil) + @override_encoding = nil @lang = @territory = @encoding_name = @modifier = nil @locale = locale || ENV["IRB_LANG"] || ENV["LC_MESSAGES"] || ENV["LC_ALL"] || ENV["LANG"] || "C" if m = LOCALE_NAME_RE.match(@locale) @@ -40,12 +41,16 @@ module IRB # :nodoc: @encoding ||= (Encoding.find('locale') rescue Encoding::ASCII_8BIT) end - attr_reader :lang, :territory, :encoding, :modifier + attr_reader :lang, :territory, :modifier + + def encoding + @override_encoding || @encoding + end def String(mes) mes = super(mes) - if @encoding - mes.encode(@encoding, undef: :replace) + if encoding + mes.encode(encoding, undef: :replace) else mes end diff --git a/lib/irb/ruby-lex.rb b/lib/irb/ruby-lex.rb index b4c31c16fe..d5630c8b52 100644 --- a/lib/irb/ruby-lex.rb +++ b/lib/irb/ruby-lex.rb @@ -211,6 +211,8 @@ class RubyLex else RubyVM::InstructionSequence.compile(code) end + rescue EncodingError + # This is for a hash with invalid encoding symbol, {"\xAE": 1} rescue SyntaxError => e case e.message when /unterminated (?:string|regexp) meets end of file/ @@ -317,11 +319,13 @@ class RubyLex def check_newline_depth_difference depth_difference = 0 + open_brace_on_line = 0 @tokens.each_with_index do |t, index| case t[1] when :on_ignored_nl, :on_nl, :on_comment if index != (@tokens.size - 1) depth_difference = 0 + open_brace_on_line = 0 end next when :on_sp @@ -330,8 +334,9 @@ class RubyLex case t[1] when :on_lbracket, :on_lbrace, :on_lparen depth_difference += 1 + open_brace_on_line += 1 when :on_rbracket, :on_rbrace, :on_rparen - depth_difference -= 1 + depth_difference -= 1 if open_brace_on_line > 0 when :on_kw next if index > 0 and @tokens[index - 1][3].allbits?(Ripper::EXPR_FNAME) case t[2] @@ -365,6 +370,7 @@ class RubyLex is_first_printable_of_line = true spaces_of_nest = [] spaces_at_line_head = 0 + open_brace_on_line = 0 @tokens.each_with_index do |t, index| case t[1] when :on_ignored_nl, :on_nl, :on_comment @@ -372,6 +378,7 @@ class RubyLex spaces_at_line_head = 0 is_first_spaces_of_line = true is_first_printable_of_line = true + open_brace_on_line = 0 next when :on_sp spaces_at_line_head = t[2].count(' ') if is_first_spaces_of_line @@ -380,7 +387,8 @@ class RubyLex end case t[1] 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 if is_first_printable_of_line corresponding_token_depth = spaces_of_nest.pop @@ -388,6 +396,7 @@ class RubyLex spaces_of_nest.pop corresponding_token_depth = nil end + open_brace_on_line -= 1 when :on_kw next if index > 0 and @tokens[index - 1][3].allbits?(Ripper::EXPR_FNAME) case t[2] diff --git a/lib/irb/version.rb b/lib/irb/version.rb index 9b8bd9b161..7669c1c535 100644 --- a/lib/irb/version.rb +++ b/lib/irb/version.rb @@ -11,7 +11,7 @@ # module IRB # :nodoc: - VERSION = "1.2.1" + VERSION = "1.2.3" @RELEASE_VERSION = VERSION - @LAST_UPDATE_DATE = "2019-12-24" + @LAST_UPDATE_DATE = "2020-02-15" end diff --git a/lib/net/pop/net-pop.gemspec b/lib/net/pop/net-pop.gemspec index 8166968d7d..c1b0ffbd2b 100644 --- a/lib/net/pop/net-pop.gemspec +++ b/lib/net/pop/net-pop.gemspec @@ -19,7 +19,7 @@ Gem::Specification.new do |spec| spec.metadata["source_code_uri"] = spec.homepage 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 spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } diff --git a/lib/net/smtp/net-smtp.gemspec b/lib/net/smtp/net-smtp.gemspec index e9635cd091..1dddfa7ca8 100644 --- a/lib/net/smtp/net-smtp.gemspec +++ b/lib/net/smtp/net-smtp.gemspec @@ -19,7 +19,7 @@ Gem::Specification.new do |spec| spec.metadata["source_code_uri"] = spec.homepage 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 spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } diff --git a/lib/observer/observer.gemspec b/lib/observer/observer.gemspec index 625a30eada..188c6bae76 100644 --- a/lib/observer/observer.gemspec +++ b/lib/observer/observer.gemspec @@ -21,7 +21,7 @@ Gem::Specification.new do |spec| # 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. 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 spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } diff --git a/lib/open3/open3.gemspec b/lib/open3/open3.gemspec index 65ddaa4723..543416e427 100644 --- a/lib/open3/open3.gemspec +++ b/lib/open3/open3.gemspec @@ -21,7 +21,7 @@ Gem::Specification.new do |spec| # 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. 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 spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } diff --git a/lib/pstore/pstore.gemspec b/lib/pstore/pstore.gemspec index 408af0a078..e781c77043 100644 --- a/lib/pstore/pstore.gemspec +++ b/lib/pstore/pstore.gemspec @@ -21,7 +21,7 @@ Gem::Specification.new do |spec| # 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. 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 spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } diff --git a/lib/reline.rb b/lib/reline.rb index 9a9f742909..1537ee7e69 100644 --- a/lib/reline.rb +++ b/lib/reline.rb @@ -45,40 +45,44 @@ module Reline @completion_quote_character = nil end + def encoding + Reline::IOGate.encoding + end + def completion_append_character=(val) if val.nil? @completion_append_character = nil elsif val.size == 1 - @completion_append_character = val.encode(Encoding::default_external) + @completion_append_character = val.encode(Reline::IOGate.encoding) elsif val.size > 1 - @completion_append_character = val[0].encode(Encoding::default_external) + @completion_append_character = val[0].encode(Reline::IOGate.encoding) else @completion_append_character = nil end end 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 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 def basic_quote_characters=(v) - @basic_quote_characters = v.encode(Encoding::default_external) + @basic_quote_characters = v.encode(Reline::IOGate.encoding) end def completer_quote_characters=(v) - @completer_quote_characters = v.encode(Encoding::default_external) + @completer_quote_characters = v.encode(Reline::IOGate.encoding) end def filename_quote_characters=(v) - @filename_quote_characters = v.encode(Encoding::default_external) + @filename_quote_characters = v.encode(Reline::IOGate.encoding) end def special_prefixes=(v) - @special_prefixes = v.encode(Encoding::default_external) + @special_prefixes = v.encode(Reline::IOGate.encoding) end def completion_case_fold=(v) @@ -171,7 +175,7 @@ module Reline whole_buffer = line_editor.whole_buffer.dup 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 end @@ -184,8 +188,8 @@ module Reline line = line_editor.line.dup line.taint if RUBY_VERSION < '2.7' - if add_hist and line and line.chomp.size > 0 - Reline::HISTORY << line.chomp + if add_hist and line and line.chomp("\n").size > 0 + Reline::HISTORY << line.chomp("\n") end line_editor.reset_line if line_editor.line.nil? @@ -201,7 +205,7 @@ module Reline otio = Reline::IOGate.prep may_req_ambiguous_char_width - line_editor.reset(prompt) + line_editor.reset(prompt, encoding: Reline::IOGate.encoding) if multiline line_editor.multiline_on if block_given? @@ -332,8 +336,14 @@ module Reline @ambiguous_width = 2 if Reline::IOGate == Reline::GeneralIO or STDOUT.is_a?(File) return if ambiguous_width Reline::IOGate.move_cursor_column(0) - print "\u{25bd}" - @ambiguous_width = Reline::IOGate.cursor_pos.x + begin + output.write "\u{25bd}" + rescue Encoding::UndefinedConversionError + # LANG=C + @ambiguous_width = 1 + else + @ambiguous_width = Reline::IOGate.cursor_pos.x + end Reline::IOGate.move_cursor_column(0) Reline::IOGate.erase_after_cursor end @@ -387,11 +397,15 @@ module Reline def_instance_delegators self, :readmultiline private :readmultiline + def self.encoding_system_needs + self.core.encoding + end + def self.core @core ||= Core.new { |core| core.config = Reline::Config.new 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.completer_word_break_characters = " \t\n`><=;|&{(" @@ -405,14 +419,11 @@ module Reline def self.line_editor core.line_editor end - - HISTORY = History.new(core.config) end if RbConfig::CONFIG['host_os'] =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/ require 'reline/windows' - if Reline::Windows.get_screen_size == [0, 0] - # Maybe Mintty on Cygwin + if Reline::Windows.msys_tty? require 'reline/ansi' Reline::IOGate = Reline::ANSI else @@ -422,4 +433,5 @@ else require 'reline/ansi' Reline::IOGate = Reline::ANSI end +Reline::HISTORY = Reline::History.new(Reline.core.config) require 'reline/general_io' diff --git a/lib/reline/ansi.rb b/lib/reline/ansi.rb index 8d83da854c..3ef02d6e7a 100644 --- a/lib/reline/ansi.rb +++ b/lib/reline/ansi.rb @@ -1,20 +1,49 @@ require 'io/console' class Reline::ANSI + def self.encoding + Encoding.default_external + end + + def self.win? + false + end + 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, 66] => :ed_next_history, # ↓ [27, 91, 67] => :ed_next_char, # → [27, 91, 68] => :ed_prev_char, # ← - [27, 91, 51, 126] => :key_delete, # Del - [27, 91, 49, 126] => :ed_move_to_beg, # Home - [27, 91, 52, 126] => :ed_move_to_end, # End + + # KDE [27, 91, 72] => :ed_move_to_beg, # Home [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> [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, 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 @@ -41,16 +70,23 @@ class Reline::ANSI end def self.retrieve_keybuffer + begin result = select([@@input], [], [], 0.001) return if result.nil? str = @@input.read_nonblock(1024) str.bytes.each do |c| @@buf.push(c) end + rescue EOFError + end end 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 [24, 80] end @@ -88,12 +124,12 @@ class Reline::ANSI end def self.move_cursor_column(x) - print "\e[#{x + 1}G" + @@output.write "\e[#{x + 1}G" end def self.move_cursor_up(x) if x > 0 - print "\e[#{x}A" if x > 0 + @@output.write "\e[#{x}A" if x > 0 elsif x < 0 move_cursor_down(-x) end @@ -101,24 +137,24 @@ class Reline::ANSI def self.move_cursor_down(x) if x > 0 - print "\e[#{x}B" if x > 0 + @@output.write "\e[#{x}B" if x > 0 elsif x < 0 move_cursor_up(-x) end end def self.erase_after_cursor - print "\e[K" + @@output.write "\e[K" end def self.scroll_down(x) return if x.zero? - print "\e[#{x}S" + @@output.write "\e[#{x}S" end def self.clear_screen - print "\e[2J" - print "\e[1;1H" + @@output.write "\e[2J" + @@output.write "\e[1;1H" end @@old_winch_handler = nil diff --git a/lib/reline/config.rb b/lib/reline/config.rb index fdc2b39c1b..53b868fd2e 100644 --- a/lib/reline/config.rb +++ b/lib/reline/config.rb @@ -83,8 +83,17 @@ class Reline::Config @key_actors[@keymap_label] end + def inputrc_path + case ENV['INPUTRC'] + when nil, '' + DEFAULT_PATH + else + ENV['INPUTRC'] + end + end + def read(file = nil) - file ||= File.expand_path(ENV['INPUTRC'] || DEFAULT_PATH) + file ||= File.expand_path(inputrc_path) begin if file.respond_to?(:readlines) lines = file.readlines @@ -184,9 +193,8 @@ class Reline::Config def bind_variable(name, value) case name - when *VARIABLE_NAMES then - variable_name = :"@#{name.tr(?-, ?_)}" - instance_variable_set(variable_name, value.nil? || value == '1' || value == 'on') + when 'history-size' + @history_size = value.to_i when 'bell-style' @bell_style = case value @@ -225,6 +233,9 @@ class Reline::Config end when 'keyseq-timeout' @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 diff --git a/lib/reline/general_io.rb b/lib/reline/general_io.rb index 291c14c7b3..85f1f13eed 100644 --- a/lib/reline/general_io.rb +++ b/lib/reline/general_io.rb @@ -1,6 +1,14 @@ require 'timeout' 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 = {} @@buf = [] diff --git a/lib/reline/history.rb b/lib/reline/history.rb index 238fcf2a76..d95f1cebc3 100644 --- a/lib/reline/history.rb +++ b/lib/reline/history.rb @@ -19,7 +19,7 @@ class Reline::History < Array def []=(index, val) index = check_index(index) - super(index, String.new(val, encoding: Encoding::default_external)) + super(index, String.new(val, encoding: Reline.encoding_system_needs)) end def concat(*val) @@ -39,12 +39,12 @@ class Reline::History < Array val.shift(diff) 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 def <<(val) 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 private def check_index(index) diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb index 75af50a908..095a7b5a09 100644 --- a/lib/reline/line_editor.rb +++ b/lib/reline/line_editor.rb @@ -57,10 +57,10 @@ class Reline::LineEditor NON_PRINTING_END = "\2" WIDTH_SCANNER = /\G(?:#{NON_PRINTING_START}|#{NON_PRINTING_END}|#{CSI_REGEXP}|#{OSC_REGEXP}|\X)/ - def initialize(config) + def initialize(config, encoding) @config = config @completion_append_character = '' - reset_variables + reset_variables(encoding: encoding) end private def check_multiline_prompt(buffer, prompt) @@ -85,10 +85,10 @@ class Reline::LineEditor 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 @screen_size = Reline::IOGate.get_screen_size - reset_variables(prompt, encoding) + reset_variables(prompt, encoding: encoding) @old_trap = Signal.trap('SIGINT') { @old_trap.call if @old_trap.respond_to?(:call) # can also be string, ex: "DEFAULT" raise Interrupt @@ -139,7 +139,7 @@ class Reline::LineEditor @eof end - def reset_variables(prompt = '', encoding = Encoding.default_external) + def reset_variables(prompt = '', encoding:) @prompt = prompt @mark_pointer = nil @encoding = encoding @@ -317,9 +317,9 @@ class Reline::LineEditor if @menu_info scroll_down(@highest_in_all - @first_line_started_from) @rerender_all = true - @menu_info.list.each do |item| + @menu_info.list.sort!.each do |item| Reline::IOGate.move_cursor_column(0) - @output.print item + @output.write item @output.flush scroll_down(1) end @@ -507,12 +507,20 @@ class Reline::LineEditor Reline::IOGate.move_cursor_column(0) visual_lines.each_with_index do |line, index| if line.nil? - Reline::IOGate.erase_after_cursor - move_cursor_down(1) - Reline::IOGate.move_cursor_column(0) + 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 + move_cursor_down(1) + Reline::IOGate.move_cursor_column(0) + end next 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 if @first_prompt @first_prompt = false @@ -535,7 +543,7 @@ class Reline::LineEditor return before if before.nil? || before.empty? if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: finished?) - after.lines(chomp: true) + after.lines("\n", chomp: true) else before end @@ -905,7 +913,6 @@ class Reline::LineEditor quote = nil i += 1 rest = nil - break_pointer = nil elsif quote and slice.start_with?(escaped_quote) # skip i += 2 @@ -915,7 +922,7 @@ class Reline::LineEditor closing_quote = /(?!\\)#{Regexp.escape(quote)}/ escaped_quote = /\\#{Regexp.escape(quote)}/ i += 1 - break_pointer = i + break_pointer = i - 1 elsif not quote and slice =~ word_break_regexp rest = $' i += 1 @@ -937,6 +944,11 @@ class Reline::LineEditor end else preposing = '' + if break_pointer + preposing = @line.byteslice(0, break_pointer) + else + preposing = '' + end target = before end [preposing.encode(@encoding), target.encode(@encoding), postposing.encode(@encoding)] @@ -1091,6 +1103,11 @@ class Reline::LineEditor private def ed_insert(key) if key.instance_of?(String) + begin + key.encode(Encoding::UTF_8) + rescue Encoding::UndefinedConversionError + return + end width = Reline::Unicode.get_mbchar_width(key) if @cursor == @cursor_max @line += key @@ -1101,6 +1118,11 @@ class Reline::LineEditor @cursor += width @cursor_max += width else + begin + key.chr.encode(Encoding::UTF_8) + rescue Encoding::UndefinedConversionError + return + end if @cursor == @cursor_max @line += key.chr else @@ -1876,6 +1898,16 @@ class Reline::LineEditor 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) deleted = '' arg.times do @@ -1898,6 +1930,18 @@ class Reline::LineEditor end 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 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) } 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) inputed_char = key else inputed_char = key.chr end + prev_total = nil total = nil found = false @line.byteslice(@byte_pointer..-1).grapheme_clusters.each do |mbchar| @@ -2086,13 +2135,66 @@ class Reline::LineEditor end end width = Reline::Unicode.get_mbchar_width(mbchar) + prev_total = total total = [total.first + mbchar.bytesize, total.last + width] end end - if found and total + if not need_prev_char and found and total byte_size, width = total @byte_pointer += byte_size @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 @waiting_proc = nil end diff --git a/lib/reline/version.rb b/lib/reline/version.rb index 47b29bd946..1bf544d74b 100644 --- a/lib/reline/version.rb +++ b/lib/reline/version.rb @@ -1,3 +1,3 @@ module Reline - VERSION = '0.1.2' + VERSION = '0.1.3' end diff --git a/lib/reline/windows.rb b/lib/reline/windows.rb index aef3073a7e..c229c8536f 100644 --- a/lib/reline/windows.rb +++ b/lib/reline/windows.rb @@ -1,6 +1,14 @@ require 'fiddle/import' class Reline::Windows + def self.encoding + Encoding::UTF_8 + end + + def self.win? + true + end + RAW_KEYSTROKE_CONFIG = { [224, 72] => :ed_prev_history, # ↑ [224, 80] => :ed_next_history, # ↓ @@ -68,6 +76,8 @@ class Reline::Windows STD_INPUT_HANDLE = -10 STD_OUTPUT_HANDLE = -11 WINDOW_BUFFER_SIZE_EVENT = 0x04 + FILE_TYPE_PIPE = 0x0003 + FILE_NAME_INFO = 2 @@getwch = Win32API.new('msvcrt', '_getwch', [], 'I') @@kbhit = Win32API.new('msvcrt', '_kbhit', [], 'I') @@GetKeyState = Win32API.new('user32', 'GetKeyState', ['L'], 'L') @@ -80,9 +90,36 @@ class Reline::Windows @@hConsoleInputHandle = @@GetStdHandle.call(STD_INPUT_HANDLE) @@GetNumberOfConsoleInputEvents = Win32API.new('kernel32', 'GetNumberOfConsoleInputEvents', ['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 = [] @@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 unless @@input_buf.empty? return @@input_buf.shift @@ -99,7 +136,7 @@ class Reline::Windows return @@input_buf.shift end begin - bytes = ret.chr(Encoding::UTF_8).encode(Encoding.default_external).bytes + bytes = ret.chr(Encoding::UTF_8).bytes @@input_buf.push(*bytes) rescue Encoding::UndefinedConversionError @@input_buf << ret @@ -205,7 +242,7 @@ class Reline::Windows def self.scroll_down(val) 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 fill = [' '.ord, 0].pack('SS') @@ScrollConsoleScreenBuffer.call(@@hConsoleHandle, scroll_rectangle, nil, destination_origin, fill) @@ -213,8 +250,8 @@ class Reline::Windows def self.clear_screen # TODO: Use FillConsoleOutputCharacter and FillConsoleOutputAttribute - print "\e[2J" - print "\e[1;1H" + write "\e[2J" + write "\e[1;1H" end def self.set_screen_size(rows, columns) diff --git a/lib/singleton/singleton.gemspec b/lib/singleton/singleton.gemspec index f7ee6bb2dc..c6a273a839 100644 --- a/lib/singleton/singleton.gemspec +++ b/lib/singleton/singleton.gemspec @@ -19,7 +19,7 @@ Gem::Specification.new do |spec| spec.metadata["source_code_uri"] = spec.homepage 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 spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } diff --git a/lib/timeout/timeout.gemspec b/lib/timeout/timeout.gemspec index 88babe9f8b..7b650bdc31 100644 --- a/lib/timeout/timeout.gemspec +++ b/lib/timeout/timeout.gemspec @@ -19,7 +19,7 @@ Gem::Specification.new do |spec| spec.metadata["source_code_uri"] = spec.homepage 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 spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } diff --git a/lib/uri/uri.gemspec b/lib/uri/uri.gemspec index 5c51721755..95cb8e2d42 100644 --- a/lib/uri/uri.gemspec +++ b/lib/uri/uri.gemspec @@ -21,7 +21,7 @@ Gem::Specification.new do |spec| # 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. 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 spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } diff --git a/lib/yaml/yaml.gemspec b/lib/yaml/yaml.gemspec index d78e8164a4..ba5027a9b6 100644 --- a/lib/yaml/yaml.gemspec +++ b/lib/yaml/yaml.gemspec @@ -15,7 +15,7 @@ Gem::Specification.new do |spec| # 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. 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 spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } |