summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHiroshi SHIBATA <hsbt@ruby-lang.org>2024-05-29 07:54:39 +0900
committerGitHub <noreply@github.com>2024-05-28 15:54:39 -0700
commit077558ee2b8dd3ed414b78384f21118f833eb259 (patch)
tree016aceed4ca1c0ab049fc2a6260a9267677b1ea5
parent917f3e5d22b3364002eb1fdc2f94b35ff76f6a73 (diff)
[Bug #20511] Update reline-0.5.7 (#10848)
* Update reline-0.5.7 * Update irb-1.13.1
-rw-r--r--doc/irb/indexes.md3
-rw-r--r--lib/irb.rb1377
-rw-r--r--lib/irb/cmd/backtrace.rb21
-rw-r--r--lib/irb/cmd/break.rb21
-rw-r--r--lib/irb/cmd/catch.rb21
-rw-r--r--lib/irb/cmd/chws.rb36
-rw-r--r--lib/irb/cmd/edit.rb60
-rw-r--r--lib/irb/cmd/help.rb23
-rw-r--r--lib/irb/cmd/info.rb21
-rw-r--r--lib/irb/cmd/nop.rb55
-rw-r--r--lib/irb/cmd/pushws.rb45
-rw-r--r--lib/irb/cmd/show_cmds.rb59
-rw-r--r--lib/irb/cmd/show_doc.rb48
-rw-r--r--lib/irb/cmd/show_source.rb65
-rw-r--r--lib/irb/color.rb4
-rw-r--r--lib/irb/command.rb23
-rw-r--r--lib/irb/command/backtrace.rb17
-rw-r--r--lib/irb/command/base.rb62
-rw-r--r--lib/irb/command/break.rb17
-rw-r--r--lib/irb/command/catch.rb17
-rw-r--r--lib/irb/command/chws.rb40
-rw-r--r--lib/irb/command/context.rb16
-rw-r--r--lib/irb/command/continue.rb (renamed from lib/irb/cmd/continue.rb)6
-rw-r--r--lib/irb/command/debug.rb (renamed from lib/irb/cmd/debug.rb)29
-rw-r--r--lib/irb/command/delete.rb (renamed from lib/irb/cmd/delete.rb)6
-rw-r--r--lib/irb/command/disable_irb.rb19
-rw-r--r--lib/irb/command/edit.rb63
-rw-r--r--lib/irb/command/exit.rb18
-rw-r--r--lib/irb/command/finish.rb (renamed from lib/irb/cmd/finish.rb)6
-rw-r--r--lib/irb/command/force_exit.rb18
-rw-r--r--lib/irb/command/help.rb83
-rw-r--r--lib/irb/command/history.rb (renamed from lib/irb/cmd/history.rb)16
-rw-r--r--lib/irb/command/info.rb17
-rw-r--r--lib/irb/command/internal_helpers.rb27
-rw-r--r--lib/irb/command/irb_info.rb (renamed from lib/irb/cmd/irb_info.rb)13
-rw-r--r--lib/irb/command/load.rb (renamed from lib/irb/cmd/load.rb)31
-rw-r--r--lib/irb/command/ls.rb (renamed from lib/irb/cmd/ls.rb)44
-rw-r--r--lib/irb/command/measure.rb (renamed from lib/irb/cmd/measure.rb)24
-rw-r--r--lib/irb/command/next.rb (renamed from lib/irb/cmd/next.rb)6
-rw-r--r--lib/irb/command/pushws.rb65
-rw-r--r--lib/irb/command/show_doc.rb51
-rw-r--r--lib/irb/command/show_source.rb74
-rw-r--r--lib/irb/command/step.rb (renamed from lib/irb/cmd/step.rb)6
-rw-r--r--lib/irb/command/subirb.rb (renamed from lib/irb/cmd/subirb.rb)48
-rw-r--r--lib/irb/command/whereami.rb (renamed from lib/irb/cmd/whereami.rb)8
-rw-r--r--lib/irb/completion.rb31
-rw-r--r--lib/irb/context.rb140
-rw-r--r--lib/irb/default_commands.rb260
-rw-r--r--lib/irb/ext/change-ws.rb14
-rw-r--r--lib/irb/ext/eval_history.rb6
-rw-r--r--lib/irb/ext/loader.rb8
-rw-r--r--lib/irb/ext/multi-irb.rb10
-rw-r--r--lib/irb/ext/tracer.rb63
-rw-r--r--lib/irb/ext/use-loader.rb14
-rw-r--r--lib/irb/ext/workspaces.rb44
-rw-r--r--lib/irb/extend-command.rb360
-rw-r--r--lib/irb/frame.rb2
-rw-r--r--lib/irb/help.rb4
-rw-r--r--lib/irb/helper_method.rb29
-rw-r--r--lib/irb/helper_method/base.rb16
-rw-r--r--lib/irb/helper_method/conf.rb11
-rw-r--r--lib/irb/history.rb17
-rw-r--r--lib/irb/init.rb115
-rw-r--r--lib/irb/input-method.rb64
-rw-r--r--lib/irb/inspector.rb6
-rw-r--r--lib/irb/irb.gemspec4
-rw-r--r--lib/irb/lc/error.rb12
-rw-r--r--lib/irb/lc/ja/error.rb12
-rw-r--r--lib/irb/lc/ja/help-message10
-rw-r--r--lib/irb/locale.rb4
-rw-r--r--lib/irb/nesting_parser.rb16
-rw-r--r--lib/irb/notifier.rb2
-rw-r--r--lib/irb/output-method.rb10
-rw-r--r--lib/irb/ruby-lex.rb4
-rw-r--r--lib/irb/source_finder.rb135
-rw-r--r--lib/irb/statement.rb48
-rw-r--r--lib/irb/version.rb6
-rw-r--r--lib/irb/workspace.rb28
-rw-r--r--lib/irb/ws-for-case-2.rb2
-rw-r--r--lib/irb/xmp.rb2
-rw-r--r--lib/reline.rb141
-rw-r--r--lib/reline/ansi.rb75
-rw-r--r--lib/reline/config.rb74
-rw-r--r--lib/reline/face.rb6
-rw-r--r--lib/reline/general_io.rb13
-rw-r--r--lib/reline/history.rb2
-rw-r--r--lib/reline/key_actor/base.rb4
-rw-r--r--lib/reline/key_actor/emacs.rb26
-rw-r--r--lib/reline/key_actor/vi_command.rb46
-rw-r--r--lib/reline/key_actor/vi_insert.rb10
-rw-r--r--lib/reline/kill_ring.rb4
-rw-r--r--lib/reline/line_editor.rb2786
-rw-r--r--lib/reline/reline.gemspec5
-rw-r--r--lib/reline/terminfo.rb21
-rw-r--r--lib/reline/unicode.rb78
-rw-r--r--lib/reline/version.rb2
-rw-r--r--lib/reline/windows.rb6
-rw-r--r--test/irb/command/test_custom_command.rb149
-rw-r--r--test/irb/command/test_force_exit.rb51
-rw-r--r--test/irb/command/test_help.rb75
-rw-r--r--test/irb/command/test_multi_irb_commands.rb50
-rw-r--r--test/irb/command/test_show_source.rb (renamed from test/irb/cmd/test_show_source.rb)121
-rw-r--r--test/irb/helper.rb8
-rw-r--r--test/irb/test_color.rb3
-rw-r--r--test/irb/test_color_printer.rb1
-rw-r--r--test/irb/test_command.rb (renamed from test/irb/test_cmd.rb)186
-rw-r--r--test/irb/test_completion.rb19
-rw-r--r--test/irb/test_context.rb257
-rw-r--r--test/irb/test_debugger_integration.rb (renamed from test/irb/test_debug_cmd.rb)76
-rw-r--r--test/irb/test_eval_history.rb4
-rw-r--r--test/irb/test_helper_method.rb134
-rw-r--r--test/irb/test_history.rb233
-rw-r--r--test/irb/test_init.rb97
-rw-r--r--test/irb/test_input_method.rb18
-rw-r--r--test/irb/test_irb.rb176
-rw-r--r--test/irb/test_nesting_parser.rb38
-rw-r--r--test/irb/test_raise_exception.rb2
-rw-r--r--test/irb/test_ruby_lex.rb4
-rw-r--r--test/irb/test_tracer.rb90
-rw-r--r--test/irb/test_type_completor.rb7
-rw-r--r--test/irb/test_workspace.rb3
-rw-r--r--test/irb/yamatanooroti/test_rendering.rb81
-rw-r--r--test/reline/helper.rb34
-rw-r--r--test/reline/test_ansi_with_terminfo.rb2
-rw-r--r--test/reline/test_config.rb116
-rw-r--r--test/reline/test_key_actor_emacs.rb1903
-rw-r--r--test/reline/test_key_actor_vi.rb1292
-rw-r--r--test/reline/test_line_editor.rb188
-rw-r--r--test/reline/test_macro.rb1
-rw-r--r--test/reline/test_reline.rb20
-rw-r--r--test/reline/test_string_processing.rb12
-rw-r--r--test/reline/test_terminfo.rb2
-rw-r--r--test/reline/test_unicode.rb31
-rw-r--r--test/reline/test_within_pipe.rb1
-rwxr-xr-xtest/reline/yamatanooroti/multiline_repl19
-rw-r--r--test/reline/yamatanooroti/test_rendering.rb226
136 files changed, 6571 insertions, 6696 deletions
diff --git a/doc/irb/indexes.md b/doc/irb/indexes.md
index 9659db8c0b..24a67b9698 100644
--- a/doc/irb/indexes.md
+++ b/doc/irb/indexes.md
@@ -165,9 +165,6 @@ for each entry that is pre-defined, the initial value is given:
- `:RC`: Whether a {configuration file}[rdoc-ref:IRB@Configuration+File]
was found and interpreted;
initial value: `true` if a configuration file was found, `false` otherwise.
-- `:RC_NAME_GENERATOR`: \Proc to generate paths of potential
- {configuration files}[rdoc-ref:IRB@Configuration+File];
- initial value: `=> #<Proc:0x000055f9bebfed80 /var/lib/gems/3.0.0/gems/irb-1.8.3/lib/irb/init.rb:401>`.
- `:SAVE_HISTORY`: Number of commands to save in
{input command history}[rdoc-ref:IRB@Input+Command+History];
initial value: `1000`.
diff --git a/lib/irb.rb b/lib/irb.rb
index 006b52bec5..b3435c257e 100644
--- a/lib/irb.rb
+++ b/lib/irb.rb
@@ -1,5 +1,6 @@
-# frozen_string_literal: false
-#
+# frozen_string_literal: true
+
+# :markup: markdown
# irb.rb - irb main module
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
#
@@ -9,7 +10,7 @@ require "reline"
require_relative "irb/init"
require_relative "irb/context"
-require_relative "irb/extend-command"
+require_relative "irb/default_commands"
require_relative "irb/ruby-lex"
require_relative "irb/statement"
@@ -22,546 +23,553 @@ require_relative "irb/easter-egg"
require_relative "irb/debug"
require_relative "irb/pager"
-# == \IRB
+# ## IRB
#
-# \Module \IRB ("Interactive Ruby") provides a shell-like interface
-# that supports user interaction with the Ruby interpreter.
+# Module IRB ("Interactive Ruby") provides a shell-like interface that supports
+# user interaction with the Ruby interpreter.
#
-# It operates as a <i>read-eval-print loop</i>
-# ({REPL}[https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop])
+# It operates as a *read-eval-print loop*
+# ([REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop))
# that:
#
-# - <b>_Reads_</b> each character as you type.
-# You can modify the \IRB context to change the way input works.
-# See {Input}[rdoc-ref:IRB@Input].
-# - <b>_Evaluates_</b> the code each time it has read a syntactically complete passage.
-# - <b>_Prints_</b> after evaluating.
-# You can modify the \IRB context to change the way output works.
-# See {Output}[rdoc-ref:IRB@Output].
+# * ***Reads*** each character as you type. You can modify the IRB context to
+# change the way input works. See [Input](rdoc-ref:IRB@Input).
+# * ***Evaluates*** the code each time it has read a syntactically complete
+# passage.
+# * ***Prints*** after evaluating. You can modify the IRB context to change
+# the way output works. See [Output](rdoc-ref:IRB@Output).
+#
#
# Example:
#
-# $ irb
-# irb(main):001> File.basename(Dir.pwd)
-# => "irb"
-# irb(main):002> Dir.entries('.').size
-# => 25
-# irb(main):003* Dir.entries('.').select do |entry|
-# irb(main):004* entry.start_with?('R')
-# irb(main):005> end
-# => ["README.md", "Rakefile"]
+# $ irb
+# irb(main):001> File.basename(Dir.pwd)
+# => "irb"
+# irb(main):002> Dir.entries('.').size
+# => 25
+# irb(main):003* Dir.entries('.').select do |entry|
+# irb(main):004* entry.start_with?('R')
+# irb(main):005> end
+# => ["README.md", "Rakefile"]
+#
+# The typed input may also include [\IRB-specific
+# commands](rdoc-ref:IRB@IRB-Specific+Commands).
#
-# The typed input may also include
-# {\IRB-specific commands}[rdoc-ref:IRB@IRB-Specific+Commands].
+# As seen above, you can start IRB by using the shell command `irb`.
#
-# As seen above, you can start \IRB by using the shell command +irb+.
+# You can stop an IRB session by typing command `exit`:
#
-# You can stop an \IRB session by typing command +exit+:
+# irb(main):006> exit
+# $
#
-# irb(main):006> exit
-# $
+# At that point, IRB calls any hooks found in array `IRB.conf[:AT_EXIT]`, then
+# exits.
#
-# At that point, \IRB calls any hooks found in array <tt>IRB.conf[:AT_EXIT]</tt>,
-# then exits.
+# ## Startup
#
-# == Startup
+# At startup, IRB:
#
-# At startup, \IRB:
+# 1. Interprets (as Ruby code) the content of the [configuration
+# file](rdoc-ref:IRB@Configuration+File) (if given).
+# 2. Constructs the initial session context from [hash
+# IRB.conf](rdoc-ref:IRB@Hash+IRB.conf) and from default values; the hash
+# content may have been affected by [command-line
+# options](rdoc-ref:IB@Command-Line+Options), and by direct assignments in
+# the configuration file.
+# 3. Assigns the context to variable `conf`.
+# 4. Assigns command-line arguments to variable `ARGV`.
+# 5. Prints the [prompt](rdoc-ref:IRB@Prompt+and+Return+Formats).
+# 6. Puts the content of the [initialization
+# script](rdoc-ref:IRB@Initialization+Script) onto the IRB shell, just as if
+# it were user-typed commands.
#
-# 1. Interprets (as Ruby code) the content of the
-# {configuration file}[rdoc-ref:IRB@Configuration+File] (if given).
-# 1. Constructs the initial session context
-# from {hash IRB.conf}[rdoc-ref:IRB@Hash+IRB.conf] and from default values;
-# the hash content may have been affected
-# by {command-line options}[rdoc-ref:IB@Command-Line+Options],
-# and by direct assignments in the configuration file.
-# 1. Assigns the context to variable +conf+.
-# 1. Assigns command-line arguments to variable <tt>ARGV</tt>.
-# 1. Prints the {prompt}[rdoc-ref:IRB@Prompt+and+Return+Formats].
-# 1. Puts the content of the
-# {initialization script}[rdoc-ref:IRB@Initialization+Script]
-# onto the \IRB shell, just as if it were user-typed commands.
#
-# === The Command Line
+# ### The Command Line
#
-# On the command line, all options precede all arguments;
-# the first item that is not recognized as an option is treated as an argument,
-# as are all items that follow.
+# On the command line, all options precede all arguments; the first item that is
+# not recognized as an option is treated as an argument, as are all items that
+# follow.
#
-# ==== Command-Line Options
+# #### Command-Line Options
#
-# Many command-line options affect entries in hash <tt>IRB.conf</tt>,
-# which in turn affect the initial configuration of the \IRB session.
+# Many command-line options affect entries in hash `IRB.conf`, which in turn
+# affect the initial configuration of the IRB session.
#
# Details of the options are described in the relevant subsections below.
#
-# A cursory list of the \IRB command-line options
-# may be seen in the {help message}[https://raw.githubusercontent.com/ruby/irb/master/lib/irb/lc/help-message],
-# which is also displayed if you use command-line option <tt>--help</tt>.
+# A cursory list of the IRB command-line options may be seen in the [help
+# message](https://raw.githubusercontent.com/ruby/irb/master/lib/irb/lc/help-message),
+# which is also displayed if you use command-line option `--help`.
#
# If you are interested in a specific option, consult the
-# {index}[rdoc-ref:doc/irb/indexes.md@Index+of+Command-Line+Options].
+# [index](rdoc-ref:doc/irb/indexes.md@Index+of+Command-Line+Options).
#
-# ==== Command-Line Arguments
+# #### Command-Line Arguments
#
-# Command-line arguments are passed to \IRB in array +ARGV+:
+# Command-line arguments are passed to IRB in array `ARGV`:
#
-# $ irb --noscript Foo Bar Baz
-# irb(main):001> ARGV
-# => ["Foo", "Bar", "Baz"]
-# irb(main):002> exit
-# $
+# $ irb --noscript Foo Bar Baz
+# irb(main):001> ARGV
+# => ["Foo", "Bar", "Baz"]
+# irb(main):002> exit
+# $
#
-# Command-line option <tt>--</tt> causes everything that follows
-# to be treated as arguments, even those that look like options:
+# Command-line option `--` causes everything that follows to be treated as
+# arguments, even those that look like options:
#
-# $ irb --noscript -- --noscript -- Foo Bar Baz
-# irb(main):001> ARGV
-# => ["--noscript", "--", "Foo", "Bar", "Baz"]
-# irb(main):002> exit
-# $
+# $ irb --noscript -- --noscript -- Foo Bar Baz
+# irb(main):001> ARGV
+# => ["--noscript", "--", "Foo", "Bar", "Baz"]
+# irb(main):002> exit
+# $
#
-# === Configuration File
+# ### Configuration File
#
-# You can initialize \IRB via a <i>configuration file</i>.
+# You can initialize IRB via a *configuration file*.
#
-# If command-line option <tt>-f</tt> is given,
-# no configuration file is looked for.
+# If command-line option `-f` is given, no configuration file is looked for.
#
-# Otherwise, \IRB reads and interprets a configuration file
-# if one is available.
+# Otherwise, IRB reads and interprets a configuration file if one is available.
#
# The configuration file can contain any Ruby code, and can usefully include
# user code that:
#
-# - Can then be debugged in \IRB.
-# - Configures \IRB itself.
-# - Requires or loads files.
+# * Can then be debugged in IRB.
+# * Configures IRB itself.
+# * Requires or loads files.
+#
#
# The path to the configuration file is the first found among:
#
-# - The value of variable <tt>$IRBRC</tt>, if defined.
-# - The value of variable <tt>$XDG_CONFIG_HOME/irb/irbrc</tt>, if defined.
-# - File <tt>$HOME/.irbrc</tt>, if it exists.
-# - File <tt>$HOME/.config/irb/irbrc</tt>, if it exists.
-# - File +.config/irb/irbrc+ in the current directory, if it exists.
-# - File +.irbrc+ in the current directory, if it exists.
-# - File +irb.rc+ in the current directory, if it exists.
-# - File +_irbrc+ in the current directory, if it exists.
-# - File <tt>$irbrc</tt> in the current directory, if it exists.
+# * The value of variable `$IRBRC`, if defined.
+# * The value of variable `$XDG_CONFIG_HOME/irb/irbrc`, if defined.
+# * File `$HOME/.irbrc`, if it exists.
+# * File `$HOME/.config/irb/irbrc`, if it exists.
+# * File `.irbrc` in the current directory, if it exists.
+# * File `irb.rc` in the current directory, if it exists.
+# * File `_irbrc` in the current directory, if it exists.
+# * File `$irbrc` in the current directory, if it exists.
+#
#
# If the search fails, there is no configuration file.
#
-# If the search succeeds, the configuration file is read as Ruby code,
-# and so can contain any Ruby programming you like.
+# If the search succeeds, the configuration file is read as Ruby code, and so
+# can contain any Ruby programming you like.
+#
+# Method `conf.rc?` returns `true` if a configuration file was read, `false`
+# otherwise. Hash entry `IRB.conf[:RC]` also contains that value.
#
-# \Method <tt>conf.rc?</tt> returns +true+ if a configuration file was read,
-# +false+ otherwise.
-# \Hash entry <tt>IRB.conf[:RC]</tt> also contains that value.
+# ### Hash `IRB.conf`
#
-# === \Hash <tt>IRB.conf</tt>
+# The initial entries in hash `IRB.conf` are determined by:
#
-# The initial entries in hash <tt>IRB.conf</tt> are determined by:
+# * Default values.
+# * Command-line options, which may override defaults.
+# * Direct assignments in the configuration file.
#
-# - Default values.
-# - Command-line options, which may override defaults.
-# - Direct assignments in the configuration file.
#
-# You can see the hash by typing <tt>IRB.conf</tt>.
+# You can see the hash by typing `IRB.conf`.
#
-# Details of the entries' meanings are described in the relevant subsections below.
+# Details of the entries' meanings are described in the relevant subsections
+# below.
#
# If you are interested in a specific entry, consult the
-# {index}[rdoc-ref:doc/irb/indexes.md@Index+of+IRB.conf+Entries].
+# [index](rdoc-ref:doc/irb/indexes.md@Index+of+IRB.conf+Entries).
+#
+# ### Notes on Initialization Precedence
#
-# === Notes on Initialization Precedence
+# * Any conflict between an entry in hash `IRB.conf` and a command-line option
+# is resolved in favor of the hash entry.
+# * Hash `IRB.conf` affects the context only once, when the configuration file
+# is interpreted; any subsequent changes to it do not affect the context and
+# are therefore essentially meaningless.
#
-# - Any conflict between an entry in hash <tt>IRB.conf</tt> and a command-line option
-# is resolved in favor of the hash entry.
-# - \Hash <tt>IRB.conf</tt> affects the context only once,
-# when the configuration file is interpreted;
-# any subsequent changes to it do not affect the context
-# and are therefore essentially meaningless.
#
-# === Initialization Script
+# ### Initialization Script
#
-# By default, the first command-line argument (after any options)
-# is the path to a Ruby initialization script.
+# By default, the first command-line argument (after any options) is the path to
+# a Ruby initialization script.
#
-# \IRB reads the initialization script and puts its content onto the \IRB shell,
+# IRB reads the initialization script and puts its content onto the IRB shell,
# just as if it were user-typed commands.
#
-# Command-line option <tt>--noscript</tt> causes the first command-line argument
-# to be treated as an ordinary argument (instead of an initialization script);
-# <tt>--script</tt> is the default.
+# Command-line option `--noscript` causes the first command-line argument to be
+# treated as an ordinary argument (instead of an initialization script);
+# `--script` is the default.
#
-# == Input
+# ## Input
#
-# This section describes the features that allow you to change
-# the way \IRB input works;
-# see also {Input and Output}[rdoc-ref:IRB@Input+and+Output].
+# This section describes the features that allow you to change the way IRB input
+# works; see also [Input and Output](rdoc-ref:IRB@Input+and+Output).
#
-# === Input Command History
+# ### Input Command History
#
-# By default, \IRB stores a history of up to 1000 input commands
-# in file <tt>~/.irb_history</tt>
-# (or, if a {configuration file}[rdoc-ref:IRB@Configuration+File]
-# is found, in file +.irb_history+
-# inin the same directory as that file).
+# By default, IRB stores a history of up to 1000 input commands in a file named
+# `.irb_history`. The history file will be in the same directory as the
+# [configuration file](rdoc-ref:IRB@Configuration+File) if one is found, or in
+# `~/` otherwise.
#
-# A new \IRB session creates the history file if it does not exist,
-# and appends to the file if it does exist.
+# A new IRB session creates the history file if it does not exist, and appends
+# to the file if it does exist.
#
# You can change the filepath by adding to your configuration file:
-# <tt>IRB.conf[:HISTORY_FILE] = _filepath_</tt>,
-# where _filepath_ is a string filepath.
+# `IRB.conf[:HISTORY_FILE] = *filepath*`, where *filepath* is a string filepath.
#
-# During the session, method <tt>conf.history_file</tt> returns the filepath,
-# and method <tt>conf.history_file = <i>new_filepath</i></tt>
-# copies the history to the file at <i>new_filepath</i>,
-# which becomes the history file for the session.
+# During the session, method `conf.history_file` returns the filepath, and
+# method `conf.history_file = *new_filepath*` copies the history to the file at
+# *new_filepath*, which becomes the history file for the session.
#
-# You can change the number of commands saved by adding to your configuration file:
-# <tt>IRB.conf[:SAVE_HISTORY] = _n_</tt>,
-# where _n_ is one of:
+# You can change the number of commands saved by adding to your configuration
+# file: `IRB.conf[:SAVE_HISTORY] = *n*`, wheHISTORY_FILEre *n* is one of:
#
-# - Positive integer: the number of commands to be saved,
-# - Zero: all commands are to be saved.
-# - +nil+: no commands are to be saved,.
+# * Positive integer: the number of commands to be saved,
+# * Zero: all commands are to be saved.
+# * `nil`: no commands are to be saved,.
#
-# During the session, you can use
-# methods <tt>conf.save_history</tt> or <tt>conf.save_history=</tt>
-# to retrieve or change the count.
#
-# === Command Aliases
+# During the session, you can use methods `conf.save_history` or
+# `conf.save_history=` to retrieve or change the count.
#
-# By default, \IRB defines several command aliases:
+# ### Command Aliases
#
-# irb(main):001> conf.command_aliases
-# => {:"$"=>:show_source, :"@"=>:whereami}
+# By default, IRB defines several command aliases:
+#
+# irb(main):001> conf.command_aliases
+# => {:"$"=>:show_source, :"@"=>:whereami}
#
# You can change the initial aliases in the configuration file with:
#
-# IRB.conf[:COMMAND_ALIASES] = {foo: :show_source, bar: :whereami}
+# IRB.conf[:COMMAND_ALIASES] = {foo: :show_source, bar: :whereami}
#
-# You can replace the current aliases at any time
-# with configuration method <tt>conf.command_aliases=</tt>;
-# Because <tt>conf.command_aliases</tt> is a hash,
-# you can modify it.
+# You can replace the current aliases at any time with configuration method
+# `conf.command_aliases=`; Because `conf.command_aliases` is a hash, you can
+# modify it.
#
-# === End-of-File
+# ### End-of-File
#
-# By default, <tt>IRB.conf[:IGNORE_EOF]</tt> is +false+,
-# which means that typing the end-of-file character <tt>Ctrl-D</tt>
-# causes the session to exit.
+# By default, `IRB.conf[:IGNORE_EOF]` is `false`, which means that typing the
+# end-of-file character `Ctrl-D` causes the session to exit.
#
-# You can reverse that behavior by adding <tt>IRB.conf[:IGNORE_EOF] = true</tt>
-# to the configuration file.
+# You can reverse that behavior by adding `IRB.conf[:IGNORE_EOF] = true` to the
+# configuration file.
#
-# During the session, method <tt>conf.ignore_eof?</tt> returns the setting,
-# and method <tt>conf.ignore_eof = _boolean_</tt> sets it.
+# During the session, method `conf.ignore_eof?` returns the setting, and method
+# `conf.ignore_eof = *boolean*` sets it.
#
-# === SIGINT
+# ### SIGINT
#
-# By default, <tt>IRB.conf[:IGNORE_SIGINT]</tt> is +true+,
-# which means that typing the interrupt character <tt>Ctrl-C</tt>
-# causes the session to exit.
+# By default, `IRB.conf[:IGNORE_SIGINT]` is `true`, which means that typing the
+# interrupt character `Ctrl-C` causes the session to exit.
#
-# You can reverse that behavior by adding <tt>IRB.conf[:IGNORE_SIGING] = false</tt>
-# to the configuration file.
+# You can reverse that behavior by adding `IRB.conf[:IGNORE_SIGING] = false` to
+# the configuration file.
#
-# During the session, method <tt>conf.ignore_siging?</tt> returns the setting,
-# and method <tt>conf.ignore_sigint = _boolean_</tt> sets it.
+# During the session, method `conf.ignore_siging?` returns the setting, and
+# method `conf.ignore_sigint = *boolean*` sets it.
#
-# === Automatic Completion
+# ### Automatic Completion
#
-# By default, \IRB enables
-# {automatic completion}[https://en.wikipedia.org/wiki/Autocomplete#In_command-line_interpreters]:
+# By default, IRB enables [automatic
+# completion](https://en.wikipedia.org/wiki/Autocomplete#In_command-line_interpr
+# eters):
#
# You can disable it by either of these:
#
-# - Adding <tt>IRB.conf[:USE_AUTOCOMPLETE] = false</tt> to the configuration file.
-# - Giving command-line option <tt>--noautocomplete</tt>
-# (<tt>--autocomplete</tt> is the default).
+# * Adding `IRB.conf[:USE_AUTOCOMPLETE] = false` to the configuration file.
+# * Giving command-line option `--noautocomplete` (`--autocomplete` is the
+# default).
+#
#
-# \Method <tt>conf.use_autocomplete?</tt> returns +true+
-# if automatic completion is enabled, +false+ otherwise.
+# Method `conf.use_autocomplete?` returns `true` if automatic completion is
+# enabled, `false` otherwise.
#
# The setting may not be changed during the session.
#
-# === Automatic Indentation
+# ### Automatic Indentation
#
-# By default, \IRB automatically indents lines of code to show structure
-# (e.g., it indent the contents of a block).
+# By default, IRB automatically indents lines of code to show structure (e.g.,
+# it indent the contents of a block).
#
-# The current setting is returned
-# by the configuration method <tt>conf.auto_indent_mode</tt>.
+# The current setting is returned by the configuration method
+# `conf.auto_indent_mode`.
#
-# The default initial setting is +true+:
+# The default initial setting is `true`:
#
-# irb(main):001> conf.auto_indent_mode
-# => true
-# irb(main):002* Dir.entries('.').select do |entry|
-# irb(main):003* entry.start_with?('R')
-# irb(main):004> end
-# => ["README.md", "Rakefile"]
+# irb(main):001> conf.auto_indent_mode
+# => true
+# irb(main):002* Dir.entries('.').select do |entry|
+# irb(main):003* entry.start_with?('R')
+# irb(main):004> end
+# => ["README.md", "Rakefile"]
#
-# You can change the initial setting in the
-# configuration file with:
+# You can change the initial setting in the configuration file with:
#
-# IRB.conf[:AUTO_INDENT] = false
+# IRB.conf[:AUTO_INDENT] = false
#
-# Note that the _current_ setting <i>may not</i> be changed in the \IRB session.
+# Note that the *current* setting *may not* be changed in the IRB session.
#
-# === Input \Method
+# ### Input Method
#
-# The \IRB input method determines how command input is to be read;
-# by default, the input method for a session is IRB::RelineInputMethod.
+# The IRB input method determines how command input is to be read; by default,
+# the input method for a session is IRB::RelineInputMethod. Unless the
+# value of the TERM environment variable is 'dumb', in which case the
+# most simplistic input method is used.
#
# You can set the input method by:
#
-# - Adding to the configuration file:
+# * Adding to the configuration file:
#
-# - <tt>IRB.conf[:USE_SINGLELINE] = true</tt>
-# or <tt>IRB.conf[:USE_MULTILINE]= false</tt>
-# sets the input method to IRB::ReadlineInputMethod.
-# - <tt>IRB.conf[:USE_SINGLELINE] = false</tt>
-# or <tt>IRB.conf[:USE_MULTILINE] = true</tt>
-# sets the input method to IRB::RelineInputMethod.
+# * `IRB.conf[:USE_SINGLELINE] = true` or `IRB.conf[:USE_MULTILINE]=
+# false` sets the input method to IRB::ReadlineInputMethod.
+# * `IRB.conf[:USE_SINGLELINE] = false` or `IRB.conf[:USE_MULTILINE] =
+# true` sets the input method to IRB::RelineInputMethod.
#
-# - Giving command-line options:
#
-# - <tt>--singleline</tt>
-# or <tt>--nomultiline</tt>
-# sets the input method to IRB::ReadlineInputMethod.
-# - <tt>--nosingleline</tt>
-# or <tt>--multiline/tt>
-# sets the input method to IRB::RelineInputMethod.
+# * Giving command-line options:
#
-# \Method <tt>conf.use_multiline?</tt>
-# and its synonym <tt>conf.use_reline</tt> return:
+# * `--singleline` or `--nomultiline` sets the input method to
+# IRB::ReadlineInputMethod.
+# * `--nosingleline` or `--multiline` sets the input method to
+# IRB::RelineInputMethod.
+# * `--nosingleline` together with `--nomultiline` sets the
+# input to IRB::StdioInputMethod.
#
-# - +true+ if option <tt>--multiline</tt> was given.
-# - +false+ if option <tt>--nomultiline</tt> was given.
-# - +nil+ if neither was given.
#
-# \Method <tt>conf.use_singleline?</tt>
-# and its synonym <tt>conf.use_readline</tt> return:
+# Method `conf.use_multiline?` and its synonym `conf.use_reline` return:
#
-# - +true+ if option <tt>--singleline</tt> was given.
-# - +false+ if option <tt>--nosingleline</tt> was given.
-# - +nil+ if neither was given.
+# * `true` if option `--multiline` was given.
+# * `false` if option `--nomultiline` was given.
+# * `nil` if neither was given.
#
-# == Output
#
-# This section describes the features that allow you to change
-# the way \IRB output works;
-# see also {Input and Output}[rdoc-ref:IRB@Input+and+Output].
+# Method `conf.use_singleline?` and its synonym `conf.use_readline` return:
#
-# === Return-Value Printing (Echoing)
+# * `true` if option `--singleline` was given.
+# * `false` if option `--nosingleline` was given.
+# * `nil` if neither was given.
#
-# By default, \IRB prints (echoes) the values returned by all input commands.
+#
+# ## Output
+#
+# This section describes the features that allow you to change the way IRB
+# output works; see also [Input and Output](rdoc-ref:IRB@Input+and+Output).
+#
+# ### Return-Value Printing (Echoing)
+#
+# By default, IRB prints (echoes) the values returned by all input commands.
#
# You can change the initial behavior and suppress all echoing by:
#
-# - Adding to the configuration file: <tt>IRB.conf[:ECHO] = false</tt>.
-# (The default value for this entry is +niL+, which means the same as +true+.)
-# - Giving command-line option <tt>--noecho</tt>.
-# (The default is <tt>--echo</tt>.)
+# * Adding to the configuration file: `IRB.conf[:ECHO] = false`. (The default
+# value for this entry is `nil`, which means the same as `true`.)
+# * Giving command-line option `--noecho`. (The default is `--echo`.)
+#
+#
+# During the session, you can change the current setting with configuration
+# method `conf.echo=` (set to `true` or `false`).
#
-# During the session, you can change the current setting
-# with configuration method <tt>conf.echo=</tt> (set to +true+ or +false+).
+# As stated above, by default IRB prints the values returned by all input
+# commands; but IRB offers special treatment for values returned by assignment
+# statements, which may be:
#
-# As stated above, by default \IRB prints the values returned by all input commands;
-# but \IRB offers special treatment for values returned by assignment statements,
-# which may be:
+# * Printed with truncation (to fit on a single line of output), which is the
+# default; an ellipsis (`...` is suffixed, to indicate the truncation):
#
-# - Printed with truncation (to fit on a single line of output),
-# which is the default;
-# an ellipsis (<tt>...</tt> is suffixed, to indicate the truncation):
+# irb(main):001> x = 'abc' * 100
#
-# irb(main):001> x = 'abc' * 100
-# => "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc...
#
-# - Printed in full (regardless of the length).
-# - Suppressed (not printed at all)
+# > "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc...
+#
+# * Printed in full (regardless of the length).
+# * Suppressed (not printed at all)
+#
#
# You can change the initial behavior by:
#
-# - Adding to the configuration file: <tt>IRB.conf[:ECHO_ON_ASSIGNMENT] = false</tt>.
-# (The default value for this entry is +niL+, which means the same as +:truncate+.)
-# - Giving command-line option <tt>--noecho-on-assignment</tt>
-# or <tt>--echo-on-assignment</tt>.
-# (The default is <tt>--truncate-echo-on-assigment</tt>.)
+# * Adding to the configuration file: `IRB.conf[:ECHO_ON_ASSIGNMENT] = false`.
+# (The default value for this entry is `niL`, which means the same as
+# `:truncate`.)
+# * Giving command-line option `--noecho-on-assignment` or
+# `--echo-on-assignment`. (The default is `--truncate-echo-on-assignment`.)
+#
#
-# During the session, you can change the current setting
-# with configuration method <tt>conf.echo_on_assignment=</tt>
-# (set to +true+, +false+, or +:truncate+).
+# During the session, you can change the current setting with configuration
+# method `conf.echo_on_assignment=` (set to `true`, `false`, or `:truncate`).
#
-# By default, \IRB formats returned values by calling method +inspect+.
+# By default, IRB formats returned values by calling method `inspect`.
#
# You can change the initial behavior by:
#
-# - Adding to the configuration file: <tt>IRB.conf[:INSPECT_MODE] = false</tt>.
-# (The default value for this entry is +true+.)
-# - Giving command-line option <tt>--noinspect</tt>.
-# (The default is <tt>--inspect</tt>.)
+# * Adding to the configuration file: `IRB.conf[:INSPECT_MODE] = false`. (The
+# default value for this entry is `true`.)
+# * Giving command-line option `--noinspect`. (The default is `--inspect`.)
#
-# During the session, you can change the setting using method <tt>conf.inspect_mode=</tt>.
#
-# === Multiline Output
+# During the session, you can change the setting using method
+# `conf.inspect_mode=`.
#
-# By default, \IRB prefixes a newline to a multiline response.
+# ### Multiline Output
#
-# You can change the initial default value by adding to the configuation file:
+# By default, IRB prefixes a newline to a multiline response.
#
-# IRB.conf[:NEWLINE_BEFORE_MULTILINE_OUTPUT] = false
+# You can change the initial default value by adding to the configuration file:
#
-# During a session, you can retrieve or set the value using
-# methods <tt>conf.newline_before_multiline_output?</tt>
-# and <tt>conf.newline_before_multiline_output=</tt>.
+# IRB.conf[:NEWLINE_BEFORE_MULTILINE_OUTPUT] = false
+#
+# During a session, you can retrieve or set the value using methods
+# `conf.newline_before_multiline_output?` and
+# `conf.newline_before_multiline_output=`.
#
# Examples:
#
-# irb(main):001> conf.inspect_mode = false
-# => false
-# irb(main):002> "foo\nbar"
-# =>
-# foo
-# bar
-# irb(main):003> conf.newline_before_multiline_output = false
-# => false
-# irb(main):004> "foo\nbar"
-# => foo
-# bar
+# irb(main):001> conf.inspect_mode = false
+# => false
+# irb(main):002> "foo\nbar"
+# =>
+# foo
+# bar
+# irb(main):003> conf.newline_before_multiline_output = false
+# => false
+# irb(main):004> "foo\nbar"
+# => foo
+# bar
+#
+# ### Evaluation History
#
-# === Evaluation History
+# By default, IRB saves no history of evaluations (returned values), and the
+# related methods `conf.eval_history`, `_`, and `__` are undefined.
#
-# By default, \IRB saves no history of evaluations (returned values),
-# and the related methods <tt>conf.eval_history</tt>, <tt>_</tt>,
-# and <tt>__</tt> are undefined.
+# You can turn on that history, and set the maximum number of evaluations to be
+# stored:
#
-# You can turn on that history, and set the maximum number of evaluations to be stored:
+# * In the configuration file: add `IRB.conf[:EVAL_HISTORY] = *n*`. (Examples
+# below assume that we've added `IRB.conf[:EVAL_HISTORY] = 5`.)
+# * In the session (at any time): `conf.eval_history = *n*`.
#
-# - In the configuration file: add <tt>IRB.conf[:EVAL_HISTORY] = _n_</tt>.
-# (Examples below assume that we've added <tt>IRB.conf[:EVAL_HISTORY] = 5</tt>.)
-# - In the session (at any time): <tt>conf.eval_history = _n_</tt>.
#
-# If +n+ is zero, all evaluation history is stored.
+# If `n` is zero, all evaluation history is stored.
#
# Doing either of the above:
#
-# - Sets the maximum size of the evaluation history;
-# defines method <tt>conf.eval_history</tt>,
-# which returns the maximum size +n+ of the evaluation history:
-#
-# irb(main):001> conf.eval_history = 5
-# => 5
-# irb(main):002> conf.eval_history
-# => 5
-#
-# - Defines variable <tt>_</tt>, which contains the most recent evaluation,
-# or +nil+ if none; same as method <tt>conf.last_value</tt>:
-#
-# irb(main):003> _
-# => 5
-# irb(main):004> :foo
-# => :foo
-# irb(main):005> :bar
-# => :bar
-# irb(main):006> _
-# => :bar
-# irb(main):007> _
-# => :bar
-#
-# - Defines variable <tt>__</tt>:
-#
-# - <tt>__</tt> unadorned: contains all evaluation history:
-#
-# irb(main):008> :foo
-# => :foo
-# irb(main):009> :bar
-# => :bar
-# irb(main):010> :baz
-# => :baz
-# irb(main):011> :bat
-# => :bat
-# irb(main):012> :bam
-# => :bam
-# irb(main):013> __
-# =>
-# 9 :bar
-# 10 :baz
-# 11 :bat
-# 12 :bam
-# irb(main):014> __
-# =>
-# 10 :baz
-# 11 :bat
-# 12 :bam
-# 13 ...self-history...
-#
-# Note that when the evaluation is multiline, it is displayed differently.
-#
-# - <tt>__[</tt>_m_<tt>]</tt>:
-#
-# - Positive _m_: contains the evaluation for the given line number,
-# or +nil+ if that line number is not in the evaluation history:
-#
-# irb(main):015> __[12]
-# => :bam
-# irb(main):016> __[1]
-# => nil
-#
-# - Negative _m_: contains the +mth+-from-end evaluation,
-# or +nil+ if that evaluation is not in the evaluation history:
-#
-# irb(main):017> __[-3]
-# => :bam
-# irb(main):018> __[-13]
-# => nil
-#
-# - Zero _m_: contains +nil+:
-#
-# irb(main):019> __[0]
-# => nil
-#
-# === Prompt and Return Formats
-#
-# By default, \IRB uses the prompt and return value formats
-# defined in its +:DEFAULT+ prompt mode.
-#
-# ==== The Default Prompt and Return Format
+# * Sets the maximum size of the evaluation history; defines method
+# `conf.eval_history`, which returns the maximum size `n` of the evaluation
+# history:
+#
+# irb(main):001> conf.eval_history = 5
+# => 5
+# irb(main):002> conf.eval_history
+# => 5
+#
+# * Defines variable `_`, which contains the most recent evaluation, or `nil`
+# if none; same as method `conf.last_value`:
+#
+# irb(main):003> _
+# => 5
+# irb(main):004> :foo
+# => :foo
+# irb(main):005> :bar
+# => :bar
+# irb(main):006> _
+# => :bar
+# irb(main):007> _
+# => :bar
+#
+# * Defines variable `__`:
+#
+# * `__` unadorned: contains all evaluation history:
+#
+# irb(main):008> :foo
+# => :foo
+# irb(main):009> :bar
+# => :bar
+# irb(main):010> :baz
+# => :baz
+# irb(main):011> :bat
+# => :bat
+# irb(main):012> :bam
+# => :bam
+# irb(main):013> __
+# =>
+# 9 :bar
+# 10 :baz
+# 11 :bat
+# 12 :bam
+# irb(main):014> __
+# =>
+# 10 :baz
+# 11 :bat
+# 12 :bam
+# 13 ...self-history...
+#
+# Note that when the evaluation is multiline, it is displayed
+# differently.
+#
+# * `__[`*m*`]`:
+#
+# * Positive *m*: contains the evaluation for the given line number,
+# or `nil` if that line number is not in the evaluation history:
+#
+# irb(main):015> __[12]
+# => :bam
+# irb(main):016> __[1]
+# => nil
+#
+# * Negative *m*: contains the `mth`-from-end evaluation, or `nil` if
+# that evaluation is not in the evaluation history:
+#
+# irb(main):017> __[-3]
+# => :bam
+# irb(main):018> __[-13]
+# => nil
+#
+# * Zero *m*: contains `nil`:
+#
+# irb(main):019> __[0]
+# => nil
+#
+#
+#
+#
+# ### Prompt and Return Formats
+#
+# By default, IRB uses the prompt and return value formats defined in its
+# `:DEFAULT` prompt mode.
+#
+# #### The Default Prompt and Return Format
#
# The default prompt and return values look like this:
#
-# irb(main):001> 1 + 1
-# => 2
-# irb(main):002> 2 + 2
-# => 4
+# irb(main):001> 1 + 1
+# => 2
+# irb(main):002> 2 + 2
+# => 4
#
# The prompt includes:
#
-# - The name of the running program (<tt>irb</tt>);
-# see {IRB Name}[rdoc-ref:IRB@IRB+Name].
-# - The name of the current session (<tt>main</tt>);
-# See {IRB Sessions}[rdoc-ref:IRB@IRB+Sessions].
-# - A 3-digit line number (1-based).
+# * The name of the running program (`irb`); see [IRB
+# Name](rdoc-ref:IRB@IRB+Name).
+# * The name of the current session (`main`); See [IRB
+# Sessions](rdoc-ref:IRB@IRB+Sessions).
+# * A 3-digit line number (1-based).
+#
#
# The default prompt actually defines three formats:
#
-# - One for most situations (as above):
+# * One for most situations (as above):
+#
+# irb(main):003> Dir
+# => Dir
#
-# irb(main):003> Dir
-# => Dir
+# * One for when the typed command is a statement continuation (adds trailing
+# asterisk):
#
-# - One for when the typed command is a statement continuation (adds trailing asterisk):
+# irb(main):004* Dir.
#
-# irb(main):004* Dir.
+# * One for when the typed command is a string continuation (adds trailing
+# single-quote):
#
-# - One for when the typed command is a string continuation (adds trailing single-quote):
+# irb(main):005' Dir.entries('.
#
-# irb(main):005' Dir.entries('.
#
# You can see the prompt change as you type the characters in the following:
#
@@ -570,276 +578,268 @@ require_relative "irb/pager"
# irb(main):003> end
# => ["README.md", "Rakefile"]
#
-# ==== Pre-Defined Prompts
+# #### Pre-Defined Prompts
#
-# \IRB has several pre-defined prompts, stored in hash <tt>IRB.conf[:PROMPT]</tt>:
+# IRB has several pre-defined prompts, stored in hash `IRB.conf[:PROMPT]`:
#
-# irb(main):001> IRB.conf[:PROMPT].keys
-# => [:NULL, :DEFAULT, :CLASSIC, :SIMPLE, :INF_RUBY, :XMP]
+# irb(main):001> IRB.conf[:PROMPT].keys
+# => [:NULL, :DEFAULT, :CLASSIC, :SIMPLE, :INF_RUBY, :XMP]
#
-# To see the full data for these, type <tt>IRB.conf[:PROMPT]</tt>.
+# To see the full data for these, type `IRB.conf[:PROMPT]`.
#
-# Most of these prompt definitions include specifiers that represent
-# values like the \IRB name, session name, and line number;
-# see {Prompt Specifiers}[rdoc-ref:IRB@Prompt+Specifiers].
+# Most of these prompt definitions include specifiers that represent values like
+# the IRB name, session name, and line number; see [Prompt
+# Specifiers](rdoc-ref:IRB@Prompt+Specifiers).
#
# You can change the initial prompt and return format by:
#
-# - Adding to the configuration file: <tt>IRB.conf[:PROMPT] = _mode_</tt>
-# where _mode_ is the symbol name of a prompt mode.
-# - Giving a command-line option:
+# * Adding to the configuration file: `IRB.conf[:PROMPT] = *mode*` where
+# *mode* is the symbol name of a prompt mode.
+# * Giving a command-line option:
+#
+# * `--prompt *mode*`: sets the prompt mode to *mode*. where *mode* is the
+# symbol name of a prompt mode.
+# * `--simple-prompt` or `--sample-book-mode`: sets the prompt mode to
+# `:SIMPLE`.
+# * `--inf-ruby-mode`: sets the prompt mode to `:INF_RUBY` and suppresses
+# both `--multiline` and `--singleline`.
+# * `--noprompt`: suppresses prompting; does not affect echoing.
+#
#
-# - <tt>--prompt _mode_</tt>: sets the prompt mode to _mode_.
-# where _mode_ is the symbol name of a prompt mode.
-# - <tt>--simple-prompt</tt> or <tt>--sample-book-mode</tt>:
-# sets the prompt mode to +:SIMPLE+.
-# - <tt>--inf-ruby-mode</tt>: sets the prompt mode to +:INF_RUBY+
-# and suppresses both <tt>--multiline</tt> and <tt>--singleline</tt>.
-# - <tt>--noprompt</tt>: suppresses prompting; does not affect echoing.
#
# You can retrieve or set the current prompt mode with methods
#
-# <tt>conf.prompt_mode</tt> and <tt>conf.prompt_mode=</tt>.
+# `conf.prompt_mode` and `conf.prompt_mode=`.
#
# If you're interested in prompts and return formats other than the defaults,
# you might experiment by trying some of the others.
#
-# ==== Custom Prompts
+# #### Custom Prompts
+#
+# You can also define custom prompts and return formats, which may be done
+# either in an IRB session or in the configuration file.
#
-# You can also define custom prompts and return formats,
-# which may be done either in an \IRB session or in the configuration file.
+# A prompt in IRB actually defines three prompts, as seen above. For simple
+# custom data, we'll make all three the same:
#
-# A prompt in \IRB actually defines three prompts, as seen above.
-# For simple custom data, we'll make all three the same:
+# irb(main):001* IRB.conf[:PROMPT][:MY_PROMPT] = {
+# irb(main):002* PROMPT_I: ': ',
+# irb(main):003* PROMPT_C: ': ',
+# irb(main):004* PROMPT_S: ': ',
+# irb(main):005* RETURN: '=> '
+# irb(main):006> }
+# => {:PROMPT_I=>": ", :PROMPT_C=>": ", :PROMPT_S=>": ", :RETURN=>"=> "}
#
-# irb(main):001* IRB.conf[:PROMPT][:MY_PROMPT] = {
-# irb(main):002* PROMPT_I: ': ',
-# irb(main):003* PROMPT_C: ': ',
-# irb(main):004* PROMPT_S: ': ',
-# irb(main):005* RETURN: '=> '
-# irb(main):006> }
-# => {:PROMPT_I=>": ", :PROMPT_C=>": ", :PROMPT_S=>": ", :RETURN=>"=> "}
+# If you define the custom prompt in the configuration file, you can also make
+# it the current prompt by adding:
#
-# If you define the custom prompt in the configuration file,
-# you can also make it the current prompt by adding:
+# IRB.conf[:PROMPT_MODE] = :MY_PROMPT
#
-# IRB.conf[:PROMPT_MODE] = :MY_PROMPT
+# Regardless of where it's defined, you can make it the current prompt in a
+# session:
#
-# Regardless of where it's defined, you can make it the current prompt in a session:
+# conf.prompt_mode = :MY_PROMPT
#
-# conf.prompt_mode = :MY_PROMPT
+# You can view or modify the current prompt data with various configuration
+# methods:
#
-# You can view or modify the current prompt data with various configuration methods:
+# * `conf.prompt_mode`, `conf.prompt_mode=`.
+# * `conf.prompt_c`, `conf.c=`.
+# * `conf.prompt_i`, `conf.i=`.
+# * `conf.prompt_s`, `conf.s=`.
+# * `conf.return_format`, `return_format=`.
#
-# - <tt>conf.prompt_mode</tt>, <tt>conf.prompt_mode=</tt>.
-# - <tt>conf.prompt_c</tt>, <tt>conf.c=</tt>.
-# - <tt>conf.prompt_i</tt>, <tt>conf.i=</tt>.
-# - <tt>conf.prompt_s</tt>, <tt>conf.s=</tt>.
-# - <tt>conf.return_format</tt>, <tt>return_format=</tt>.
#
-# ==== Prompt Specifiers
+# #### Prompt Specifiers
#
-# A prompt's definition can include specifiers for which certain values are substituted:
+# A prompt's definition can include specifiers for which certain values are
+# substituted:
#
-# - <tt>%N</tt>: the name of the running program.
-# - <tt>%m</tt>: the value of <tt>self.to_s</tt>.
-# - <tt>%M</tt>: the value of <tt>self.inspect</tt>.
-# - <tt>%l</tt>: an indication of the type of string;
-# one of <tt>"</tt>, <tt>'</tt>, <tt>/</tt>, <tt>]</tt>.
-# - <tt><i>NN</i>i</tt>: Indentation level.
-# - <tt><i>NN</i>n</tt>: Line number.
-# - <tt>%%</tt>: Literal <tt>%</tt>.
+# * `%N`: the name of the running program.
+# * `%m`: the value of `self.to_s`.
+# * `%M`: the value of `self.inspect`.
+# * `%l`: an indication of the type of string; one of `"`, `'`, `/`, `]`.
+# * `%NNi`: Indentation level. NN is a 2-digit number that specifies the number
+# of digits of the indentation level (03 will result in 001).
+# * `%NNn`: Line number. NN is a 2-digit number that specifies the number
+# of digits of the line number (03 will result in 001).
+# * `%%`: Literal `%`.
#
-# === Verbosity
#
-# By default, \IRB verbosity is disabled, which means that output is smaller
+# ### Verbosity
+#
+# By default, IRB verbosity is disabled, which means that output is smaller
# rather than larger.
#
# You can enable verbosity by:
#
-# - Adding to the configuration file: <tt>IRB.conf[:VERBOSE] = true</tt>
-# (the default is +nil+).
-# - Giving command-line options <tt>--verbose</tt>
-# (the default is <tt>--noverbose</tt>).
+# * Adding to the configuration file: `IRB.conf[:VERBOSE] = true` (the default
+# is `nil`).
+# * Giving command-line options `--verbose` (the default is `--noverbose`).
+#
#
# During a session, you can retrieve or set verbosity with methods
-# <tt>conf.verbose</tt> and <tt>conf.verbose=</tt>.
+# `conf.verbose` and `conf.verbose=`.
+#
+# ### Help
#
-# === Help
+# Command-line option `--version` causes IRB to print its help text and exit.
#
-# Command-line option <tt>--version</tt> causes \IRB to print its help text
-# and exit.
+# ### Version
#
-# === Version
+# Command-line option `--version` causes IRB to print its version text and exit.
#
-# Command-line option <tt>--version</tt> causes \IRB to print its version text
-# and exit.
+# ## Input and Output
#
-# == Input and Output
+# ### Color Highlighting
#
-# === \Color Highlighting
+# By default, IRB color highlighting is enabled, and is used for both:
#
-# By default, \IRB color highlighting is enabled, and is used for both:
+# * Input: As you type, IRB reads the typed characters and highlights elements
+# that it recognizes; it also highlights errors such as mismatched
+# parentheses.
+# * Output: IRB highlights syntactical elements.
#
-# - Input: As you type, \IRB reads the typed characters and highlights
-# elements that it recognizes;
-# it also highlights errors such as mismatched parentheses.
-# - Output: \IRB highlights syntactical elements.
#
# You can disable color highlighting by:
#
-# - Adding to the configuration file: <tt>IRB.conf[:USE_COLORIZE] = false</tt>
-# (the default value is +true+).
-# - Giving command-line option <tt>--nocolorize</tt>
+# * Adding to the configuration file: `IRB.conf[:USE_COLORIZE] = false` (the
+# default value is `true`).
+# * Giving command-line option `--nocolorize`
#
-# == Debugging
#
-# Command-line option <tt>-d</tt> sets variables <tt>$VERBOSE</tt>
-# and <tt>$DEBUG</tt> to +true+;
-# these have no effect on \IRB output.
+# ## Debugging
#
-# === Warnings
+# Command-line option `-d` sets variables `$VERBOSE` and `$DEBUG` to `true`;
+# these have no effect on IRB output.
#
-# Command-line option <tt>-w</tt> suppresses warnings.
+# ### Warnings
#
-# Command-line option <tt>-W[_level_]<tt>
-# sets warning level; 0=silence, 1=medium, 2=verbose.
+# Command-line option `-w` suppresses warnings.
#
-# :stopdoc:
-# === Performance Measurement
+# Command-line option `-W[*level*]` sets warning level;
#
-# IRB.conf[:MEASURE] IRB.conf[:MEASURE_CALLBACKS] IRB.conf[:MEASURE_PROC]
-# :startdoc:
+# * 0=silence
+# * 1=medium
+# * 2=verbose
#
-# == Other Features
+# ## Other Features
#
-# === Load Modules
+# ### Load Modules
#
# You can specify the names of modules that are to be required at startup.
#
-# \Array <tt>conf.load_modules</tt> determines the modules (if any)
-# that are to be required during session startup.
-# The array is used only during session startup,
-# so the initial value is the only one that counts.
+# Array `conf.load_modules` determines the modules (if any) that are to be
+# required during session startup. The array is used only during session
+# startup, so the initial value is the only one that counts.
#
-# The default initial value is <tt>[]</tt> (load no modules):
+# The default initial value is `[]` (load no modules):
#
-# irb(main):001> conf.load_modules
-# => []
+# irb(main):001> conf.load_modules
+# => []
#
# You can set the default initial value via:
#
-# - Command-line option <tt>-r</tt>
+# * Command-line option `-r`
+#
+# $ irb -r csv -r json
+# irb(main):001> conf.load_modules
+# => ["csv", "json"]
#
-# $ irb -r csv -r json
-# irb(main):001> conf.load_modules
-# => ["csv", "json"]
+# * Hash entry `IRB.conf[:LOAD_MODULES] = *array*`:
#
-# - \Hash entry <tt>IRB.conf[:LOAD_MODULES] = _array_</tt>:
+# IRB.conf[:LOAD_MODULES] = %w[csv, json]
#
-# IRB.conf[:LOAD_MODULES] = %w[csv, json]
#
# Note that the configuration file entry overrides the command-line options.
#
-# === RI Documentation Directories
+# ### RI Documentation Directories
#
-# You can specify the paths to RI documentation directories
-# that are to be loaded (in addition to the default directories) at startup;
-# see details about RI by typing <tt>ri --help</tt>.
+# You can specify the paths to RI documentation directories that are to be
+# loaded (in addition to the default directories) at startup; see details about
+# RI by typing `ri --help`.
#
-# \Array <tt>conf.extra_doc_dirs</tt> determines the directories (if any)
-# that are to be loaded during session startup.
-# The array is used only during session startup,
+# Array `conf.extra_doc_dirs` determines the directories (if any) that are to be
+# loaded during session startup. The array is used only during session startup,
# so the initial value is the only one that counts.
#
-# The default initial value is <tt>[]</tt> (load no extra documentation):
-#
-# irb(main):001> conf.extra_doc_dirs
-# => []
+# The default initial value is `[]` (load no extra documentation):
#
-# You can set the default initial value via:
-#
-# - Command-line option <tt>--extra_doc_dir</tt>
-#
-# $ irb --extra-doc-dir your_doc_dir --extra-doc-dir my_doc_dir
# irb(main):001> conf.extra_doc_dirs
-# => ["your_doc_dir", "my_doc_dir"]
+# => []
#
-# - \Hash entry <tt>IRB.conf[:EXTRA_DOC_DIRS] = _array_</tt>:
+# You can set the default initial value via:
#
-# IRB.conf[:EXTRA_DOC_DIRS] = %w[your_doc_dir my_doc_dir]
+# * Command-line option `--extra_doc_dir`
#
-# Note that the configuration file entry overrides the command-line options.
+# $ irb --extra-doc-dir your_doc_dir --extra-doc-dir my_doc_dir
+# irb(main):001> conf.extra_doc_dirs
+# => ["your_doc_dir", "my_doc_dir"]
#
-# :stopdoc:
-# === \Context Mode
+# * Hash entry `IRB.conf[:EXTRA_DOC_DIRS] = *array*`:
#
-# IRB.conf[:CONTEXT_MODE]
-# :startdoc:
+# IRB.conf[:EXTRA_DOC_DIRS] = %w[your_doc_dir my_doc_dir]
#
-# === \IRB Name
#
-# You can specify a name for \IRB.
+# Note that the configuration file entry overrides the command-line options.
#
-# The default initial value is <tt>'irb'</tt>:
+# ### IRB Name
#
-# irb(main):001> conf.irb_name
-# => "irb"
+# You can specify a name for IRB.
#
-# You can set the default initial value
-# via hash entry <tt>IRB.conf[:IRB_NAME] = _string_</tt>:
+# The default initial value is `'irb'`:
#
-# IRB.conf[:IRB_NAME] = 'foo'
+# irb(main):001> conf.irb_name
+# => "irb"
#
-# === Application Name
+# You can set the default initial value via hash entry `IRB.conf[:IRB_NAME] =
+# *string*`:
#
-# You can specify an application name for the \IRB session.
+# IRB.conf[:IRB_NAME] = 'foo'
#
-# The default initial value is <tt>'irb'</tt>:
+# ### Application Name
#
-# irb(main):001> conf.ap_name
-# => "irb"
+# You can specify an application name for the IRB session.
#
-# You can set the default initial value
-# via hash entry <tt>IRB.conf[:AP_NAME] = _string_</tt>:
+# The default initial value is `'irb'`:
#
-# IRB.conf[:AP_NAME] = 'my_ap_name'
+# irb(main):001> conf.ap_name
+# => "irb"
#
-# === Configuration Monitor
+# You can set the default initial value via hash entry `IRB.conf[:AP_NAME] =
+# *string*`:
#
-# You can monitor changes to the configuration by assigning a proc
-# to <tt>IRB.conf[:IRB_RC]</tt> in the configuration file:
+# IRB.conf[:AP_NAME] = 'my_ap_name'
#
-# IRB.conf[:IRB_RC] = proc {|conf| puts conf.class }
+# ### Configuration Monitor
#
-# Each time the configuration is changed,
-# that proc is called with argument +conf+:
+# You can monitor changes to the configuration by assigning a proc to
+# `IRB.conf[:IRB_RC]` in the configuration file:
#
-# :stopdoc:
-# === \Locale
+# IRB.conf[:IRB_RC] = proc {|conf| puts conf.class }
#
-# IRB.conf[:LC_MESSAGES]
-# :startdoc:
+# Each time the configuration is changed, that proc is called with argument
+# `conf`:
#
-# === Encodings
+# ### Encodings
#
-# Command-line option <tt>-E _ex_[:_in_]</tt>
-# sets initial external (ex) and internal (in) encodings.
+# Command-line option `-E *ex*[:*in*]` sets initial external (ex) and internal
+# (in) encodings.
#
-# Command-line option <tt>-U</tt> sets both to UTF-8.
+# Command-line option `-U` sets both to UTF-8.
#
-# === Commands
+# ### Commands
#
-# Please use the `show_cmds` command to see the list of available commands.
+# Please use the `help` command to see the list of available commands.
#
-# === IRB Sessions
+# ### IRB Sessions
#
# IRB has a special feature, that allows you to manage many sessions at once.
#
# You can create new sessions with Irb.irb, and get a list of current sessions
-# with the +jobs+ command in the prompt.
+# with the `jobs` command in the prompt.
#
-# ==== Configuration
+# #### Configuration
#
# The command line options, or IRB.conf, specify the default behavior of
# Irb.irb.
@@ -847,32 +847,33 @@ require_relative "irb/pager"
# On the other hand, each conf in IRB@Command-Line+Options is used to
# individually configure IRB.irb.
#
-# If a proc is set for <code>IRB.conf[:IRB_RC]</code>, its will be invoked after execution
+# If a proc is set for `IRB.conf[:IRB_RC]`, its will be invoked after execution
# of that proc with the context of the current session as its argument. Each
# session can be configured using this mechanism.
#
-# ==== Session variables
+# #### Session variables
#
# There are a few variables in every Irb session that can come in handy:
#
-# <code>_</code>::
-# The value command executed, as a local variable
-# <code>__</code>::
-# The history of evaluated commands. Available only if
-# <code>IRB.conf[:EVAL_HISTORY]</code> is not +nil+ (which is the default).
-# See also IRB::Context#eval_history= and IRB::History.
-# <code>__[line_no]</code>::
-# Returns the evaluation value at the given line number, +line_no+.
-# If +line_no+ is a negative, the return value +line_no+ many lines before
-# the most recent return value.
+# `_`
+# : The value command executed, as a local variable
+# `__`
+# : The history of evaluated commands. Available only if
+# `IRB.conf[:EVAL_HISTORY]` is not `nil` (which is the default). See also
+# IRB::Context#eval_history= and IRB::History.
+# `__[line_no]`
+# : Returns the evaluation value at the given line number, `line_no`. If
+# `line_no` is a negative, the return value `line_no` many lines before the
+# most recent return value.
#
-# == Restrictions
#
-# Ruby code typed into \IRB behaves the same as Ruby code in a file, except that:
+# ## Restrictions
#
-# - Because \IRB evaluates input immediately after it is syntactically complete,
-# some results may be slightly different.
-# - Forking may not be well behaved.
+# Ruby code typed into IRB behaves the same as Ruby code in a file, except that:
+#
+# * Because IRB evaluates input immediately after it is syntactically
+# complete, some results may be slightly different.
+# * Forking may not be well behaved.
#
module IRB
@@ -881,14 +882,14 @@ module IRB
# The current IRB::Context of the session, see IRB.conf
#
- # irb
- # irb(main):001:0> IRB.CurrentContext.irb_name = "foo"
- # foo(main):002:0> IRB.conf[:MAIN_CONTEXT].irb_name #=> "foo"
- def IRB.CurrentContext
+ # irb
+ # irb(main):001:0> IRB.CurrentContext.irb_name = "foo"
+ # foo(main):002:0> IRB.conf[:MAIN_CONTEXT].irb_name #=> "foo"
+ def IRB.CurrentContext # :nodoc:
IRB.conf[:MAIN_CONTEXT]
end
- # Initializes IRB and creates a new Irb.irb object at the +TOPLEVEL_BINDING+
+ # Initializes IRB and creates a new Irb.irb object at the `TOPLEVEL_BINDING`
def IRB.start(ap_path = nil)
STDOUT.sync = true
$0 = File::basename(ap_path, ".rb") if ap_path
@@ -904,42 +905,46 @@ module IRB
end
# Quits irb
- def IRB.irb_exit(irb, ret)
- throw :IRB_EXIT, ret
+ def IRB.irb_exit(*) # :nodoc:
+ throw :IRB_EXIT, false
end
# Aborts then interrupts irb.
#
- # Will raise an Abort exception, or the given +exception+.
- def IRB.irb_abort(irb, exception = Abort)
+ # Will raise an Abort exception, or the given `exception`.
+ def IRB.irb_abort(irb, exception = Abort) # :nodoc:
irb.context.thread.raise exception, "abort then interrupt!"
end
class Irb
# Note: instance and index assignment expressions could also be written like:
- # "foo.bar=(1)" and "foo.[]=(1, bar)", when expressed that way, the former
- # be parsed as :assign and echo will be suppressed, but the latter is
- # parsed as a :method_add_arg and the output won't be suppressed
+ # "foo.bar=(1)" and "foo.[]=(1, bar)", when expressed that way, the former be
+ # parsed as :assign and echo will be suppressed, but the latter is parsed as a
+ # :method_add_arg and the output won't be suppressed
PROMPT_MAIN_TRUNCATE_LENGTH = 32
- PROMPT_MAIN_TRUNCATE_OMISSION = '...'.freeze
- CONTROL_CHARACTERS_PATTERN = "\x00-\x1F".freeze
+ PROMPT_MAIN_TRUNCATE_OMISSION = '...'
+ CONTROL_CHARACTERS_PATTERN = "\x00-\x1F"
# Returns the current context of this irb session
attr_reader :context
# The lexer used by this irb session
attr_accessor :scanner
+ attr_reader :from_binding
+
# Creates a new irb session
- def initialize(workspace = nil, input_method = nil)
+ def initialize(workspace = nil, input_method = nil, from_binding: false)
+ @from_binding = from_binding
@context = Context.new(self, workspace, input_method)
- @context.workspace.load_commands_to_main
+ @context.workspace.load_helper_methods_to_main
@signal_status = :IN_IRB
@scanner = RubyLex.new
@line_no = 1
end
- # A hook point for `debug` command's breakpoint after :IRB_EXIT as well as its clean-up
+ # A hook point for `debug` command's breakpoint after :IRB_EXIT as well as its
+ # clean-up
def debug_break
# it means the debug integration has been activated
if defined?(DEBUGGER__) && DEBUGGER__.respond_to?(:capture_frames_without_irb)
@@ -952,29 +957,37 @@ module IRB
def debug_readline(binding)
workspace = IRB::WorkSpace.new(binding)
- context.workspace = workspace
- context.workspace.load_commands_to_main
+ context.replace_workspace(workspace)
+ context.workspace.load_helper_methods_to_main
@line_no += 1
# When users run:
- # 1. Debugging commands, like `step 2`
- # 2. Any input that's not irb-command, like `foo = 123`
+ # 1. Debugging commands, like `step 2`
+ # 2. Any input that's not irb-command, like `foo = 123`
+ #
#
- # Irb#eval_input will simply return the input, and we need to pass it to the debugger.
- input = if IRB.conf[:SAVE_HISTORY] && context.io.support_history_saving?
- # Previous IRB session's history has been saved when `Irb#run` is exited
- # We need to make sure the saved history is not saved again by reseting the counter
- context.io.reset_history_counter
+ # Irb#eval_input will simply return the input, and we need to pass it to the
+ # debugger.
+ input = nil
+ forced_exit = catch(:IRB_EXIT) do
+ if IRB.conf[:SAVE_HISTORY] && context.io.support_history_saving?
+ # Previous IRB session's history has been saved when `Irb#run` is exited We need
+ # to make sure the saved history is not saved again by resetting the counter
+ context.io.reset_history_counter
- begin
- eval_input
- ensure
- context.io.save_history
+ begin
+ input = eval_input
+ ensure
+ context.io.save_history
+ end
+ else
+ input = eval_input
end
- else
- eval_input
+ false
end
+ Kernel.exit if forced_exit
+
if input&.include?("\n")
@line_no += input.count("\n") - 1
end
@@ -985,6 +998,7 @@ module IRB
def run(conf = IRB.conf)
in_nested_session = !!conf[:MAIN_CONTEXT]
conf[:IRB_RC].call(context) if conf[:IRB_RC]
+ prev_context = conf[:MAIN_CONTEXT]
conf[:MAIN_CONTEXT] = context
save_history = !in_nested_session && conf[:SAVE_HISTORY] && context.io.support_history_saving?
@@ -998,13 +1012,24 @@ module IRB
end
begin
- catch(:IRB_EXIT) do
+ if defined?(RubyVM.keep_script_lines)
+ keep_script_lines_backup = RubyVM.keep_script_lines
+ RubyVM.keep_script_lines = true
+ end
+
+ forced_exit = catch(:IRB_EXIT) do
eval_input
end
ensure
+ # Do not restore to nil. It will cause IRB crash when used with threads.
+ IRB.conf[:MAIN_CONTEXT] = prev_context if prev_context
+
+ RubyVM.keep_script_lines = keep_script_lines_backup if defined?(RubyVM.keep_script_lines)
trap("SIGINT", prev_trap)
conf[:AT_EXIT].each{|hook| hook.call}
+
context.io.save_history if save_history
+ Kernel.exit if forced_exit
end
end
@@ -1015,12 +1040,13 @@ module IRB
each_top_level_statement do |statement, line_no|
signal_status(:IN_EVAL) do
begin
- # If the integration with debugger is activated, we return certain input if it should be dealt with by debugger
+ # If the integration with debugger is activated, we return certain input if it
+ # should be dealt with by debugger
if @context.with_debugger && statement.should_be_handled_by_debugger?
return statement.code
end
- @context.evaluate(statement.evaluable_code, line_no)
+ @context.evaluate(statement, line_no)
if @context.echo? && !statement.suppresses_echo?
if statement.is_assignment?
@@ -1067,7 +1093,7 @@ module IRB
return read_input(prompt) if @context.io.respond_to?(:check_termination)
# nomultiline
- code = ''
+ code = +''
line_offset = 0
loop do
line = read_input(prompt)
@@ -1076,9 +1102,7 @@ module IRB
end
code << line
-
- # Accept any single-line input for symbol aliases or commands that transform args
- return code if single_line_command?(code)
+ return code if command?(code)
tokens, opens, terminated = @scanner.check_code_state(code, local_variables: @context.local_variables)
return code if terminated
@@ -1093,34 +1117,48 @@ module IRB
loop do
code = readmultiline
break unless code
-
- if code != "\n"
- yield build_statement(code), @line_no
- end
+ yield build_statement(code), @line_no
@line_no += code.count("\n")
rescue RubyLex::TerminateLineInput
end
end
def build_statement(code)
+ if code.match?(/\A\n*\z/)
+ return Statement::EmptyInput.new
+ end
+
code.force_encoding(@context.io.encoding)
- command_or_alias, arg = code.split(/\s/, 2)
- # Transform a non-identifier alias (@, $) or keywords (next, break)
- command_name = @context.command_aliases[command_or_alias.to_sym]
- command = command_name || command_or_alias
- command_class = ExtendCommandBundle.load_command(command)
-
- if command_class
- Statement::Command.new(code, command, arg, command_class)
+ if (command, arg = parse_command(code))
+ command_class = Command.load_command(command)
+ Statement::Command.new(code, command_class, arg)
else
is_assignment_expression = @scanner.assignment_expression?(code, local_variables: @context.local_variables)
Statement::Expression.new(code, is_assignment_expression)
end
end
- def single_line_command?(code)
- command = code.split(/\s/, 2).first
- @context.symbol_alias?(command) || @context.transform_args?(command)
+ def parse_command(code)
+ command_name, arg = code.strip.split(/\s+/, 2)
+ return unless code.lines.size == 1 && command_name
+
+ arg ||= ''
+ command = command_name.to_sym
+ # Command aliases are always command. example: $, @
+ if (alias_name = @context.command_aliases[command])
+ return [alias_name, arg]
+ end
+
+ # Check visibility
+ public_method = !!Kernel.instance_method(:public_method).bind_call(@context.main, command) rescue false
+ private_method = !public_method && !!Kernel.instance_method(:method).bind_call(@context.main, command) rescue false
+ if Command.execute_as_command?(command, public_method: public_method, private_method: private_method)
+ [command, arg]
+ end
+ end
+
+ def command?(code)
+ !!parse_command(code)
end
def configure_io
@@ -1138,8 +1176,7 @@ module IRB
false
end
else
- # Accept any single-line input for symbol aliases or commands that transform args
- next true if single_line_command?(code)
+ next true if command?(code)
_tokens, _opens, terminated = @scanner.check_code_state(code, local_variables: @context.local_variables)
terminated
@@ -1148,13 +1185,13 @@ module IRB
end
if @context.io.respond_to?(:dynamic_prompt)
@context.io.dynamic_prompt do |lines|
- lines << '' if lines.empty?
tokens = RubyLex.ripper_lex_without_warning(lines.map{ |l| l + "\n" }.join, local_variables: @context.local_variables)
line_results = IRB::NestingParser.parse_by_line(tokens)
tokens_until_line = []
line_results.map.with_index do |(line_tokens, _prev_opens, next_opens, _min_depth), line_num_offset|
line_tokens.each do |token, _s|
- # Avoid appending duplicated token. Tokens that include "\n" like multiline tstring_content can exist in multiple lines.
+ # Avoid appending duplicated token. Tokens that include "n" like multiline
+ # tstring_content can exist in multiple lines.
tokens_until_line << token if token != tokens_until_line.last
end
continue = @scanner.should_continue?(tokens_until_line)
@@ -1211,20 +1248,33 @@ module IRB
irb_bug = true
else
irb_bug = false
+ # To support backtrace filtering while utilizing Exception#full_message, we need to clone
+ # the exception to avoid modifying the original exception's backtrace.
+ exc = exc.clone
+ filtered_backtrace = exc.backtrace.map { |l| @context.workspace.filter_backtrace(l) }.compact
+ backtrace_filter = IRB.conf[:BACKTRACE_FILTER]
+
+ if backtrace_filter
+ if backtrace_filter.respond_to?(:call)
+ filtered_backtrace = backtrace_filter.call(filtered_backtrace)
+ else
+ warn "IRB.conf[:BACKTRACE_FILTER] #{backtrace_filter} should respond to `call` method"
+ end
+ end
+
+ exc.set_backtrace(filtered_backtrace)
end
- if RUBY_VERSION < '3.0.0'
- if STDOUT.tty?
- message = exc.full_message(order: :bottom)
- order = :bottom
- else
- message = exc.full_message(order: :top)
- order = :top
+ highlight = Color.colorable?
+
+ order =
+ if RUBY_VERSION < '3.0.0'
+ STDOUT.tty? ? :bottom : :top
+ else # '3.0.0' <= RUBY_VERSION
+ :top
end
- else # '3.0.0' <= RUBY_VERSION
- message = exc.full_message(order: :top)
- order = :top
- end
+
+ message = exc.full_message(order: order, highlight: highlight)
message = convert_invalid_byte_sequence(message, exc.message.encoding)
message = encode_with_invalid_byte_sequence(message, IRB.conf[:LC_MESSAGES].encoding) unless message.encoding.to_s.casecmp?(IRB.conf[:LC_MESSAGES].encoding.to_s)
message = message.gsub(/((?:^\t.+$\n)+)/) { |m|
@@ -1235,7 +1285,6 @@ module IRB
lines = m.split("\n").reverse
end
unless irb_bug
- lines = lines.map { |l| @context.workspace.filter_backtrace(l) }.compact
if lines.size > @context.back_trace_limit
omit = lines.size - @context.back_trace_limit
lines = lines[0..(@context.back_trace_limit - 1)]
@@ -1246,7 +1295,7 @@ module IRB
lines.map{ |l| l + "\n" }.join
}
# The "<top (required)>" in "(irb)" may be the top level of IRB so imitate the main object.
- message = message.gsub(/\(irb\):(?<num>\d+):in `<(?<frame>top \(required\))>'/) { "(irb):#{$~[:num]}:in `<main>'" }
+ message = message.gsub(/\(irb\):(?<num>\d+):in (?<open_quote>[`'])<(?<frame>top \(required\))>'/) { "(irb):#{$~[:num]}:in #{$~[:open_quote]}<main>'" }
puts message
puts 'Maybe IRB bug!' if irb_bug
rescue Exception => handler_exc
@@ -1258,11 +1307,10 @@ module IRB
end
end
- # Evaluates the given block using the given +path+ as the Context#irb_path
- # and +name+ as the Context#irb_name.
+ # Evaluates the given block using the given `path` as the Context#irb_path and
+ # `name` as the Context#irb_name.
#
- # Used by the irb command +source+, see IRB@IRB+Sessions for more
- # information.
+ # Used by the irb command `source`, see IRB@IRB+Sessions for more information.
def suspend_name(path = nil, name = nil)
@context.irb_path, back_path = path, @context.irb_path if path
@context.irb_name, back_name = name, @context.irb_name if name
@@ -1274,25 +1322,22 @@ module IRB
end
end
- # Evaluates the given block using the given +workspace+ as the
+ # Evaluates the given block using the given `workspace` as the
# Context#workspace.
#
- # Used by the irb command +irb_load+, see IRB@IRB+Sessions for more
- # information.
+ # Used by the irb command `irb_load`, see IRB@IRB+Sessions for more information.
def suspend_workspace(workspace)
- @context.workspace, back_workspace = workspace, @context.workspace
- begin
- yield back_workspace
- ensure
- @context.workspace = back_workspace
- end
+ current_workspace = @context.workspace
+ @context.replace_workspace(workspace)
+ yield
+ ensure
+ @context.replace_workspace current_workspace
end
- # Evaluates the given block using the given +input_method+ as the
- # Context#io.
+ # Evaluates the given block using the given `input_method` as the Context#io.
#
- # Used by the irb commands +source+ and +irb_load+, see IRB@IRB+Sessions
- # for more information.
+ # Used by the irb commands `source` and `irb_load`, see IRB@IRB+Sessions for
+ # more information.
def suspend_input_method(input_method)
back_io = @context.io
@context.instance_eval{@io = input_method}
@@ -1325,7 +1370,7 @@ module IRB
end
end
- # Evaluates the given block using the given +status+.
+ # Evaluates the given block using the given `status`.
def signal_status(status)
return yield if @signal_status == :IN_LOAD
@@ -1375,8 +1420,8 @@ module IRB
Pager.page_content(format(@context.return_format, str), retain_content: true)
end
- # Outputs the local variables to this current session, including
- # #signal_status and #context, using IRB::Locale.
+ # Outputs the local variables to this current session, including #signal_status
+ # and #context, using IRB::Locale.
def inspect
ary = []
for iv in instance_variables
@@ -1434,7 +1479,7 @@ module IRB
end
def format_prompt(format, ltype, indent, line_no) # :nodoc:
- format.gsub(/%([0-9]+)?([a-zA-Z])/) do
+ format.gsub(/%([0-9]+)?([a-zA-Z%])/) do
case $2
when "N"
@context.irb_name
@@ -1467,7 +1512,7 @@ module IRB
line_no.to_s
end
when "%"
- "%"
+ "%" unless $1
end
end
end
@@ -1475,12 +1520,11 @@ module IRB
end
class Binding
- # Opens an IRB session where +binding.irb+ is called which allows for
- # interactive debugging. You can call any methods or variables available in
- # the current scope, and mutate state if you need to.
- #
+ # Opens an IRB session where `binding.irb` is called which allows for
+ # interactive debugging. You can call any methods or variables available in the
+ # current scope, and mutate state if you need to.
#
- # Given a Ruby file called +potato.rb+ containing the following code:
+ # Given a Ruby file called `potato.rb` containing the following code:
#
# class Potato
# def initialize
@@ -1492,8 +1536,8 @@ class Binding
#
# Potato.new
#
- # Running <code>ruby potato.rb</code> will open an IRB session where
- # +binding.irb+ is called, and you will see the following:
+ # Running `ruby potato.rb` will open an IRB session where `binding.irb` is
+ # called, and you will see the following:
#
# $ ruby potato.rb
#
@@ -1523,8 +1567,8 @@ class Binding
# irb(#<Potato:0x00007feea1916670>):004:0> @cooked = true
# => true
#
- # You can exit the IRB session with the +exit+ command. Note that exiting will
- # resume execution where +binding.irb+ had paused it, as you can see from the
+ # You can exit the IRB session with the `exit` command. Note that exiting will
+ # resume execution where `binding.irb` had paused it, as you can see from the
# output printed to standard output in this example:
#
# irb(#<Potato:0x00007feea1916670>):005:0> exit
@@ -1533,7 +1577,7 @@ class Binding
# See IRB for more information.
def irb(show_code: true)
# Setup IRB with the current file's path and no command line arguments
- IRB.setup(source_location[0], argv: [])
+ IRB.setup(source_location[0], argv: []) unless IRB.initialized?
# Create a new workspace using the current binding
workspace = IRB::WorkSpace.new(self)
# Print the code around the binding if show_code is true
@@ -1545,16 +1589,17 @@ class Binding
if debugger_irb
# If we're already in a debugger session, set the workspace and irb_path for the original IRB instance
- debugger_irb.context.workspace = workspace
+ debugger_irb.context.replace_workspace(workspace)
debugger_irb.context.irb_path = irb_path
- # If we've started a debugger session and hit another binding.irb, we don't want to start an IRB session
- # instead, we want to resume the irb:rdbg session.
+ # If we've started a debugger session and hit another binding.irb, we don't want
+ # to start an IRB session instead, we want to resume the irb:rdbg session.
IRB::Debug.setup(debugger_irb)
IRB::Debug.insert_debug_break
debugger_irb.debug_break
else
- # If we're not in a debugger session, create a new IRB instance with the current workspace
- binding_irb = IRB::Irb.new(workspace)
+ # If we're not in a debugger session, create a new IRB instance with the current
+ # workspace
+ binding_irb = IRB::Irb.new(workspace, from_binding: true)
binding_irb.context.irb_path = irb_path
binding_irb.run(IRB.conf)
binding_irb.debug_break
diff --git a/lib/irb/cmd/backtrace.rb b/lib/irb/cmd/backtrace.rb
deleted file mode 100644
index f632894618..0000000000
--- a/lib/irb/cmd/backtrace.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-# frozen_string_literal: true
-
-require_relative "debug"
-
-module IRB
- # :stopdoc:
-
- module ExtendCommand
- class Backtrace < DebugCommand
- def self.transform_args(args)
- args&.dump
- end
-
- def execute(*args)
- super(pre_cmds: ["backtrace", *args].join(" "))
- end
- end
- end
-
- # :startdoc:
-end
diff --git a/lib/irb/cmd/break.rb b/lib/irb/cmd/break.rb
deleted file mode 100644
index df259a90ca..0000000000
--- a/lib/irb/cmd/break.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-# frozen_string_literal: true
-
-require_relative "debug"
-
-module IRB
- # :stopdoc:
-
- module ExtendCommand
- class Break < DebugCommand
- def self.transform_args(args)
- args&.dump
- end
-
- def execute(args = nil)
- super(pre_cmds: "break #{args}")
- end
- end
- end
-
- # :startdoc:
-end
diff --git a/lib/irb/cmd/catch.rb b/lib/irb/cmd/catch.rb
deleted file mode 100644
index 40b62c7533..0000000000
--- a/lib/irb/cmd/catch.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-# frozen_string_literal: true
-
-require_relative "debug"
-
-module IRB
- # :stopdoc:
-
- module ExtendCommand
- class Catch < DebugCommand
- def self.transform_args(args)
- args&.dump
- end
-
- def execute(*args)
- super(pre_cmds: ["catch", *args].join(" "))
- end
- end
- end
-
- # :startdoc:
-end
diff --git a/lib/irb/cmd/chws.rb b/lib/irb/cmd/chws.rb
deleted file mode 100644
index 31045f9bbb..0000000000
--- a/lib/irb/cmd/chws.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-# frozen_string_literal: false
-#
-# change-ws.rb -
-# by Keiju ISHITSUKA(keiju@ruby-lang.org)
-#
-
-require_relative "nop"
-require_relative "../ext/change-ws"
-
-module IRB
- # :stopdoc:
-
- module ExtendCommand
-
- class CurrentWorkingWorkspace < Nop
- category "Workspace"
- description "Show the current workspace."
-
- def execute(*obj)
- irb_context.main
- end
- end
-
- class ChangeWorkspace < Nop
- category "Workspace"
- description "Change the current workspace to an object."
-
- def execute(*obj)
- irb_context.change_workspace(*obj)
- irb_context.main
- end
- end
- end
-
- # :startdoc:
-end
diff --git a/lib/irb/cmd/edit.rb b/lib/irb/cmd/edit.rb
deleted file mode 100644
index 69606beea0..0000000000
--- a/lib/irb/cmd/edit.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-require 'shellwords'
-require_relative "nop"
-require_relative "../source_finder"
-
-module IRB
- # :stopdoc:
-
- module ExtendCommand
- class Edit < Nop
- category "Misc"
- description 'Open a file with the editor command defined with `ENV["VISUAL"]` or `ENV["EDITOR"]`.'
-
- class << self
- def transform_args(args)
- # Return a string literal as is for backward compatibility
- if args.nil? || args.empty? || string_literal?(args)
- args
- else # Otherwise, consider the input as a String for convenience
- args.strip.dump
- end
- end
- end
-
- def execute(*args)
- path = args.first
-
- if path.nil? && (irb_path = @irb_context.irb_path)
- path = irb_path
- end
-
- if !File.exist?(path)
- source =
- begin
- SourceFinder.new(@irb_context).find_source(path)
- rescue NameError
- # if user enters a path that doesn't exist, it'll cause NameError when passed here because find_source would try to evaluate it as well
- # in this case, we should just ignore the error
- end
-
- if source
- path = source.file
- else
- puts "Can not find file: #{path}"
- return
- end
- end
-
- if editor = (ENV['VISUAL'] || ENV['EDITOR'])
- puts "command: '#{editor}'"
- puts " path: #{path}"
- system(*Shellwords.split(editor), path)
- else
- puts "Can not find editor setting: ENV['VISUAL'] or ENV['EDITOR']"
- end
- end
- end
- end
-
- # :startdoc:
-end
diff --git a/lib/irb/cmd/help.rb b/lib/irb/cmd/help.rb
deleted file mode 100644
index 64b885c383..0000000000
--- a/lib/irb/cmd/help.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# frozen_string_literal: true
-
-require_relative "show_doc"
-
-module IRB
- module ExtendCommand
- class Help < ShowDoc
- category "Context"
- description "[DEPRECATED] Enter the mode to look up RI documents."
-
- DEPRECATION_MESSAGE = <<~MSG
- [Deprecation] The `help` command will be repurposed to display command help in the future.
- For RI document lookup, please use the `show_doc` command instead.
- For command help, please use `show_cmds` for now.
- MSG
-
- def execute(*names)
- warn DEPRECATION_MESSAGE
- super
- end
- end
- end
-end
diff --git a/lib/irb/cmd/info.rb b/lib/irb/cmd/info.rb
deleted file mode 100644
index 2c0a32b34f..0000000000
--- a/lib/irb/cmd/info.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-# frozen_string_literal: true
-
-require_relative "debug"
-
-module IRB
- # :stopdoc:
-
- module ExtendCommand
- class Info < DebugCommand
- def self.transform_args(args)
- args&.dump
- end
-
- def execute(*args)
- super(pre_cmds: ["info", *args].join(" "))
- end
- end
- end
-
- # :startdoc:
-end
diff --git a/lib/irb/cmd/nop.rb b/lib/irb/cmd/nop.rb
index 7fb197c51f..9d2e3c4d47 100644
--- a/lib/irb/cmd/nop.rb
+++ b/lib/irb/cmd/nop.rb
@@ -1,53 +1,4 @@
-# frozen_string_literal: false
-#
-# nop.rb -
-# by Keiju ISHITSUKA(keiju@ruby-lang.org)
-#
+# frozen_string_literal: true
-module IRB
- # :stopdoc:
-
- module ExtendCommand
- class CommandArgumentError < StandardError; end
-
- class Nop
- class << self
- def category(category = nil)
- @category = category if category
- @category
- end
-
- def description(description = nil)
- @description = description if description
- @description
- end
-
- private
-
- def string_literal?(args)
- sexp = Ripper.sexp(args)
- sexp && sexp.size == 2 && sexp.last&.first&.first == :string_literal
- end
- end
-
- def self.execute(irb_context, *opts, **kwargs, &block)
- command = new(irb_context)
- command.execute(*opts, **kwargs, &block)
- rescue CommandArgumentError => e
- puts e.message
- end
-
- def initialize(irb_context)
- @irb_context = irb_context
- end
-
- attr_reader :irb_context
-
- def execute(*opts)
- #nop
- end
- end
- end
-
- # :startdoc:
-end
+# This file is just a placeholder for backward-compatibility.
+# Please require 'irb' and inherit your command from `IRB::Command::Base` instead.
diff --git a/lib/irb/cmd/pushws.rb b/lib/irb/cmd/pushws.rb
deleted file mode 100644
index 59996ceb0c..0000000000
--- a/lib/irb/cmd/pushws.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-# frozen_string_literal: false
-#
-# change-ws.rb -
-# by Keiju ISHITSUKA(keiju@ruby-lang.org)
-#
-
-require_relative "nop"
-require_relative "../ext/workspaces"
-
-module IRB
- # :stopdoc:
-
- module ExtendCommand
- class Workspaces < Nop
- category "Workspace"
- description "Show workspaces."
-
- def execute(*obj)
- irb_context.workspaces.collect{|ws| ws.main}
- end
- end
-
- class PushWorkspace < Workspaces
- category "Workspace"
- description "Push an object to the workspace stack."
-
- def execute(*obj)
- irb_context.push_workspace(*obj)
- super
- end
- end
-
- class PopWorkspace < Workspaces
- category "Workspace"
- description "Pop a workspace from the workspace stack."
-
- def execute(*obj)
- irb_context.pop_workspace(*obj)
- super
- end
- end
- end
-
- # :startdoc:
-end
diff --git a/lib/irb/cmd/show_cmds.rb b/lib/irb/cmd/show_cmds.rb
deleted file mode 100644
index a8d899e4ac..0000000000
--- a/lib/irb/cmd/show_cmds.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-# frozen_string_literal: true
-
-require "stringio"
-require_relative "nop"
-require_relative "../pager"
-
-module IRB
- # :stopdoc:
-
- module ExtendCommand
- class ShowCmds < Nop
- category "IRB"
- description "List all available commands and their description."
-
- def execute(*args)
- commands_info = IRB::ExtendCommandBundle.all_commands_info
- commands_grouped_by_categories = commands_info.group_by { |cmd| cmd[:category] }
-
- user_aliases = irb_context.instance_variable_get(:@user_aliases)
-
- commands_grouped_by_categories["Aliases"] = user_aliases.map do |alias_name, target|
- { display_name: alias_name, description: "Alias for `#{target}`" }
- end
-
- if irb_context.with_debugger
- # Remove the original "Debugging" category
- commands_grouped_by_categories.delete("Debugging")
- # Remove the `help` command as it's delegated to the debugger
- commands_grouped_by_categories["Context"].delete_if { |cmd| cmd[:display_name] == :help }
- # Add an empty "Debugging (from debug.gem)" category at the end
- commands_grouped_by_categories["Debugging (from debug.gem)"] = []
- end
-
- longest_cmd_name_length = commands_info.map { |c| c[:display_name].length }.max
-
- output = StringIO.new
-
- commands_grouped_by_categories.each do |category, cmds|
- output.puts Color.colorize(category, [:BOLD])
-
- cmds.each do |cmd|
- output.puts " #{cmd[:display_name].to_s.ljust(longest_cmd_name_length)} #{cmd[:description]}"
- end
-
- output.puts
- end
-
- # Append the debugger help at the end
- if irb_context.with_debugger
- output.puts DEBUGGER__.help
- end
-
- Pager.page_content(output.string)
- end
- end
- end
-
- # :startdoc:
-end
diff --git a/lib/irb/cmd/show_doc.rb b/lib/irb/cmd/show_doc.rb
deleted file mode 100644
index 99dd9ab95a..0000000000
--- a/lib/irb/cmd/show_doc.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-# frozen_string_literal: true
-
-require_relative "nop"
-
-module IRB
- module ExtendCommand
- class ShowDoc < Nop
- class << self
- def transform_args(args)
- # Return a string literal as is for backward compatibility
- if args.empty? || string_literal?(args)
- args
- else # Otherwise, consider the input as a String for convenience
- args.strip.dump
- end
- end
- end
-
- category "Context"
- description "Enter the mode to look up RI documents."
-
- def execute(*names)
- require 'rdoc/ri/driver'
-
- unless ShowDoc.const_defined?(:Ri)
- opts = RDoc::RI::Driver.process_args([])
- ShowDoc.const_set(:Ri, RDoc::RI::Driver.new(opts))
- end
-
- if names.empty?
- Ri.interactive
- else
- names.each do |name|
- begin
- Ri.display_name(name.to_s)
- rescue RDoc::RI::Error
- puts $!.message
- end
- end
- end
-
- nil
- rescue LoadError, SystemExit
- warn "Can't display document because `rdoc` is not installed."
- end
- end
- end
-end
diff --git a/lib/irb/cmd/show_source.rb b/lib/irb/cmd/show_source.rb
deleted file mode 100644
index 826cb11ed2..0000000000
--- a/lib/irb/cmd/show_source.rb
+++ /dev/null
@@ -1,65 +0,0 @@
-# frozen_string_literal: true
-
-require_relative "nop"
-require_relative "../source_finder"
-require_relative "../pager"
-require_relative "../color"
-
-module IRB
- module ExtendCommand
- class ShowSource < Nop
- category "Context"
- description "Show the source code of a given method or constant."
-
- class << self
- def transform_args(args)
- # Return a string literal as is for backward compatibility
- if args.empty? || string_literal?(args)
- args
- else # Otherwise, consider the input as a String for convenience
- args.strip.dump
- end
- end
- end
-
- def execute(str = nil)
- unless str.is_a?(String)
- puts "Error: Expected a string but got #{str.inspect}"
- return
- end
-
- str, esses = str.split(" -")
- super_level = esses ? esses.count("s") : 0
- source = SourceFinder.new(@irb_context).find_source(str, super_level)
-
- if source
- show_source(source)
- elsif super_level > 0
- puts "Error: Couldn't locate a super definition for #{str}"
- else
- puts "Error: Couldn't locate a definition for #{str}"
- end
- nil
- end
-
- private
-
- def show_source(source)
- file_content = IRB::Color.colorize_code(File.read(source.file))
- code = file_content.lines[(source.first_line - 1)...source.last_line].join
- content = <<~CONTENT
-
- #{bold("From")}: #{source.file}:#{source.first_line}
-
- #{code}
- CONTENT
-
- Pager.page_content(content)
- end
-
- def bold(str)
- Color.colorize(str, [:BOLD])
- end
- end
- end
-end
diff --git a/lib/irb/color.rb b/lib/irb/color.rb
index ad8670160c..fca942b28b 100644
--- a/lib/irb/color.rb
+++ b/lib/irb/color.rb
@@ -79,12 +79,12 @@ module IRB # :nodoc:
class << self
def colorable?
- supported = $stdout.tty? && (/mswin|mingw/ =~ RUBY_PLATFORM || (ENV.key?('TERM') && ENV['TERM'] != 'dumb'))
+ supported = $stdout.tty? && (/mswin|mingw/.match?(RUBY_PLATFORM) || (ENV.key?('TERM') && ENV['TERM'] != 'dumb'))
# because ruby/debug also uses irb's color module selectively,
# irb won't be activated in that case.
if IRB.respond_to?(:conf)
- supported && IRB.conf.fetch(:USE_COLORIZE, true)
+ supported && !!IRB.conf.fetch(:USE_COLORIZE, true)
else
supported
end
diff --git a/lib/irb/command.rb b/lib/irb/command.rb
new file mode 100644
index 0000000000..68a4b52727
--- /dev/null
+++ b/lib/irb/command.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+#
+# irb/command.rb - irb command
+# by Keiju ISHITSUKA(keiju@ruby-lang.org)
+#
+
+require_relative "command/base"
+
+module IRB # :nodoc:
+ module Command
+ @commands = {}
+
+ class << self
+ attr_reader :commands
+
+ # Registers a command with the given name.
+ # Aliasing is intentionally not supported at the moment.
+ def register(name, command_class)
+ @commands[name.to_sym] = [command_class, []]
+ end
+ end
+ end
+end
diff --git a/lib/irb/command/backtrace.rb b/lib/irb/command/backtrace.rb
new file mode 100644
index 0000000000..687bb075ac
--- /dev/null
+++ b/lib/irb/command/backtrace.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require_relative "debug"
+
+module IRB
+ # :stopdoc:
+
+ module Command
+ class Backtrace < DebugCommand
+ def execute(arg)
+ execute_debug_command(pre_cmds: "backtrace #{arg}")
+ end
+ end
+ end
+
+ # :startdoc:
+end
diff --git a/lib/irb/command/base.rb b/lib/irb/command/base.rb
new file mode 100644
index 0000000000..1d406630a2
--- /dev/null
+++ b/lib/irb/command/base.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+#
+# nop.rb -
+# by Keiju ISHITSUKA(keiju@ruby-lang.org)
+#
+
+module IRB
+ # :stopdoc:
+
+ module Command
+ class CommandArgumentError < StandardError; end
+
+ def self.extract_ruby_args(*args, **kwargs)
+ throw :EXTRACT_RUBY_ARGS, [args, kwargs]
+ end
+
+ class Base
+ class << self
+ def category(category = nil)
+ @category = category if category
+ @category || "No category"
+ end
+
+ def description(description = nil)
+ @description = description if description
+ @description || "No description provided."
+ end
+
+ def help_message(help_message = nil)
+ @help_message = help_message if help_message
+ @help_message
+ end
+
+ private
+
+ def highlight(text)
+ Color.colorize(text, [:BOLD, :BLUE])
+ end
+ end
+
+ def self.execute(irb_context, arg)
+ new(irb_context).execute(arg)
+ rescue CommandArgumentError => e
+ puts e.message
+ end
+
+ def initialize(irb_context)
+ @irb_context = irb_context
+ end
+
+ attr_reader :irb_context
+
+ def execute(arg)
+ #nop
+ end
+ end
+
+ Nop = Base
+ end
+
+ # :startdoc:
+end
diff --git a/lib/irb/command/break.rb b/lib/irb/command/break.rb
new file mode 100644
index 0000000000..a8f81fe665
--- /dev/null
+++ b/lib/irb/command/break.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require_relative "debug"
+
+module IRB
+ # :stopdoc:
+
+ module Command
+ class Break < DebugCommand
+ def execute(arg)
+ execute_debug_command(pre_cmds: "break #{arg}")
+ end
+ end
+ end
+
+ # :startdoc:
+end
diff --git a/lib/irb/command/catch.rb b/lib/irb/command/catch.rb
new file mode 100644
index 0000000000..529dcbca5a
--- /dev/null
+++ b/lib/irb/command/catch.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require_relative "debug"
+
+module IRB
+ # :stopdoc:
+
+ module Command
+ class Catch < DebugCommand
+ def execute(arg)
+ execute_debug_command(pre_cmds: "catch #{arg}")
+ end
+ end
+ end
+
+ # :startdoc:
+end
diff --git a/lib/irb/command/chws.rb b/lib/irb/command/chws.rb
new file mode 100644
index 0000000000..ef456d0961
--- /dev/null
+++ b/lib/irb/command/chws.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+#
+# change-ws.rb -
+# by Keiju ISHITSUKA(keiju@ruby-lang.org)
+#
+require_relative "../ext/change-ws"
+
+module IRB
+ # :stopdoc:
+
+ module Command
+
+ class CurrentWorkingWorkspace < Base
+ category "Workspace"
+ description "Show the current workspace."
+
+ def execute(_arg)
+ puts "Current workspace: #{irb_context.main}"
+ end
+ end
+
+ class ChangeWorkspace < Base
+ category "Workspace"
+ description "Change the current workspace to an object."
+
+ def execute(arg)
+ if arg.empty?
+ irb_context.change_workspace
+ else
+ obj = eval(arg, irb_context.workspace.binding)
+ irb_context.change_workspace(obj)
+ end
+
+ puts "Current workspace: #{irb_context.main}"
+ end
+ end
+ end
+
+ # :startdoc:
+end
diff --git a/lib/irb/command/context.rb b/lib/irb/command/context.rb
new file mode 100644
index 0000000000..b4fc807343
--- /dev/null
+++ b/lib/irb/command/context.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module IRB
+ module Command
+ class Context < Base
+ category "IRB"
+ description "Displays current configuration."
+
+ def execute(_arg)
+ # This command just displays the configuration.
+ # Modifying the configuration is achieved by sending a message to IRB.conf.
+ Pager.page_content(IRB.CurrentContext.inspect)
+ end
+ end
+ end
+end
diff --git a/lib/irb/cmd/continue.rb b/lib/irb/command/continue.rb
index 9136177eef..0daa029b15 100644
--- a/lib/irb/cmd/continue.rb
+++ b/lib/irb/command/continue.rb
@@ -5,10 +5,10 @@ require_relative "debug"
module IRB
# :stopdoc:
- module ExtendCommand
+ module Command
class Continue < DebugCommand
- def execute(*args)
- super(do_cmds: ["continue", *args].join(" "))
+ def execute(arg)
+ execute_debug_command(do_cmds: "continue #{arg}")
end
end
end
diff --git a/lib/irb/cmd/debug.rb b/lib/irb/command/debug.rb
index e236084ca4..8a091a49ed 100644
--- a/lib/irb/cmd/debug.rb
+++ b/lib/irb/command/debug.rb
@@ -1,20 +1,21 @@
-require_relative "nop"
require_relative "../debug"
module IRB
# :stopdoc:
- module ExtendCommand
- class Debug < Nop
+ module Command
+ class Debug < Base
category "Debugging"
description "Start the debugger of debug.gem."
- BINDING_IRB_FRAME_REGEXPS = [
- '<internal:prelude>',
- binding.method(:irb).source_location.first,
- ].map { |file| /\A#{Regexp.escape(file)}:\d+:in `irb'\z/ }
+ def execute(_arg)
+ execute_debug_command
+ end
+
+ def execute_debug_command(pre_cmds: nil, do_cmds: nil)
+ pre_cmds = pre_cmds&.rstrip
+ do_cmds = do_cmds&.rstrip
- def execute(pre_cmds: nil, do_cmds: nil)
if irb_context.with_debugger
# If IRB is already running with a debug session, throw the command and IRB.debug_readline will pass it to the debugger.
if cmd = pre_cmds || do_cmds
@@ -30,7 +31,7 @@ module IRB
# 3. Insert a debug breakpoint at `Irb#debug_break` with the intended command.
# 4. Exit the current Irb#run call via `throw :IRB_EXIT`.
# 5. `Irb#debug_break` will be called and trigger the breakpoint, which will run the intended command.
- unless binding_irb?
+ unless irb_context.from_binding?
puts "Debugging commands are only available when IRB is started with binding.irb"
return
end
@@ -54,16 +55,6 @@ module IRB
throw :IRB_EXIT
end
end
-
- private
-
- def binding_irb?
- caller.any? do |frame|
- BINDING_IRB_FRAME_REGEXPS.any? do |regexp|
- frame.match?(regexp)
- end
- end
- end
end
class DebugCommand < Debug
diff --git a/lib/irb/cmd/delete.rb b/lib/irb/command/delete.rb
index aeb26d2572..2a57a4a3de 100644
--- a/lib/irb/cmd/delete.rb
+++ b/lib/irb/command/delete.rb
@@ -5,10 +5,10 @@ require_relative "debug"
module IRB
# :stopdoc:
- module ExtendCommand
+ module Command
class Delete < DebugCommand
- def execute(*args)
- super(pre_cmds: ["delete", *args].join(" "))
+ def execute(arg)
+ execute_debug_command(pre_cmds: "delete #{arg}")
end
end
end
diff --git a/lib/irb/command/disable_irb.rb b/lib/irb/command/disable_irb.rb
new file mode 100644
index 0000000000..0b00d0302b
--- /dev/null
+++ b/lib/irb/command/disable_irb.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module IRB
+ # :stopdoc:
+
+ module Command
+ class DisableIrb < Base
+ category "IRB"
+ description "Disable binding.irb."
+
+ def execute(*)
+ ::Binding.define_method(:irb) {}
+ IRB.irb_exit
+ end
+ end
+ end
+
+ # :startdoc:
+end
diff --git a/lib/irb/command/edit.rb b/lib/irb/command/edit.rb
new file mode 100644
index 0000000000..cb7e0c4873
--- /dev/null
+++ b/lib/irb/command/edit.rb
@@ -0,0 +1,63 @@
+require 'shellwords'
+
+require_relative "../color"
+require_relative "../source_finder"
+
+module IRB
+ # :stopdoc:
+
+ module Command
+ class Edit < Base
+ include RubyArgsExtractor
+
+ category "Misc"
+ description 'Open a file or source location.'
+ help_message <<~HELP_MESSAGE
+ Usage: edit [FILE or constant or method signature]
+
+ Open a file in the editor specified in #{highlight('ENV["VISUAL"]')} or #{highlight('ENV["EDITOR"]')}
+
+ - If no arguments are provided, IRB will attempt to open the file the current context was defined in.
+ - If FILE is provided, IRB will open the file.
+ - If a constant or method signature is provided, IRB will attempt to locate the source file and open it.
+
+ Examples:
+
+ edit
+ edit foo.rb
+ edit Foo
+ edit Foo#bar
+ HELP_MESSAGE
+
+ def execute(arg)
+ # Accept string literal for backward compatibility
+ path = unwrap_string_literal(arg)
+
+ if path.nil?
+ path = @irb_context.irb_path
+ elsif !File.exist?(path)
+ source = SourceFinder.new(@irb_context).find_source(path)
+
+ if source&.file_exist? && !source.binary_file?
+ path = source.file
+ end
+ end
+
+ unless File.exist?(path)
+ puts "Can not find file: #{path}"
+ return
+ end
+
+ if editor = (ENV['VISUAL'] || ENV['EDITOR'])
+ puts "command: '#{editor}'"
+ puts " path: #{path}"
+ system(*Shellwords.split(editor), path)
+ else
+ puts "Can not find editor setting: ENV['VISUAL'] or ENV['EDITOR']"
+ end
+ end
+ end
+ end
+
+ # :startdoc:
+end
diff --git a/lib/irb/command/exit.rb b/lib/irb/command/exit.rb
new file mode 100644
index 0000000000..b4436f0343
--- /dev/null
+++ b/lib/irb/command/exit.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module IRB
+ # :stopdoc:
+
+ module Command
+ class Exit < Base
+ category "IRB"
+ description "Exit the current irb session."
+
+ def execute(_arg)
+ IRB.irb_exit
+ end
+ end
+ end
+
+ # :startdoc:
+end
diff --git a/lib/irb/cmd/finish.rb b/lib/irb/command/finish.rb
index 29f100feb5..3311a0e6e9 100644
--- a/lib/irb/cmd/finish.rb
+++ b/lib/irb/command/finish.rb
@@ -5,10 +5,10 @@ require_relative "debug"
module IRB
# :stopdoc:
- module ExtendCommand
+ module Command
class Finish < DebugCommand
- def execute(*args)
- super(do_cmds: ["finish", *args].join(" "))
+ def execute(arg)
+ execute_debug_command(do_cmds: "finish #{arg}")
end
end
end
diff --git a/lib/irb/command/force_exit.rb b/lib/irb/command/force_exit.rb
new file mode 100644
index 0000000000..14086aa849
--- /dev/null
+++ b/lib/irb/command/force_exit.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module IRB
+ # :stopdoc:
+
+ module Command
+ class ForceExit < Base
+ category "IRB"
+ description "Exit the current process."
+
+ def execute(_arg)
+ throw :IRB_EXIT, true
+ end
+ end
+ end
+
+ # :startdoc:
+end
diff --git a/lib/irb/command/help.rb b/lib/irb/command/help.rb
new file mode 100644
index 0000000000..c2018f9b30
--- /dev/null
+++ b/lib/irb/command/help.rb
@@ -0,0 +1,83 @@
+# frozen_string_literal: true
+
+module IRB
+ module Command
+ class Help < Base
+ category "Help"
+ description "List all available commands. Use `help <command>` to get information about a specific command."
+
+ def execute(command_name)
+ content =
+ if command_name.empty?
+ help_message
+ else
+ if command_class = Command.load_command(command_name)
+ command_class.help_message || command_class.description
+ else
+ "Can't find command `#{command_name}`. Please check the command name and try again.\n\n"
+ end
+ end
+ Pager.page_content(content)
+ end
+
+ private
+
+ def help_message
+ commands_info = IRB::Command.all_commands_info
+ helper_methods_info = IRB::HelperMethod.all_helper_methods_info
+ commands_grouped_by_categories = commands_info.group_by { |cmd| cmd[:category] }
+ commands_grouped_by_categories["Helper methods"] = helper_methods_info
+
+ if irb_context.with_debugger
+ # Remove the original "Debugging" category
+ commands_grouped_by_categories.delete("Debugging")
+ end
+
+ longest_cmd_name_length = commands_info.map { |c| c[:display_name].length }.max
+
+ output = StringIO.new
+
+ help_cmds = commands_grouped_by_categories.delete("Help")
+ no_category_cmds = commands_grouped_by_categories.delete("No category")
+ aliases = irb_context.instance_variable_get(:@user_aliases).map do |alias_name, target|
+ { display_name: alias_name, description: "Alias for `#{target}`" }
+ end
+
+ # Display help commands first
+ add_category_to_output("Help", help_cmds, output, longest_cmd_name_length)
+
+ # Display the rest of the commands grouped by categories
+ commands_grouped_by_categories.each do |category, cmds|
+ add_category_to_output(category, cmds, output, longest_cmd_name_length)
+ end
+
+ # Display commands without a category
+ if no_category_cmds
+ add_category_to_output("No category", no_category_cmds, output, longest_cmd_name_length)
+ end
+
+ # Display aliases
+ add_category_to_output("Aliases", aliases, output, longest_cmd_name_length)
+
+ # Append the debugger help at the end
+ if irb_context.with_debugger
+ # Add "Debugging (from debug.gem)" category as title
+ add_category_to_output("Debugging (from debug.gem)", [], output, longest_cmd_name_length)
+ output.puts DEBUGGER__.help
+ end
+
+ output.string
+ end
+
+ def add_category_to_output(category, cmds, output, longest_cmd_name_length)
+ output.puts Color.colorize(category, [:BOLD])
+
+ cmds.each do |cmd|
+ output.puts " #{cmd[:display_name].to_s.ljust(longest_cmd_name_length)} #{cmd[:description]}"
+ end
+
+ output.puts
+ end
+ end
+ end
+end
diff --git a/lib/irb/cmd/history.rb b/lib/irb/command/history.rb
index 5b712fa44d..90f87f9102 100644
--- a/lib/irb/cmd/history.rb
+++ b/lib/irb/command/history.rb
@@ -1,25 +1,23 @@
# frozen_string_literal: true
require "stringio"
-require_relative "nop"
+
require_relative "../pager"
module IRB
# :stopdoc:
- module ExtendCommand
- class History < Nop
+ module Command
+ class History < Base
category "IRB"
description "Shows the input history. `-g [query]` or `-G [query]` allows you to filter the output."
- def self.transform_args(args)
- match = args&.match(/(-g|-G)\s+(?<grep>.+)\s*\n\z/)
- return unless match
+ def execute(arg)
- "grep: #{Regexp.new(match[:grep]).inspect}"
- end
+ if (match = arg&.match(/(-g|-G)\s+(?<grep>.+)\s*\n\z/))
+ grep = Regexp.new(match[:grep])
+ end
- def execute(grep: nil)
formatted_inputs = irb_context.io.class::HISTORY.each_with_index.reverse_each.filter_map do |input, index|
next if grep && !input.match?(grep)
diff --git a/lib/irb/command/info.rb b/lib/irb/command/info.rb
new file mode 100644
index 0000000000..d08ce00a32
--- /dev/null
+++ b/lib/irb/command/info.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require_relative "debug"
+
+module IRB
+ # :stopdoc:
+
+ module Command
+ class Info < DebugCommand
+ def execute(arg)
+ execute_debug_command(pre_cmds: "info #{arg}")
+ end
+ end
+ end
+
+ # :startdoc:
+end
diff --git a/lib/irb/command/internal_helpers.rb b/lib/irb/command/internal_helpers.rb
new file mode 100644
index 0000000000..249b5cdede
--- /dev/null
+++ b/lib/irb/command/internal_helpers.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module IRB
+ module Command
+ # Internal use only, for default command's backward compatibility.
+ module RubyArgsExtractor # :nodoc:
+ def unwrap_string_literal(str)
+ return if str.empty?
+
+ sexp = Ripper.sexp(str)
+ if sexp && sexp.size == 2 && sexp.last&.first&.first == :string_literal
+ @irb_context.workspace.binding.eval(str).to_s
+ else
+ str
+ end
+ end
+
+ def ruby_args(arg)
+ # Use throw and catch to handle arg that includes `;`
+ # For example: "1, kw: (2; 3); 4" will be parsed to [[1], { kw: 3 }]
+ catch(:EXTRACT_RUBY_ARGS) do
+ @irb_context.workspace.binding.eval "IRB::Command.extract_ruby_args #{arg}"
+ end || [[], {}]
+ end
+ end
+ end
+end
diff --git a/lib/irb/cmd/irb_info.rb b/lib/irb/command/irb_info.rb
index 5b905a09bd..6d868de94c 100644
--- a/lib/irb/cmd/irb_info.rb
+++ b/lib/irb/command/irb_info.rb
@@ -1,21 +1,20 @@
-# frozen_string_literal: false
-
-require_relative "nop"
+# frozen_string_literal: true
module IRB
# :stopdoc:
- module ExtendCommand
- class IrbInfo < Nop
+ module Command
+ class IrbInfo < Base
category "IRB"
description "Show information about IRB."
- def execute
+ def execute(_arg)
str = "Ruby version: #{RUBY_VERSION}\n"
str += "IRB version: #{IRB.version}\n"
str += "InputMethod: #{IRB.CurrentContext.io.inspect}\n"
str += "Completion: #{IRB.CurrentContext.io.respond_to?(:completion_info) ? IRB.CurrentContext.io.completion_info : 'off'}\n"
- str += ".irbrc path: #{IRB.rc_file}\n" if File.exist?(IRB.rc_file)
+ rc_files = IRB.irbrc_files
+ str += ".irbrc paths: #{rc_files.join(", ")}\n" if rc_files.any?
str += "RUBY_PLATFORM: #{RUBY_PLATFORM}\n"
str += "LANG env: #{ENV["LANG"]}\n" if ENV["LANG"] && !ENV["LANG"].empty?
str += "LC_ALL env: #{ENV["LC_ALL"]}\n" if ENV["LC_ALL"] && !ENV["LC_ALL"].empty?
diff --git a/lib/irb/cmd/load.rb b/lib/irb/command/load.rb
index a3e797a7e0..1cd3f279d1 100644
--- a/lib/irb/cmd/load.rb
+++ b/lib/irb/command/load.rb
@@ -1,17 +1,16 @@
-# frozen_string_literal: false
+# frozen_string_literal: true
#
# load.rb -
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
#
-
-require_relative "nop"
require_relative "../ext/loader"
module IRB
# :stopdoc:
- module ExtendCommand
- class LoaderCommand < Nop
+ module Command
+ class LoaderCommand < Base
+ include RubyArgsExtractor
include IrbLoader
def raise_cmd_argument_error
@@ -23,7 +22,12 @@ module IRB
category "IRB"
description "Load a Ruby file."
- def execute(file_name = nil, priv = nil)
+ def execute(arg)
+ args, kwargs = ruby_args(arg)
+ execute_internal(*args, **kwargs)
+ end
+
+ def execute_internal(file_name = nil, priv = nil)
raise_cmd_argument_error unless file_name
irb_load(file_name, priv)
end
@@ -32,7 +36,13 @@ module IRB
class Require < LoaderCommand
category "IRB"
description "Require a Ruby file."
- def execute(file_name = nil)
+
+ def execute(arg)
+ args, kwargs = ruby_args(arg)
+ execute_internal(*args, **kwargs)
+ end
+
+ def execute_internal(file_name = nil)
raise_cmd_argument_error unless file_name
rex = Regexp.new("#{Regexp.quote(file_name)}(\.o|\.rb)?")
@@ -65,7 +75,12 @@ module IRB
category "IRB"
description "Loads a given file in the current session."
- def execute(file_name = nil)
+ def execute(arg)
+ args, kwargs = ruby_args(arg)
+ execute_internal(*args, **kwargs)
+ end
+
+ def execute_internal(file_name = nil)
raise_cmd_argument_error unless file_name
source_file(file_name)
diff --git a/lib/irb/cmd/ls.rb b/lib/irb/command/ls.rb
index 791b1c1b21..cbd9998bc4 100644
--- a/lib/irb/cmd/ls.rb
+++ b/lib/irb/command/ls.rb
@@ -2,39 +2,55 @@
require "reline"
require "stringio"
-require_relative "nop"
+
require_relative "../pager"
require_relative "../color"
module IRB
# :stopdoc:
- module ExtendCommand
- class Ls < Nop
+ module Command
+ class Ls < Base
+ include RubyArgsExtractor
+
category "Context"
- description "Show methods, constants, and variables. `-g [query]` or `-G [query]` allows you to filter out the output."
+ description "Show methods, constants, and variables."
+
+ help_message <<~HELP_MESSAGE
+ Usage: ls [obj] [-g [query]]
+
+ -g [query] Filter the output with a query.
+ HELP_MESSAGE
- def self.transform_args(args)
- if match = args&.match(/\A(?<args>.+\s|)(-g|-G)\s+(?<grep>[^\s]+)\s*\n\z/)
- args = match[:args]
- "#{args}#{',' unless args.chomp.empty?} grep: /#{match[:grep]}/"
+ def execute(arg)
+ if match = arg.match(/\A(?<target>.+\s|)(-g|-G)\s+(?<grep>.+)$/)
+ if match[:target].empty?
+ use_main = true
+ else
+ obj = @irb_context.workspace.binding.eval(match[:target])
+ end
+ grep = Regexp.new(match[:grep])
else
- args
+ args, kwargs = ruby_args(arg)
+ use_main = args.empty?
+ obj = args.first
+ grep = kwargs[:grep]
+ end
+
+ if use_main
+ obj = irb_context.workspace.main
+ locals = irb_context.workspace.binding.local_variables
end
- end
- def execute(*arg, grep: nil)
o = Output.new(grep: grep)
- obj = arg.empty? ? irb_context.workspace.main : arg.first
- locals = arg.empty? ? irb_context.workspace.binding.local_variables : []
klass = (obj.class == Class || obj.class == Module ? obj : obj.class)
o.dump("constants", obj.constants) if obj.respond_to?(:constants)
dump_methods(o, klass, obj)
o.dump("instance variables", obj.instance_variables)
o.dump("class variables", klass.class_variables)
- o.dump("locals", locals)
+ o.dump("locals", locals) if locals
o.print_result
end
diff --git a/lib/irb/cmd/measure.rb b/lib/irb/command/measure.rb
index 4e1125a0a6..f96be20de8 100644
--- a/lib/irb/cmd/measure.rb
+++ b/lib/irb/command/measure.rb
@@ -1,10 +1,10 @@
-require_relative "nop"
-
module IRB
# :stopdoc:
- module ExtendCommand
- class Measure < Nop
+ module Command
+ class Measure < Base
+ include RubyArgsExtractor
+
category "Misc"
description "`measure` enables the mode to measure processing time. `measure :off` disables it."
@@ -12,15 +12,19 @@ module IRB
super(*args)
end
- def execute(type = nil, arg = nil)
- # Please check IRB.init_config in lib/irb/init.rb that sets
- # IRB.conf[:MEASURE_PROC] to register default "measure" methods,
- # "measure :time" (abbreviated as "measure") and "measure :stackprof".
-
- if block_given?
+ def execute(arg)
+ if arg&.match?(/^do$|^do[^\w]|^\{/)
warn 'Configure IRB.conf[:MEASURE_PROC] to add custom measure methods.'
return
end
+ args, kwargs = ruby_args(arg)
+ execute_internal(*args, **kwargs)
+ end
+
+ def execute_internal(type = nil, arg = nil)
+ # Please check IRB.init_config in lib/irb/init.rb that sets
+ # IRB.conf[:MEASURE_PROC] to register default "measure" methods,
+ # "measure :time" (abbreviated as "measure") and "measure :stackprof".
case type
when :off
diff --git a/lib/irb/cmd/next.rb b/lib/irb/command/next.rb
index d29c82e7fc..3fc6b68d21 100644
--- a/lib/irb/cmd/next.rb
+++ b/lib/irb/command/next.rb
@@ -5,10 +5,10 @@ require_relative "debug"
module IRB
# :stopdoc:
- module ExtendCommand
+ module Command
class Next < DebugCommand
- def execute(*args)
- super(do_cmds: ["next", *args].join(" "))
+ def execute(arg)
+ execute_debug_command(do_cmds: "next #{arg}")
end
end
end
diff --git a/lib/irb/command/pushws.rb b/lib/irb/command/pushws.rb
new file mode 100644
index 0000000000..b51928c650
--- /dev/null
+++ b/lib/irb/command/pushws.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+#
+# change-ws.rb -
+# by Keiju ISHITSUKA(keiju@ruby-lang.org)
+#
+
+require_relative "../ext/workspaces"
+
+module IRB
+ # :stopdoc:
+
+ module Command
+ class Workspaces < Base
+ category "Workspace"
+ description "Show workspaces."
+
+ def execute(_arg)
+ inspection_resuls = irb_context.instance_variable_get(:@workspace_stack).map do |ws|
+ truncated_inspect(ws.main)
+ end
+
+ puts "[" + inspection_resuls.join(", ") + "]"
+ end
+
+ private
+
+ def truncated_inspect(obj)
+ obj_inspection = obj.inspect
+
+ if obj_inspection.size > 20
+ obj_inspection = obj_inspection[0, 19] + "...>"
+ end
+
+ obj_inspection
+ end
+ end
+
+ class PushWorkspace < Workspaces
+ category "Workspace"
+ description "Push an object to the workspace stack."
+
+ def execute(arg)
+ if arg.empty?
+ irb_context.push_workspace
+ else
+ obj = eval(arg, irb_context.workspace.binding)
+ irb_context.push_workspace(obj)
+ end
+ super
+ end
+ end
+
+ class PopWorkspace < Workspaces
+ category "Workspace"
+ description "Pop a workspace from the workspace stack."
+
+ def execute(_arg)
+ irb_context.pop_workspace
+ super
+ end
+ end
+ end
+
+ # :startdoc:
+end
diff --git a/lib/irb/command/show_doc.rb b/lib/irb/command/show_doc.rb
new file mode 100644
index 0000000000..8a2188e4eb
--- /dev/null
+++ b/lib/irb/command/show_doc.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+module IRB
+ module Command
+ class ShowDoc < Base
+ include RubyArgsExtractor
+
+ category "Context"
+ description "Look up documentation with RI."
+
+ help_message <<~HELP_MESSAGE
+ Usage: show_doc [name]
+
+ When name is provided, IRB will look up the documentation for the given name.
+ When no name is provided, a RI session will be started.
+
+ Examples:
+
+ show_doc
+ show_doc Array
+ show_doc Array#each
+
+ HELP_MESSAGE
+
+ def execute(arg)
+ # Accept string literal for backward compatibility
+ name = unwrap_string_literal(arg)
+ require 'rdoc/ri/driver'
+
+ unless ShowDoc.const_defined?(:Ri)
+ opts = RDoc::RI::Driver.process_args([])
+ ShowDoc.const_set(:Ri, RDoc::RI::Driver.new(opts))
+ end
+
+ if name.nil?
+ Ri.interactive
+ else
+ begin
+ Ri.display_name(name)
+ rescue RDoc::RI::Error
+ puts $!.message
+ end
+ end
+
+ nil
+ rescue LoadError, SystemExit
+ warn "Can't display document because `rdoc` is not installed."
+ end
+ end
+ end
+end
diff --git a/lib/irb/command/show_source.rb b/lib/irb/command/show_source.rb
new file mode 100644
index 0000000000..f4c6f104a2
--- /dev/null
+++ b/lib/irb/command/show_source.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+require_relative "../source_finder"
+require_relative "../pager"
+require_relative "../color"
+
+module IRB
+ module Command
+ class ShowSource < Base
+ include RubyArgsExtractor
+
+ category "Context"
+ description "Show the source code of a given method, class/module, or constant."
+
+ help_message <<~HELP_MESSAGE
+ Usage: show_source [target] [-s]
+
+ -s Show the super method. You can stack it like `-ss` to show the super of the super, etc.
+
+ Examples:
+
+ show_source Foo
+ show_source Foo#bar
+ show_source Foo#bar -s
+ show_source Foo.baz
+ show_source Foo::BAR
+ HELP_MESSAGE
+
+ def execute(arg)
+ # Accept string literal for backward compatibility
+ str = unwrap_string_literal(arg)
+ unless str.is_a?(String)
+ puts "Error: Expected a string but got #{str.inspect}"
+ return
+ end
+
+ str, esses = str.split(" -")
+ super_level = esses ? esses.count("s") : 0
+ source = SourceFinder.new(@irb_context).find_source(str, super_level)
+
+ if source
+ show_source(source)
+ elsif super_level > 0
+ puts "Error: Couldn't locate a super definition for #{str}"
+ else
+ puts "Error: Couldn't locate a definition for #{str}"
+ end
+ nil
+ end
+
+ private
+
+ def show_source(source)
+ if source.binary_file?
+ content = "\n#{bold('Defined in binary file')}: #{source.file}\n\n"
+ else
+ code = source.colorized_content || 'Source not available'
+ content = <<~CONTENT
+
+ #{bold("From")}: #{source.file}:#{source.line}
+
+ #{code.chomp}
+
+ CONTENT
+ end
+ Pager.page_content(content)
+ end
+
+ def bold(str)
+ Color.colorize(str, [:BOLD])
+ end
+ end
+ end
+end
diff --git a/lib/irb/cmd/step.rb b/lib/irb/command/step.rb
index 2bc74a9d79..29e5e35ac0 100644
--- a/lib/irb/cmd/step.rb
+++ b/lib/irb/command/step.rb
@@ -5,10 +5,10 @@ require_relative "debug"
module IRB
# :stopdoc:
- module ExtendCommand
+ module Command
class Step < DebugCommand
- def execute(*args)
- super(do_cmds: ["step", *args].join(" "))
+ def execute(arg)
+ execute_debug_command(do_cmds: "step #{arg}")
end
end
end
diff --git a/lib/irb/cmd/subirb.rb b/lib/irb/command/subirb.rb
index 5ffd646416..85af28c1a5 100644
--- a/lib/irb/cmd/subirb.rb
+++ b/lib/irb/command/subirb.rb
@@ -1,19 +1,15 @@
-# frozen_string_literal: false
+# frozen_string_literal: true
#
# multi.rb -
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
#
-require_relative "nop"
-
module IRB
# :stopdoc:
- module ExtendCommand
- class MultiIRBCommand < Nop
- def execute(*args)
- extend_irb_context
- end
+ module Command
+ class MultiIRBCommand < Base
+ include RubyArgsExtractor
private
@@ -38,7 +34,12 @@ module IRB
category "Multi-irb (DEPRECATED)"
description "Start a child IRB."
- def execute(*obj)
+ def execute(arg)
+ args, kwargs = ruby_args(arg)
+ execute_internal(*args, **kwargs)
+ end
+
+ def execute_internal(*obj)
print_deprecated_warning
if irb_context.with_debugger
@@ -46,8 +47,9 @@ module IRB
return
end
- super
+ extend_irb_context
IRB.irb(nil, *obj)
+ puts IRB.JobManager.inspect
end
end
@@ -55,7 +57,7 @@ module IRB
category "Multi-irb (DEPRECATED)"
description "List of current sessions."
- def execute
+ def execute(_arg)
print_deprecated_warning
if irb_context.with_debugger
@@ -63,8 +65,8 @@ module IRB
return
end
- super
- IRB.JobManager
+ extend_irb_context
+ puts IRB.JobManager.inspect
end
end
@@ -72,7 +74,12 @@ module IRB
category "Multi-irb (DEPRECATED)"
description "Switches to the session of the given number."
- def execute(key = nil)
+ def execute(arg)
+ args, kwargs = ruby_args(arg)
+ execute_internal(*args, **kwargs)
+ end
+
+ def execute_internal(key = nil)
print_deprecated_warning
if irb_context.with_debugger
@@ -80,10 +87,11 @@ module IRB
return
end
- super
+ extend_irb_context
raise CommandArgumentError.new("Please specify the id of target IRB job (listed in the `jobs` command).") unless key
IRB.JobManager.switch(key)
+ puts IRB.JobManager.inspect
end
end
@@ -91,7 +99,12 @@ module IRB
category "Multi-irb (DEPRECATED)"
description "Kills the session with the given number."
- def execute(*keys)
+ def execute(arg)
+ args, kwargs = ruby_args(arg)
+ execute_internal(*args, **kwargs)
+ end
+
+ def execute_internal(*keys)
print_deprecated_warning
if irb_context.with_debugger
@@ -99,8 +112,9 @@ module IRB
return
end
- super
+ extend_irb_context
IRB.JobManager.kill(*keys)
+ puts IRB.JobManager.inspect
end
end
end
diff --git a/lib/irb/cmd/whereami.rb b/lib/irb/command/whereami.rb
index 8f56ba073d..c8439f1212 100644
--- a/lib/irb/cmd/whereami.rb
+++ b/lib/irb/command/whereami.rb
@@ -1,16 +1,14 @@
# frozen_string_literal: true
-require_relative "nop"
-
module IRB
# :stopdoc:
- module ExtendCommand
- class Whereami < Nop
+ module Command
+ class Whereami < Base
category "Context"
description "Show the source code around binding.irb again."
- def execute(*)
+ def execute(_arg)
code = irb_context.workspace.code_around_binding
if code
puts code
diff --git a/lib/irb/completion.rb b/lib/irb/completion.rb
index af3b69eb27..a3d89373c3 100644
--- a/lib/irb/completion.rb
+++ b/lib/irb/completion.rb
@@ -1,4 +1,4 @@
-# frozen_string_literal: false
+# frozen_string_literal: true
#
# irb/completion.rb -
# by Keiju ISHITSUKA(keiju@ishitsuka.com)
@@ -86,6 +86,14 @@ module IRB
)
end
+ def command_completions(preposing, target)
+ if preposing.empty? && !target.empty?
+ IRB::Command.command_names.select { _1.start_with?(target) }
+ else
+ []
+ end
+ end
+
def retrieve_files_to_require_relative_from_current_dir
@files_from_current_dir ||= Dir.glob("**/*.{rb,#{RbConfig::CONFIG['DLEXT']}}", base: '.').map { |path|
path.sub(/\.(rb|#{RbConfig::CONFIG['DLEXT']})\z/, '')
@@ -103,9 +111,11 @@ module IRB
end
def completion_candidates(preposing, target, _postposing, bind:)
+ commands = command_completions(preposing, target)
result = ReplTypeCompletor.analyze(preposing + target, binding: bind, filename: @context.irb_path)
- return [] unless result
- result.completion_candidates.map { target + _1 }
+ return commands unless result
+
+ commands | result.completion_candidates.map { target + _1 }
end
def doc_namespace(preposing, matched, _postposing, bind:)
@@ -181,7 +191,8 @@ module IRB
result = complete_require_path(target, preposing, postposing)
return result if result
end
- retrieve_completion_data(target, bind: bind, doc_namespace: false).compact.map{ |i| i.encode(Encoding.default_external) }
+ commands = command_completions(preposing || '', target)
+ commands | retrieve_completion_data(target, bind: bind, doc_namespace: false).compact.map{ |i| i.encode(Encoding.default_external) }
end
def doc_namespace(_preposing, matched, _postposing, bind:)
@@ -388,7 +399,7 @@ module IRB
if doc_namespace
rec_class = rec.is_a?(Module) ? rec : rec.class
- "#{rec_class.name}#{sep}#{candidates.find{ |i| i == message }}"
+ "#{rec_class.name}#{sep}#{candidates.find{ |i| i == message }}" rescue nil
else
select_message(receiver, message, candidates, sep)
end
@@ -406,13 +417,19 @@ module IRB
else
select_message(receiver, message, candidates.sort)
end
-
+ when /^\s*$/
+ # empty input
+ if doc_namespace
+ nil
+ else
+ []
+ end
else
if doc_namespace
vars = (bind.local_variables | bind.eval_instance_variables).collect{|m| m.to_s}
perfect_match_var = vars.find{|m| m.to_s == input}
if perfect_match_var
- eval("#{perfect_match_var}.class.name", bind)
+ eval("#{perfect_match_var}.class.name", bind) rescue nil
else
candidates = (bind.eval_methods | bind.eval_private_methods | bind.local_variables | bind.eval_instance_variables | bind.eval_class_constants).collect{|m| m.to_s}
candidates |= ReservedWords
diff --git a/lib/irb/context.rb b/lib/irb/context.rb
index c3690fcac7..5d2ff97328 100644
--- a/lib/irb/context.rb
+++ b/lib/irb/context.rb
@@ -1,4 +1,4 @@
-# frozen_string_literal: false
+# frozen_string_literal: true
#
# irb/context.rb - irb context
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
@@ -22,10 +22,11 @@ module IRB
# +other+:: uses this as InputMethod
def initialize(irb, workspace = nil, input_method = nil)
@irb = irb
+ @workspace_stack = []
if workspace
- @workspace = workspace
+ @workspace_stack << workspace
else
- @workspace = WorkSpace.new
+ @workspace_stack << WorkSpace.new
end
@thread = Thread.current
@@ -61,7 +62,7 @@ module IRB
@io = nil
self.inspect_mode = IRB.conf[:INSPECT_MODE]
- self.use_tracer = IRB.conf[:USE_TRACER] if IRB.conf[:USE_TRACER]
+ self.use_tracer = IRB.conf[:USE_TRACER]
self.use_loader = IRB.conf[:USE_LOADER] if IRB.conf[:USE_LOADER]
self.eval_history = IRB.conf[:EVAL_HISTORY] if IRB.conf[:EVAL_HISTORY]
@@ -77,14 +78,14 @@ module IRB
else
@irb_name = IRB.conf[:IRB_NAME]+"#"+IRB.JobManager.n_jobs.to_s
end
- @irb_path = "(" + @irb_name + ")"
+ self.irb_path = "(" + @irb_name + ")"
case input_method
when nil
@io = nil
case use_multiline?
when nil
- if STDIN.tty? && IRB.conf[:PROMPT_MODE] != :INF_RUBY && !use_singleline?
+ if term_interactive? && IRB.conf[:PROMPT_MODE] != :INF_RUBY && !use_singleline?
# Both of multiline mode and singleline mode aren't specified.
@io = RelineInputMethod.new(build_completor)
else
@@ -98,7 +99,7 @@ module IRB
unless @io
case use_singleline?
when nil
- if (defined?(ReadlineInputMethod) && STDIN.tty? &&
+ if (defined?(ReadlineInputMethod) && term_interactive? &&
IRB.conf[:PROMPT_MODE] != :INF_RUBY)
@io = ReadlineInputMethod.new
else
@@ -121,11 +122,11 @@ module IRB
when '-'
@io = FileInputMethod.new($stdin)
@irb_name = '-'
- @irb_path = '-'
+ self.irb_path = '-'
when String
@io = FileInputMethod.new(input_method)
@irb_name = File.basename(input_method)
- @irb_path = input_method
+ self.irb_path = input_method
else
@io = input_method
end
@@ -150,6 +151,11 @@ module IRB
@command_aliases = @user_aliases.merge(KEYWORD_ALIASES)
end
+ private def term_interactive?
+ return true if ENV['TEST_IRB_FORCE_INTERACTIVE']
+ STDIN.tty? && ENV['TERM'] != 'dumb'
+ end
+
# because all input will eventually be evaluated as Ruby code,
# command names that conflict with Ruby keywords need special workaround
# we can remove them once we implemented a better command system for IRB
@@ -161,6 +167,23 @@ module IRB
private_constant :KEYWORD_ALIASES
+ def use_tracer=(val)
+ require_relative "ext/tracer" if val
+ IRB.conf[:USE_TRACER] = val
+ end
+
+ def eval_history=(val)
+ self.class.remove_method(__method__)
+ require_relative "ext/eval_history"
+ __send__(__method__, val)
+ end
+
+ def use_loader=(val)
+ self.class.remove_method(__method__)
+ require_relative "ext/use-loader"
+ __send__(__method__, val)
+ end
+
private def build_completor
completor_type = IRB.conf[:COMPLETOR]
case completor_type
@@ -178,7 +201,7 @@ module IRB
private def build_type_completor
if RUBY_ENGINE == 'truffleruby'
- # Avoid SynatxError. truffleruby does not support endless method definition yet.
+ # Avoid SyntaxError. truffleruby does not support endless method definition yet.
warn 'TypeCompletor is not supported on TruffleRuby yet'
return
end
@@ -212,15 +235,24 @@ module IRB
IRB.conf[:HISTORY_FILE] = hist
end
+ # Workspace in the current context.
+ def workspace
+ @workspace_stack.last
+ end
+
+ # Replace the current workspace with the given +workspace+.
+ def replace_workspace(workspace)
+ @workspace_stack.pop
+ @workspace_stack.push(workspace)
+ end
+
# The top-level workspace, see WorkSpace#main
def main
- @workspace.main
+ workspace.main
end
# The toplevel workspace, see #home_workspace
attr_reader :workspace_home
- # WorkSpace in the current context.
- attr_accessor :workspace
# The current thread in this context.
attr_reader :thread
# The current input method.
@@ -241,9 +273,27 @@ module IRB
# Can be either name from <code>IRB.conf[:IRB_NAME]</code>, or the number of
# the current job set by JobManager, such as <code>irb#2</code>
attr_accessor :irb_name
- # Can be either the #irb_name surrounded by parenthesis, or the
- # +input_method+ passed to Context.new
- attr_accessor :irb_path
+
+ # Can be one of the following:
+ # - the #irb_name surrounded by parenthesis
+ # - the +input_method+ passed to Context.new
+ # - the file path of the current IRB context in a binding.irb session
+ attr_reader :irb_path
+
+ # Sets @irb_path to the given +path+ as well as @eval_path
+ # @eval_path is used for evaluating code in the context of IRB session
+ # It's the same as irb_path, but with the IRB name postfix
+ # This makes sure users can distinguish the methods defined in the IRB session
+ # from the methods defined in the current file's context, especially with binding.irb
+ def irb_path=(path)
+ @irb_path = path
+
+ if File.exist?(path)
+ @eval_path = "#{path}(#{IRB.conf[:IRB_NAME]})"
+ else
+ @eval_path = path
+ end
+ end
# Whether multiline editor mode is enabled or not.
#
@@ -444,9 +494,7 @@ module IRB
# StdioInputMethod or RelineInputMethod or ReadlineInputMethod, see #io
# for more information.
def prompting?
- verbose? || (STDIN.tty? && @io.kind_of?(StdioInputMethod) ||
- @io.kind_of?(RelineInputMethod) ||
- (defined?(ReadlineInputMethod) && @io.kind_of?(ReadlineInputMethod)))
+ verbose? || @io.prompting?
end
# The return value of the last statement evaluated.
@@ -456,7 +504,7 @@ module IRB
# to #last_value.
def set_last_value(value)
@last_value = value
- @workspace.local_variable_set :_, value
+ workspace.local_variable_set :_, value
end
# Sets the +mode+ of the prompt in this context.
@@ -542,45 +590,55 @@ module IRB
@inspect_mode
end
- def evaluate(line, line_no) # :nodoc:
+ def evaluate(statement, line_no) # :nodoc:
@line_no = line_no
- result = nil
+ case statement
+ when Statement::EmptyInput
+ return
+ when Statement::Expression
+ result = evaluate_expression(statement.code, line_no)
+ set_last_value(result)
+ when Statement::Command
+ statement.command_class.execute(self, statement.arg)
+ set_last_value(nil)
+ end
+
+ nil
+ end
+
+ def from_binding?
+ @irb.from_binding
+ end
+
+ def evaluate_expression(code, line_no) # :nodoc:
+ result = nil
if IRB.conf[:MEASURE] && IRB.conf[:MEASURE_CALLBACKS].empty?
IRB.set_measure_callback
end
if IRB.conf[:MEASURE] && !IRB.conf[:MEASURE_CALLBACKS].empty?
last_proc = proc do
- result = @workspace.evaluate(line, irb_path, line_no)
+ result = workspace.evaluate(code, @eval_path, line_no)
end
IRB.conf[:MEASURE_CALLBACKS].inject(last_proc) do |chain, item|
_name, callback, arg = item
proc do
- callback.(self, line, line_no, arg) do
+ callback.(self, code, line_no, arg) do
chain.call
end
end
end.call
else
- result = @workspace.evaluate(line, irb_path, line_no)
+ result = workspace.evaluate(code, @eval_path, line_no)
end
-
- set_last_value(result)
+ result
end
def inspect_last_value # :nodoc:
@inspect_method.inspect_value(@last_value)
end
- alias __exit__ exit
- # Exits the current session, see IRB.irb_exit
- def exit(ret = 0)
- IRB.irb_exit(@irb, ret)
- rescue UncaughtThrowError
- super
- end
-
NOPRINTING_IVARS = ["@last_value"] # :nodoc:
NO_INSPECTING_IVARS = ["@irb", "@io"] # :nodoc:
IDNAME_IVARS = ["@prompt_mode"] # :nodoc:
@@ -611,17 +669,5 @@ module IRB
def local_variables # :nodoc:
workspace.binding.local_variables
end
-
- # Return true if it's aliased from the argument and it's not an identifier.
- def symbol_alias?(command)
- return nil if command.match?(/\A\w+\z/)
- command_aliases.key?(command.to_sym)
- end
-
- # Return true if the command supports transforming args
- def transform_args?(command)
- command = command_aliases.fetch(command.to_sym, command)
- ExtendCommandBundle.load_command(command)&.respond_to?(:transform_args)
- end
end
end
diff --git a/lib/irb/default_commands.rb b/lib/irb/default_commands.rb
new file mode 100644
index 0000000000..1bbc68efa7
--- /dev/null
+++ b/lib/irb/default_commands.rb
@@ -0,0 +1,260 @@
+# frozen_string_literal: true
+
+require_relative "command"
+require_relative "command/internal_helpers"
+require_relative "command/context"
+require_relative "command/exit"
+require_relative "command/force_exit"
+require_relative "command/chws"
+require_relative "command/pushws"
+require_relative "command/subirb"
+require_relative "command/load"
+require_relative "command/debug"
+require_relative "command/edit"
+require_relative "command/break"
+require_relative "command/catch"
+require_relative "command/next"
+require_relative "command/delete"
+require_relative "command/step"
+require_relative "command/continue"
+require_relative "command/finish"
+require_relative "command/backtrace"
+require_relative "command/info"
+require_relative "command/help"
+require_relative "command/show_doc"
+require_relative "command/irb_info"
+require_relative "command/ls"
+require_relative "command/measure"
+require_relative "command/show_source"
+require_relative "command/whereami"
+require_relative "command/history"
+
+module IRB
+ module Command
+ NO_OVERRIDE = 0
+ OVERRIDE_PRIVATE_ONLY = 0x01
+ OVERRIDE_ALL = 0x02
+
+ class << self
+ # This API is for IRB's internal use only and may change at any time.
+ # Please do NOT use it.
+ def _register_with_aliases(name, command_class, *aliases)
+ @commands[name.to_sym] = [command_class, aliases]
+ end
+
+ def all_commands_info
+ user_aliases = IRB.CurrentContext.command_aliases.each_with_object({}) do |(alias_name, target), result|
+ result[target] ||= []
+ result[target] << alias_name
+ end
+
+ commands.map do |command_name, (command_class, aliases)|
+ aliases = aliases.map { |a| a.first }
+
+ if additional_aliases = user_aliases[command_name]
+ aliases += additional_aliases
+ end
+
+ display_name = aliases.shift || command_name
+ {
+ display_name: display_name,
+ description: command_class.description,
+ category: command_class.category
+ }
+ end
+ end
+
+ def command_override_policies
+ @@command_override_policies ||= commands.flat_map do |cmd_name, (cmd_class, aliases)|
+ [[cmd_name, OVERRIDE_ALL]] + aliases
+ end.to_h
+ end
+
+ def execute_as_command?(name, public_method:, private_method:)
+ case command_override_policies[name]
+ when OVERRIDE_ALL
+ true
+ when OVERRIDE_PRIVATE_ONLY
+ !public_method
+ when NO_OVERRIDE
+ !public_method && !private_method
+ end
+ end
+
+ def command_names
+ command_override_policies.keys.map(&:to_s)
+ end
+
+ # Convert a command name to its implementation class if such command exists
+ def load_command(command)
+ command = command.to_sym
+ commands.each do |command_name, (command_class, aliases)|
+ if command_name == command || aliases.any? { |alias_name, _| alias_name == command }
+ return command_class
+ end
+ end
+ nil
+ end
+ end
+
+ _register_with_aliases(:irb_context, Command::Context,
+ [:context, NO_OVERRIDE]
+ )
+
+ _register_with_aliases(:irb_exit, Command::Exit,
+ [:exit, OVERRIDE_PRIVATE_ONLY],
+ [:quit, OVERRIDE_PRIVATE_ONLY],
+ [:irb_quit, OVERRIDE_PRIVATE_ONLY]
+ )
+
+ _register_with_aliases(:irb_exit!, Command::ForceExit,
+ [:exit!, OVERRIDE_PRIVATE_ONLY]
+ )
+
+ _register_with_aliases(:irb_current_working_workspace, Command::CurrentWorkingWorkspace,
+ [:cwws, NO_OVERRIDE],
+ [:pwws, NO_OVERRIDE],
+ [:irb_print_working_workspace, OVERRIDE_ALL],
+ [:irb_cwws, OVERRIDE_ALL],
+ [:irb_pwws, OVERRIDE_ALL],
+ [:irb_current_working_binding, OVERRIDE_ALL],
+ [:irb_print_working_binding, OVERRIDE_ALL],
+ [:irb_cwb, OVERRIDE_ALL],
+ [:irb_pwb, OVERRIDE_ALL],
+ )
+
+ _register_with_aliases(:irb_change_workspace, Command::ChangeWorkspace,
+ [:chws, NO_OVERRIDE],
+ [:cws, NO_OVERRIDE],
+ [:irb_chws, OVERRIDE_ALL],
+ [:irb_cws, OVERRIDE_ALL],
+ [:irb_change_binding, OVERRIDE_ALL],
+ [:irb_cb, OVERRIDE_ALL],
+ [:cb, NO_OVERRIDE],
+ )
+
+ _register_with_aliases(:irb_workspaces, Command::Workspaces,
+ [:workspaces, NO_OVERRIDE],
+ [:irb_bindings, OVERRIDE_ALL],
+ [:bindings, NO_OVERRIDE],
+ )
+
+ _register_with_aliases(:irb_push_workspace, Command::PushWorkspace,
+ [:pushws, NO_OVERRIDE],
+ [:irb_pushws, OVERRIDE_ALL],
+ [:irb_push_binding, OVERRIDE_ALL],
+ [:irb_pushb, OVERRIDE_ALL],
+ [:pushb, NO_OVERRIDE],
+ )
+
+ _register_with_aliases(:irb_pop_workspace, Command::PopWorkspace,
+ [:popws, NO_OVERRIDE],
+ [:irb_popws, OVERRIDE_ALL],
+ [:irb_pop_binding, OVERRIDE_ALL],
+ [:irb_popb, OVERRIDE_ALL],
+ [:popb, NO_OVERRIDE],
+ )
+
+ _register_with_aliases(:irb_load, Command::Load)
+ _register_with_aliases(:irb_require, Command::Require)
+ _register_with_aliases(:irb_source, Command::Source,
+ [:source, NO_OVERRIDE]
+ )
+
+ _register_with_aliases(:irb, Command::IrbCommand)
+ _register_with_aliases(:irb_jobs, Command::Jobs,
+ [:jobs, NO_OVERRIDE]
+ )
+ _register_with_aliases(:irb_fg, Command::Foreground,
+ [:fg, NO_OVERRIDE]
+ )
+ _register_with_aliases(:irb_kill, Command::Kill,
+ [:kill, OVERRIDE_PRIVATE_ONLY]
+ )
+
+ _register_with_aliases(:irb_debug, Command::Debug,
+ [:debug, NO_OVERRIDE]
+ )
+ _register_with_aliases(:irb_edit, Command::Edit,
+ [:edit, NO_OVERRIDE]
+ )
+
+ _register_with_aliases(:irb_break, Command::Break)
+ _register_with_aliases(:irb_catch, Command::Catch)
+ _register_with_aliases(:irb_next, Command::Next)
+ _register_with_aliases(:irb_delete, Command::Delete,
+ [:delete, NO_OVERRIDE]
+ )
+
+ _register_with_aliases(:irb_step, Command::Step,
+ [:step, NO_OVERRIDE]
+ )
+ _register_with_aliases(:irb_continue, Command::Continue,
+ [:continue, NO_OVERRIDE]
+ )
+ _register_with_aliases(:irb_finish, Command::Finish,
+ [:finish, NO_OVERRIDE]
+ )
+ _register_with_aliases(:irb_backtrace, Command::Backtrace,
+ [:backtrace, NO_OVERRIDE],
+ [:bt, NO_OVERRIDE]
+ )
+
+ _register_with_aliases(:irb_debug_info, Command::Info,
+ [:info, NO_OVERRIDE]
+ )
+
+ _register_with_aliases(:irb_help, Command::Help,
+ [:help, NO_OVERRIDE],
+ [:show_cmds, NO_OVERRIDE]
+ )
+
+ _register_with_aliases(:irb_show_doc, Command::ShowDoc,
+ [:show_doc, NO_OVERRIDE]
+ )
+
+ _register_with_aliases(:irb_info, Command::IrbInfo)
+
+ _register_with_aliases(:irb_ls, Command::Ls,
+ [:ls, NO_OVERRIDE]
+ )
+
+ _register_with_aliases(:irb_measure, Command::Measure,
+ [:measure, NO_OVERRIDE]
+ )
+
+ _register_with_aliases(:irb_show_source, Command::ShowSource,
+ [:show_source, NO_OVERRIDE]
+ )
+
+ _register_with_aliases(:irb_whereami, Command::Whereami,
+ [:whereami, NO_OVERRIDE]
+ )
+
+ _register_with_aliases(:irb_history, Command::History,
+ [:history, NO_OVERRIDE],
+ [:hist, NO_OVERRIDE]
+ )
+ end
+
+ ExtendCommand = Command
+
+ # For backward compatibility, we need to keep this module:
+ # - As a container of helper methods
+ # - As a place to register commands with the deprecated def_extend_command method
+ module ExtendCommandBundle
+ # For backward compatibility
+ NO_OVERRIDE = Command::NO_OVERRIDE
+ OVERRIDE_PRIVATE_ONLY = Command::OVERRIDE_PRIVATE_ONLY
+ OVERRIDE_ALL = Command::OVERRIDE_ALL
+
+ # Deprecated. Doesn't have any effect.
+ @EXTEND_COMMANDS = []
+
+ # Drepcated. Use Command.regiser instead.
+ def self.def_extend_command(cmd_name, cmd_class, _, *aliases)
+ Command._register_with_aliases(cmd_name, cmd_class, *aliases)
+ Command.class_variable_set(:@@command_override_policies, nil)
+ end
+ end
+end
diff --git a/lib/irb/ext/change-ws.rb b/lib/irb/ext/change-ws.rb
index c0f810a4c8..60e8afe31f 100644
--- a/lib/irb/ext/change-ws.rb
+++ b/lib/irb/ext/change-ws.rb
@@ -1,4 +1,4 @@
-# frozen_string_literal: false
+# frozen_string_literal: true
#
# irb/ext/cb.rb -
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
@@ -12,7 +12,7 @@ module IRB # :nodoc:
if defined? @home_workspace
@home_workspace
else
- @home_workspace = @workspace
+ @home_workspace = workspace
end
end
@@ -25,15 +25,13 @@ module IRB # :nodoc:
# See IRB::WorkSpace.new for more information.
def change_workspace(*_main)
if _main.empty?
- @workspace = home_workspace
+ replace_workspace(home_workspace)
return main
end
- @workspace = WorkSpace.new(_main[0])
-
- if !(class<<main;ancestors;end).include?(ExtendCommandBundle)
- main.extend ExtendCommandBundle
- end
+ workspace = WorkSpace.new(_main[0])
+ replace_workspace(workspace)
+ workspace.load_helper_methods_to_main
end
end
end
diff --git a/lib/irb/ext/eval_history.rb b/lib/irb/ext/eval_history.rb
index 1a04178b40..6c21ff00ee 100644
--- a/lib/irb/ext/eval_history.rb
+++ b/lib/irb/ext/eval_history.rb
@@ -1,4 +1,4 @@
-# frozen_string_literal: false
+# frozen_string_literal: true
#
# history.rb -
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
@@ -18,7 +18,7 @@ module IRB # :nodoc:
if defined?(@eval_history) && @eval_history
@eval_history_values.push @line_no, @last_value
- @workspace.evaluate "__ = IRB.CurrentContext.instance_eval{@eval_history_values}"
+ workspace.evaluate "__ = IRB.CurrentContext.instance_eval{@eval_history_values}"
end
@last_value
@@ -49,7 +49,7 @@ module IRB # :nodoc:
else
@eval_history_values = EvalHistory.new(no)
IRB.conf[:__TMP__EHV__] = @eval_history_values
- @workspace.evaluate("__ = IRB.conf[:__TMP__EHV__]")
+ workspace.evaluate("__ = IRB.conf[:__TMP__EHV__]")
IRB.conf.delete(:__TMP_EHV__)
end
else
diff --git a/lib/irb/ext/loader.rb b/lib/irb/ext/loader.rb
index d65695df3b..df5aaa8e5a 100644
--- a/lib/irb/ext/loader.rb
+++ b/lib/irb/ext/loader.rb
@@ -1,4 +1,4 @@
-# frozen_string_literal: false
+# frozen_string_literal: true
#
# loader.rb -
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
@@ -98,13 +98,13 @@ module IRB # :nodoc:
def old # :nodoc:
back_io = @io
- back_path = @irb_path
+ back_path = irb_path
back_name = @irb_name
back_scanner = @irb.scanner
begin
@io = FileInputMethod.new(path)
@irb_name = File.basename(path)
- @irb_path = path
+ self.irb_path = path
@irb.signal_status(:IN_LOAD) do
if back_io.kind_of?(FileInputMethod)
@irb.eval_input
@@ -119,7 +119,7 @@ module IRB # :nodoc:
ensure
@io = back_io
@irb_name = back_name
- @irb_path = back_path
+ self.irb_path = back_path
@irb.scanner = back_scanner
end
end
diff --git a/lib/irb/ext/multi-irb.rb b/lib/irb/ext/multi-irb.rb
index 1c20489137..9f234f0cdc 100644
--- a/lib/irb/ext/multi-irb.rb
+++ b/lib/irb/ext/multi-irb.rb
@@ -1,11 +1,11 @@
-# frozen_string_literal: false
+# frozen_string_literal: true
#
# irb/multi-irb.rb - multiple irb module
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
#
module IRB
- class JobManager
+ class JobManager # :nodoc:
# Creates a new JobManager object
def initialize
@@ -166,12 +166,12 @@ module IRB
@JobManager = JobManager.new
# The current JobManager in the session
- def IRB.JobManager
+ def IRB.JobManager # :nodoc:
@JobManager
end
# The current Context in this session
- def IRB.CurrentContext
+ def IRB.CurrentContext # :nodoc:
IRB.JobManager.irb(Thread.current).context
end
@@ -179,7 +179,7 @@ module IRB
#
# The optional +file+ argument is given to Context.new, along with the
# workspace created with the remaining arguments, see WorkSpace.new
- def IRB.irb(file = nil, *main)
+ def IRB.irb(file = nil, *main) # :nodoc:
workspace = WorkSpace.new(*main)
parent_thread = Thread.current
Thread.start do
diff --git a/lib/irb/ext/tracer.rb b/lib/irb/ext/tracer.rb
index 3eaeb70ef2..fd6daa88ae 100644
--- a/lib/irb/ext/tracer.rb
+++ b/lib/irb/ext/tracer.rb
@@ -1,78 +1,39 @@
-# frozen_string_literal: false
+# frozen_string_literal: true
#
# irb/lib/tracer.rb -
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
#
-
+# Loading the gem "tracer" will cause it to extend IRB commands with:
+# https://github.com/ruby/tracer/blob/v0.2.2/lib/tracer/irb.rb
begin
require "tracer"
rescue LoadError
$stderr.puts "Tracer extension of IRB is enabled but tracer gem wasn't found."
- module IRB
- class Context
- def use_tracer=(opt)
- # do nothing
- end
- end
- end
return # This is about to disable loading below
end
module IRB
+ class CallTracer < ::CallTracer
+ IRB_DIR = File.expand_path('../..', __dir__)
- # initialize tracing function
- def IRB.initialize_tracer
- Tracer.verbose = false
- Tracer.add_filter {
- |event, file, line, id, binding, *rests|
- /^#{Regexp.quote(@CONF[:IRB_LIB_PATH])}/ !~ file and
- File::basename(file) != "irb.rb"
- }
- end
-
- class Context
- # Whether Tracer is used when evaluating statements in this context.
- #
- # See +lib/tracer.rb+ for more information.
- attr_reader :use_tracer
- alias use_tracer? use_tracer
-
- # Sets whether or not to use the Tracer library when evaluating statements
- # in this context.
- #
- # See +lib/tracer.rb+ for more information.
- def use_tracer=(opt)
- if opt
- Tracer.set_get_line_procs(@irb_path) {
- |line_no, *rests|
- @io.line(line_no)
- }
- elsif !opt && @use_tracer
- Tracer.off
- end
- @use_tracer=opt
+ def skip?(tp)
+ super || tp.path.match?(IRB_DIR) || tp.path.match?('<internal:prelude>')
end
end
-
class WorkSpace
alias __evaluate__ evaluate
# Evaluate the context of this workspace and use the Tracer library to
# output the exact lines of code are being executed in chronological order.
#
- # See +lib/tracer.rb+ for more information.
- def evaluate(context, statements, file = nil, line = nil)
- if context.use_tracer? && file != nil && line != nil
- Tracer.on
- begin
+ # See https://github.com/ruby/tracer for more information.
+ def evaluate(statements, file = __FILE__, line = __LINE__)
+ if IRB.conf[:USE_TRACER] == true
+ CallTracer.new(colorize: Color.colorable?).start do
__evaluate__(statements, file, line)
- ensure
- Tracer.off
end
else
- __evaluate__(statements, file || __FILE__, line || __LINE__)
+ __evaluate__(statements, file, line)
end
end
end
-
- IRB.initialize_tracer
end
diff --git a/lib/irb/ext/use-loader.rb b/lib/irb/ext/use-loader.rb
index d0b8c2d4f4..c8a3ea1fe8 100644
--- a/lib/irb/ext/use-loader.rb
+++ b/lib/irb/ext/use-loader.rb
@@ -1,10 +1,10 @@
-# frozen_string_literal: false
+# frozen_string_literal: true
#
# use-loader.rb -
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
#
-require_relative "../cmd/load"
+require_relative "../command/load"
require_relative "loader"
class Object
@@ -17,12 +17,12 @@ module IRB
remove_method :irb_load if method_defined?(:irb_load)
# Loads the given file similarly to Kernel#load, see IrbLoader#irb_load
def irb_load(*opts, &b)
- ExtendCommand::Load.execute(irb_context, *opts, &b)
+ Command::Load.execute(irb_context, *opts, &b)
end
remove_method :irb_require if method_defined?(:irb_require)
# Loads the given file similarly to Kernel#require
def irb_require(*opts, &b)
- ExtendCommand::Require.execute(irb_context, *opts, &b)
+ Command::Require.execute(irb_context, *opts, &b)
end
end
@@ -49,14 +49,12 @@ module IRB
if IRB.conf[:USE_LOADER] != opt
IRB.conf[:USE_LOADER] = opt
if opt
- if !$".include?("irb/cmd/load")
- end
- (class<<@workspace.main;self;end).instance_eval {
+ (class<<workspace.main;self;end).instance_eval {
alias_method :load, :irb_load
alias_method :require, :irb_require
}
else
- (class<<@workspace.main;self;end).instance_eval {
+ (class<<workspace.main;self;end).instance_eval {
alias_method :load, :__original__load__IRB_use_loader__
alias_method :require, :__original__require__IRB_use_loader__
}
diff --git a/lib/irb/ext/workspaces.rb b/lib/irb/ext/workspaces.rb
index 9defc3e17b..da09faa83e 100644
--- a/lib/irb/ext/workspaces.rb
+++ b/lib/irb/ext/workspaces.rb
@@ -1,4 +1,4 @@
-# frozen_string_literal: false
+# frozen_string_literal: true
#
# push-ws.rb -
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
@@ -6,21 +6,6 @@
module IRB # :nodoc:
class Context
-
- # Size of the current WorkSpace stack
- def irb_level
- workspace_stack.size
- end
-
- # WorkSpaces in the current stack
- def workspaces
- if defined? @workspaces
- @workspaces
- else
- @workspaces = []
- end
- end
-
# Creates a new workspace with the given object or binding, and appends it
# onto the current #workspaces stack.
#
@@ -28,20 +13,15 @@ module IRB # :nodoc:
# information.
def push_workspace(*_main)
if _main.empty?
- if workspaces.empty?
- print "No other workspace\n"
- return nil
+ if @workspace_stack.size > 1
+ # swap the top two workspaces
+ previous_workspace, current_workspace = @workspace_stack.pop(2)
+ @workspace_stack.push current_workspace, previous_workspace
end
- ws = workspaces.pop
- workspaces.push @workspace
- @workspace = ws
- return workspaces
- end
-
- workspaces.push @workspace
- @workspace = WorkSpace.new(@workspace.binding, _main[0])
- if !(class<<main;ancestors;end).include?(ExtendCommandBundle)
- main.extend ExtendCommandBundle
+ else
+ new_workspace = WorkSpace.new(workspace.binding, _main[0])
+ @workspace_stack.push new_workspace
+ new_workspace.load_helper_methods_to_main
end
end
@@ -50,11 +30,7 @@ module IRB # :nodoc:
#
# Also, see #push_workspace.
def pop_workspace
- if workspaces.empty?
- print "workspace stack empty\n"
- return
- end
- @workspace = workspaces.pop
+ @workspace_stack.pop if @workspace_stack.size > 1
end
end
end
diff --git a/lib/irb/extend-command.rb b/lib/irb/extend-command.rb
deleted file mode 100644
index 072069d4c4..0000000000
--- a/lib/irb/extend-command.rb
+++ /dev/null
@@ -1,360 +0,0 @@
-# frozen_string_literal: false
-#
-# irb/extend-command.rb - irb extend command
-# by Keiju ISHITSUKA(keiju@ruby-lang.org)
-#
-
-module IRB # :nodoc:
- # Installs the default irb extensions command bundle.
- module ExtendCommandBundle
- EXCB = ExtendCommandBundle # :nodoc:
-
- # See #install_alias_method.
- NO_OVERRIDE = 0
- # See #install_alias_method.
- OVERRIDE_PRIVATE_ONLY = 0x01
- # See #install_alias_method.
- OVERRIDE_ALL = 0x02
-
- # Quits the current irb context
- #
- # +ret+ is the optional signal or message to send to Context#exit
- #
- # Same as <code>IRB.CurrentContext.exit</code>.
- def irb_exit(ret = 0)
- irb_context.exit(ret)
- end
-
- # Displays current configuration.
- #
- # Modifying the configuration is achieved by sending a message to IRB.conf.
- def irb_context
- IRB.CurrentContext
- end
-
- @ALIASES = [
- [:context, :irb_context, NO_OVERRIDE],
- [:conf, :irb_context, NO_OVERRIDE],
- [:irb_quit, :irb_exit, OVERRIDE_PRIVATE_ONLY],
- [:exit, :irb_exit, OVERRIDE_PRIVATE_ONLY],
- [:quit, :irb_exit, OVERRIDE_PRIVATE_ONLY],
- ]
-
-
- @EXTEND_COMMANDS = [
- [
- :irb_current_working_workspace, :CurrentWorkingWorkspace, "cmd/chws",
- [:cwws, NO_OVERRIDE],
- [:pwws, NO_OVERRIDE],
- [:irb_print_working_workspace, OVERRIDE_ALL],
- [:irb_cwws, OVERRIDE_ALL],
- [:irb_pwws, OVERRIDE_ALL],
- [:irb_current_working_binding, OVERRIDE_ALL],
- [:irb_print_working_binding, OVERRIDE_ALL],
- [:irb_cwb, OVERRIDE_ALL],
- [:irb_pwb, OVERRIDE_ALL],
- ],
- [
- :irb_change_workspace, :ChangeWorkspace, "cmd/chws",
- [:chws, NO_OVERRIDE],
- [:cws, NO_OVERRIDE],
- [:irb_chws, OVERRIDE_ALL],
- [:irb_cws, OVERRIDE_ALL],
- [:irb_change_binding, OVERRIDE_ALL],
- [:irb_cb, OVERRIDE_ALL],
- [:cb, NO_OVERRIDE],
- ],
-
- [
- :irb_workspaces, :Workspaces, "cmd/pushws",
- [:workspaces, NO_OVERRIDE],
- [:irb_bindings, OVERRIDE_ALL],
- [:bindings, NO_OVERRIDE],
- ],
- [
- :irb_push_workspace, :PushWorkspace, "cmd/pushws",
- [:pushws, NO_OVERRIDE],
- [:irb_pushws, OVERRIDE_ALL],
- [:irb_push_binding, OVERRIDE_ALL],
- [:irb_pushb, OVERRIDE_ALL],
- [:pushb, NO_OVERRIDE],
- ],
- [
- :irb_pop_workspace, :PopWorkspace, "cmd/pushws",
- [:popws, NO_OVERRIDE],
- [:irb_popws, OVERRIDE_ALL],
- [:irb_pop_binding, OVERRIDE_ALL],
- [:irb_popb, OVERRIDE_ALL],
- [:popb, NO_OVERRIDE],
- ],
-
- [
- :irb_load, :Load, "cmd/load"],
- [
- :irb_require, :Require, "cmd/load"],
- [
- :irb_source, :Source, "cmd/load",
- [:source, NO_OVERRIDE],
- ],
-
- [
- :irb, :IrbCommand, "cmd/subirb"],
- [
- :irb_jobs, :Jobs, "cmd/subirb",
- [:jobs, NO_OVERRIDE],
- ],
- [
- :irb_fg, :Foreground, "cmd/subirb",
- [:fg, NO_OVERRIDE],
- ],
- [
- :irb_kill, :Kill, "cmd/subirb",
- [:kill, OVERRIDE_PRIVATE_ONLY],
- ],
-
- [
- :irb_debug, :Debug, "cmd/debug",
- [:debug, NO_OVERRIDE],
- ],
- [
- :irb_edit, :Edit, "cmd/edit",
- [:edit, NO_OVERRIDE],
- ],
- [
- :irb_break, :Break, "cmd/break",
- ],
- [
- :irb_catch, :Catch, "cmd/catch",
- ],
- [
- :irb_next, :Next, "cmd/next"
- ],
- [
- :irb_delete, :Delete, "cmd/delete",
- [:delete, NO_OVERRIDE],
- ],
- [
- :irb_step, :Step, "cmd/step",
- [:step, NO_OVERRIDE],
- ],
- [
- :irb_continue, :Continue, "cmd/continue",
- [:continue, NO_OVERRIDE],
- ],
- [
- :irb_finish, :Finish, "cmd/finish",
- [:finish, NO_OVERRIDE],
- ],
- [
- :irb_backtrace, :Backtrace, "cmd/backtrace",
- [:backtrace, NO_OVERRIDE],
- [:bt, NO_OVERRIDE],
- ],
- [
- :irb_debug_info, :Info, "cmd/info",
- [:info, NO_OVERRIDE],
- ],
-
- [
- :irb_help, :Help, "cmd/help",
- [:help, NO_OVERRIDE],
- ],
-
- [
- :irb_show_doc, :ShowDoc, "cmd/show_doc",
- [:show_doc, NO_OVERRIDE],
- ],
-
- [
- :irb_info, :IrbInfo, "cmd/irb_info"
- ],
-
- [
- :irb_ls, :Ls, "cmd/ls",
- [:ls, NO_OVERRIDE],
- ],
-
- [
- :irb_measure, :Measure, "cmd/measure",
- [:measure, NO_OVERRIDE],
- ],
-
- [
- :irb_show_source, :ShowSource, "cmd/show_source",
- [:show_source, NO_OVERRIDE],
- ],
-
- [
- :irb_whereami, :Whereami, "cmd/whereami",
- [:whereami, NO_OVERRIDE],
- ],
- [
- :irb_show_cmds, :ShowCmds, "cmd/show_cmds",
- [:show_cmds, NO_OVERRIDE],
- ],
-
- [
- :irb_history, :History, "cmd/history",
- [:history, NO_OVERRIDE],
- [:hist, NO_OVERRIDE],
- ]
- ]
-
-
- @@commands = []
-
- def self.all_commands_info
- return @@commands unless @@commands.empty?
- user_aliases = IRB.CurrentContext.command_aliases.each_with_object({}) do |(alias_name, target), result|
- result[target] ||= []
- result[target] << alias_name
- end
-
- @EXTEND_COMMANDS.each do |cmd_name, cmd_class, load_file, *aliases|
- if !defined?(ExtendCommand) || !ExtendCommand.const_defined?(cmd_class, false)
- require_relative load_file
- end
-
- klass = ExtendCommand.const_get(cmd_class, false)
- aliases = aliases.map { |a| a.first }
-
- if additional_aliases = user_aliases[cmd_name]
- aliases += additional_aliases
- end
-
- display_name = aliases.shift || cmd_name
- @@commands << { display_name: display_name, description: klass.description, category: klass.category }
- end
-
- @@commands
- end
-
- # Convert a command name to its implementation class if such command exists
- def self.load_command(command)
- command = command.to_sym
- @EXTEND_COMMANDS.each do |cmd_name, cmd_class, load_file, *aliases|
- next if cmd_name != command && aliases.all? { |alias_name, _| alias_name != command }
-
- if !defined?(ExtendCommand) || !ExtendCommand.const_defined?(cmd_class, false)
- require_relative load_file
- end
- return ExtendCommand.const_get(cmd_class, false)
- end
- nil
- end
-
- # Installs the default irb commands.
- def self.install_extend_commands
- for args in @EXTEND_COMMANDS
- def_extend_command(*args)
- end
- end
-
- # Evaluate the given +cmd_name+ on the given +cmd_class+ Class.
- #
- # Will also define any given +aliases+ for the method.
- #
- # The optional +load_file+ parameter will be required within the method
- # definition.
- def self.def_extend_command(cmd_name, cmd_class, load_file, *aliases)
- case cmd_class
- when Symbol
- cmd_class = cmd_class.id2name
- when String
- when Class
- cmd_class = cmd_class.name
- end
-
- line = __LINE__; eval %[
- def #{cmd_name}(*opts, **kwargs, &b)
- Kernel.require_relative "#{load_file}"
- ::IRB::ExtendCommand::#{cmd_class}.execute(irb_context, *opts, **kwargs, &b)
- end
- ], nil, __FILE__, line
-
- for ali, flag in aliases
- @ALIASES.push [ali, cmd_name, flag]
- end
- end
-
- # Installs alias methods for the default irb commands, see
- # ::install_extend_commands.
- def install_alias_method(to, from, override = NO_OVERRIDE)
- to = to.id2name unless to.kind_of?(String)
- from = from.id2name unless from.kind_of?(String)
-
- if override == OVERRIDE_ALL or
- (override == OVERRIDE_PRIVATE_ONLY) && !respond_to?(to) or
- (override == NO_OVERRIDE) && !respond_to?(to, true)
- target = self
- (class << self; self; end).instance_eval{
- if target.respond_to?(to, true) &&
- !target.respond_to?(EXCB.irb_original_method_name(to), true)
- alias_method(EXCB.irb_original_method_name(to), to)
- end
- alias_method to, from
- }
- else
- Kernel.warn "irb: warn: can't alias #{to} from #{from}.\n"
- end
- end
-
- def self.irb_original_method_name(method_name) # :nodoc:
- "irb_" + method_name + "_org"
- end
-
- # Installs alias methods for the default irb commands on the given object
- # using #install_alias_method.
- def self.extend_object(obj)
- unless (class << obj; ancestors; end).include?(EXCB)
- super
- for ali, com, flg in @ALIASES
- obj.install_alias_method(ali, com, flg)
- end
- end
- end
-
- install_extend_commands
- end
-
- # Extends methods for the Context module
- module ContextExtender
- CE = ContextExtender # :nodoc:
-
- @EXTEND_COMMANDS = [
- [:eval_history=, "ext/eval_history.rb"],
- [:use_tracer=, "ext/tracer.rb"],
- [:use_loader=, "ext/use-loader.rb"],
- ]
-
- # Installs the default context extensions as irb commands:
- #
- # Context#eval_history=:: +irb/ext/history.rb+
- # Context#use_tracer=:: +irb/ext/tracer.rb+
- # Context#use_loader=:: +irb/ext/use-loader.rb+
- def self.install_extend_commands
- for args in @EXTEND_COMMANDS
- def_extend_command(*args)
- end
- end
-
- # Evaluate the given +command+ from the given +load_file+ on the Context
- # module.
- #
- # Will also define any given +aliases+ for the method.
- def self.def_extend_command(cmd_name, load_file, *aliases)
- line = __LINE__; Context.module_eval %[
- def #{cmd_name}(*opts, &b)
- Context.module_eval {remove_method(:#{cmd_name})}
- require_relative "#{load_file}"
- __send__ :#{cmd_name}, *opts, &b
- end
- for ali in aliases
- alias_method ali, cmd_name
- end
- ], __FILE__, line
- end
-
- CE.install_extend_commands
- end
-end
diff --git a/lib/irb/frame.rb b/lib/irb/frame.rb
index 14768bd8f6..4b697c8719 100644
--- a/lib/irb/frame.rb
+++ b/lib/irb/frame.rb
@@ -1,4 +1,4 @@
-# frozen_string_literal: false
+# frozen_string_literal: true
#
# frame.rb -
# by Keiju ISHITSUKA(Nihon Rational Software Co.,Ltd)
diff --git a/lib/irb/help.rb b/lib/irb/help.rb
index 6861d7efc8..a24bc10a15 100644
--- a/lib/irb/help.rb
+++ b/lib/irb/help.rb
@@ -1,4 +1,4 @@
-# frozen_string_literal: false
+# frozen_string_literal: true
#
# irb/help.rb - print usage module
# by Keiju ISHITSUKA(keiju@ishitsuka.com)
@@ -6,7 +6,7 @@
module IRB
# Outputs the irb help message, see IRB@Command-Line+Options.
- def IRB.print_usage
+ def IRB.print_usage # :nodoc:
lc = IRB.conf[:LC_MESSAGES]
path = lc.find("irb/help-message")
space_line = false
diff --git a/lib/irb/helper_method.rb b/lib/irb/helper_method.rb
new file mode 100644
index 0000000000..f1f6fff915
--- /dev/null
+++ b/lib/irb/helper_method.rb
@@ -0,0 +1,29 @@
+require_relative "helper_method/base"
+
+module IRB
+ module HelperMethod
+ @helper_methods = {}
+
+ class << self
+ attr_reader :helper_methods
+
+ def register(name, helper_class)
+ @helper_methods[name] = helper_class
+
+ if defined?(HelpersContainer)
+ HelpersContainer.install_helper_methods
+ end
+ end
+
+ def all_helper_methods_info
+ @helper_methods.map do |name, helper_class|
+ { display_name: name, description: helper_class.description }
+ end
+ end
+ end
+
+ # Default helper_methods
+ require_relative "helper_method/conf"
+ register(:conf, HelperMethod::Conf)
+ end
+end
diff --git a/lib/irb/helper_method/base.rb b/lib/irb/helper_method/base.rb
new file mode 100644
index 0000000000..a68001ed28
--- /dev/null
+++ b/lib/irb/helper_method/base.rb
@@ -0,0 +1,16 @@
+require "singleton"
+
+module IRB
+ module HelperMethod
+ class Base
+ include Singleton
+
+ class << self
+ def description(description = nil)
+ @description = description if description
+ @description
+ end
+ end
+ end
+ end
+end
diff --git a/lib/irb/helper_method/conf.rb b/lib/irb/helper_method/conf.rb
new file mode 100644
index 0000000000..718ed279c0
--- /dev/null
+++ b/lib/irb/helper_method/conf.rb
@@ -0,0 +1,11 @@
+module IRB
+ module HelperMethod
+ class Conf < Base
+ description "Returns the current IRB context."
+
+ def execute
+ IRB.CurrentContext
+ end
+ end
+ end
+end
diff --git a/lib/irb/history.rb b/lib/irb/history.rb
index 06088adb0d..685354b2d8 100644
--- a/lib/irb/history.rb
+++ b/lib/irb/history.rb
@@ -1,3 +1,5 @@
+require "pathname"
+
module IRB
module HistorySavingAbility # :nodoc:
def support_history_saving?
@@ -5,7 +7,7 @@ module IRB
end
def reset_history_counter
- @loaded_history_lines = self.class::HISTORY.size if defined? @loaded_history_lines
+ @loaded_history_lines = self.class::HISTORY.size
end
def load_history
@@ -15,7 +17,7 @@ module IRB
history_file = File.expand_path(history_file)
end
history_file = IRB.rc_file("_history") unless history_file
- if File.exist?(history_file)
+ if history_file && File.exist?(history_file)
File.open(history_file, "r:#{IRB.conf[:LC_MESSAGES].encoding}") do |f|
f.each { |l|
l = l.chomp
@@ -41,6 +43,9 @@ module IRB
end
history_file = IRB.rc_file("_history") unless history_file
+ # When HOME and XDG_CONFIG_HOME are not available, history_file might be nil
+ return unless history_file
+
# Change the permission of a file that already exists[BUG #7694]
begin
if File.stat(history_file).mode & 066 != 0
@@ -59,13 +64,19 @@ module IRB
append_history = true
end
+ pathname = Pathname.new(history_file)
+ unless Dir.exist?(pathname.dirname)
+ warn "Warning: The directory to save IRB's history file does not exist. Please double check `IRB.conf[:HISTORY_FILE]`'s value."
+ return
+ end
+
File.open(history_file, (append_history ? 'a' : 'w'), 0o600, encoding: IRB.conf[:LC_MESSAGES]&.encoding) do |f|
hist = history.map{ |l| l.scrub.split("\n").join("\\\n") }
unless append_history
begin
hist = hist.last(num) if hist.size > num and num > 0
rescue RangeError # bignum too big to convert into `long'
- # Do nothing because the bignum should be treated as inifinity
+ # Do nothing because the bignum should be treated as infinity
end
end
f.puts(hist)
diff --git a/lib/irb/init.rb b/lib/irb/init.rb
index 66e7b61468..355047519c 100644
--- a/lib/irb/init.rb
+++ b/lib/irb/init.rb
@@ -1,4 +1,4 @@
-# frozen_string_literal: false
+# frozen_string_literal: true
#
# irb/init.rb - irb initialize module
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
@@ -6,6 +6,7 @@
module IRB # :nodoc:
@CONF = {}
+ @INITIALIZED = false
# Displays current configuration.
#
# Modifying the configuration is achieved by sending a message to IRB.conf.
@@ -41,6 +42,10 @@ module IRB # :nodoc:
format("irb %s (%s)", @RELEASE_VERSION, @LAST_UPDATE_DATE)
end
+ def IRB.initialized?
+ !!@INITIALIZED
+ end
+
# initialize config
def IRB.setup(ap_path, argv: ::ARGV)
IRB.init_config(ap_path)
@@ -52,13 +57,11 @@ module IRB # :nodoc:
unless @CONF[:PROMPT][@CONF[:PROMPT_MODE]]
fail UndefinedPromptMode, @CONF[:PROMPT_MODE]
end
+ @INITIALIZED = true
end
# @CONF default setting
def IRB.init_config(ap_path)
- # class instance variables
- @TRACER_INITIALIZED = false
-
# default configurations
unless ap_path and @CONF[:AP_NAME]
ap_path = File.join(File.dirname(File.dirname(__FILE__)), "irb.rb")
@@ -392,56 +395,36 @@ module IRB # :nodoc:
# Run the config file
def IRB.run_config
if @CONF[:RC]
- begin
- file = rc_file
- # Because rc_file always returns `HOME/.irbrc` even if no rc file is present, we can't warn users about missing rc files.
- # Otherwise, it'd be very noisy.
- load file if File.exist?(file)
+ irbrc_files.each do |rc|
+ load rc
rescue StandardError, ScriptError => e
- warn "Error loading RC file '#{file}':\n#{e.full_message(highlight: false)}"
+ warn "Error loading RC file '#{rc}':\n#{e.full_message(highlight: false)}"
end
end
end
IRBRC_EXT = "rc"
- def IRB.rc_file(ext = IRBRC_EXT)
- if !@CONF[:RC_NAME_GENERATOR]
- rc_file_generators do |rcgen|
- @CONF[:RC_NAME_GENERATOR] ||= rcgen
- if File.exist?(rcgen.call(IRBRC_EXT))
- @CONF[:RC_NAME_GENERATOR] = rcgen
- break
- end
- end
+
+ def IRB.rc_file(ext)
+ prepare_irbrc_name_generators
+
+ # When irbrc exist in default location
+ if (rcgen = @existing_rc_name_generators.first)
+ return rcgen.call(ext)
end
- case rc_file = @CONF[:RC_NAME_GENERATOR].call(ext)
- when String
- rc_file
- else
- fail IllegalRCNameGenerator
+
+ # When irbrc does not exist in default location
+ rc_file_generators do |rcgen|
+ return rcgen.call(ext)
end
+
+ # When HOME and XDG_CONFIG_HOME are not available
+ nil
end
- # enumerate possible rc-file base name generators
- def IRB.rc_file_generators
- if irbrc = ENV["IRBRC"]
- yield proc{|rc| rc == "rc" ? irbrc : irbrc+rc}
- end
- if xdg_config_home = ENV["XDG_CONFIG_HOME"]
- irb_home = File.join(xdg_config_home, "irb")
- if File.directory?(irb_home)
- yield proc{|rc| irb_home + "/irb#{rc}"}
- end
- end
- if home = ENV["HOME"]
- yield proc{|rc| home+"/.irb#{rc}"}
- yield proc{|rc| home+"/.config/irb/irb#{rc}"}
- end
- current_dir = Dir.pwd
- yield proc{|rc| current_dir+"/.irb#{rc}"}
- yield proc{|rc| current_dir+"/irb#{rc.sub(/\A_?/, '.')}"}
- yield proc{|rc| current_dir+"/_irb#{rc}"}
- yield proc{|rc| current_dir+"/$irb#{rc}"}
+ def IRB.irbrc_files
+ prepare_irbrc_name_generators
+ @irbrc_files
end
# loading modules
@@ -457,6 +440,50 @@ module IRB # :nodoc:
class << IRB
private
+
+ def prepare_irbrc_name_generators
+ return if @existing_rc_name_generators
+
+ @existing_rc_name_generators = []
+ @irbrc_files = []
+ rc_file_generators do |rcgen|
+ irbrc = rcgen.call(IRBRC_EXT)
+ if File.exist?(irbrc)
+ @irbrc_files << irbrc
+ @existing_rc_name_generators << rcgen
+ end
+ end
+ generate_current_dir_irbrc_files.each do |irbrc|
+ @irbrc_files << irbrc if File.exist?(irbrc)
+ end
+ @irbrc_files.uniq!
+ end
+
+ # enumerate possible rc-file base name generators
+ def rc_file_generators
+ if irbrc = ENV["IRBRC"]
+ yield proc{|rc| rc == "rc" ? irbrc : irbrc+rc}
+ end
+ if xdg_config_home = ENV["XDG_CONFIG_HOME"]
+ irb_home = File.join(xdg_config_home, "irb")
+ if File.directory?(irb_home)
+ yield proc{|rc| irb_home + "/irb#{rc}"}
+ end
+ end
+ if home = ENV["HOME"]
+ yield proc{|rc| home+"/.irb#{rc}"}
+ if xdg_config_home.nil? || xdg_config_home.empty?
+ yield proc{|rc| home+"/.config/irb/irb#{rc}"}
+ end
+ end
+ end
+
+ # possible irbrc files in current directory
+ def generate_current_dir_irbrc_files
+ current_dir = Dir.pwd
+ %w[.irbrc irbrc _irbrc $irbrc].map { |file| "#{current_dir}/#{file}" }
+ end
+
def set_encoding(extern, intern = nil, override: true)
verbose, $VERBOSE = $VERBOSE, nil
Encoding.default_external = extern unless extern.nil? || extern.empty?
diff --git a/lib/irb/input-method.rb b/lib/irb/input-method.rb
index b74974b925..684527edc4 100644
--- a/lib/irb/input-method.rb
+++ b/lib/irb/input-method.rb
@@ -1,4 +1,4 @@
-# frozen_string_literal: false
+# frozen_string_literal: true
#
# irb/input-method.rb - input methods used irb
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
@@ -20,7 +20,7 @@ module IRB
#
# See IO#gets for more information.
def gets
- fail NotImplementedError, "gets"
+ fail NotImplementedError
end
public :gets
@@ -44,6 +44,10 @@ module IRB
false
end
+ def prompting?
+ false
+ end
+
# For debug message
def inspect
'Abstract InputMethod'
@@ -63,6 +67,7 @@ module IRB
#
# See IO#gets for more information.
def gets
+ puts if @stdout.tty? # workaround for debug compatibility test
print @prompt
line = @stdin.gets
@line[@line_no += 1] = line
@@ -91,6 +96,10 @@ module IRB
true
end
+ def prompting?
+ STDIN.tty?
+ end
+
# Returns the current line number for #io.
#
# #line counts the number of times #gets is called.
@@ -220,6 +229,10 @@ module IRB
@eof
end
+ def prompting?
+ true
+ end
+
# For debug message
def inspect
readline_impl = (defined?(Reline) && Readline == Reline) ? 'Reline' : 'ext/readline'
@@ -291,11 +304,27 @@ module IRB
@auto_indent_proc = block
end
+ def retrieve_doc_namespace(matched)
+ preposing, _target, postposing, bind = @completion_params
+ @completor.doc_namespace(preposing, matched, postposing, bind: bind)
+ end
+
+ def rdoc_ri_driver
+ return @rdoc_ri_driver if defined?(@rdoc_ri_driver)
+
+ begin
+ require 'rdoc'
+ rescue LoadError
+ @rdoc_ri_driver = nil
+ else
+ options = {}
+ options[:extra_doc_dirs] = IRB.conf[:EXTRA_DOC_DIRS] unless IRB.conf[:EXTRA_DOC_DIRS].empty?
+ @rdoc_ri_driver = RDoc::RI::Driver.new(options)
+ end
+ end
+
def show_doc_dialog_proc
- doc_namespace = ->(matched) {
- preposing, _target, postposing, bind = @completion_params
- @completor.doc_namespace(preposing, matched, postposing, bind: bind)
- }
+ input_method = self # self is changed in the lambda below.
->() {
dialog.trap_key = nil
alt_d = [
@@ -311,15 +340,13 @@ module IRB
cursor_pos_to_render, result, pointer, autocomplete_dialog = context.pop(4)
return nil if result.nil? or pointer.nil? or pointer < 0
- name = doc_namespace.call(result[pointer])
+ name = input_method.retrieve_doc_namespace(result[pointer])
# Use first one because document dialog does not support multiple namespaces.
name = name.first if name.is_a?(Array)
show_easter_egg = name&.match?(/\ARubyVM/) && !ENV['RUBY_YES_I_AM_NOT_A_NORMAL_USER']
- options = {}
- options[:extra_doc_dirs] = IRB.conf[:EXTRA_DOC_DIRS] unless IRB.conf[:EXTRA_DOC_DIRS].empty?
- driver = RDoc::RI::Driver.new(options)
+ driver = input_method.rdoc_ri_driver
if key.match?(dialog.name)
if show_easter_egg
@@ -407,23 +434,18 @@ module IRB
}
end
- def display_document(matched, driver: nil)
- begin
- require 'rdoc'
- rescue LoadError
- return
- end
+ def display_document(matched)
+ driver = rdoc_ri_driver
+ return unless driver
if matched =~ /\A(?:::)?RubyVM/ and not ENV['RUBY_YES_I_AM_NOT_A_NORMAL_USER']
IRB.__send__(:easter_egg)
return
end
- _target, preposing, postposing, bind = @completion_params
- namespace = @completor.doc_namespace(preposing, matched, postposing, bind: bind)
+ namespace = retrieve_doc_namespace(matched)
return unless namespace
- driver ||= RDoc::RI::Driver.new
if namespace.is_a?(Array)
out = RDoc::Markup::Document.new
namespace.each do |m|
@@ -466,6 +488,10 @@ module IRB
@eof
end
+ def prompting?
+ true
+ end
+
# For debug message
def inspect
config = Reline::Config.new
diff --git a/lib/irb/inspector.rb b/lib/irb/inspector.rb
index ee3b19efdc..667087ccba 100644
--- a/lib/irb/inspector.rb
+++ b/lib/irb/inspector.rb
@@ -1,4 +1,4 @@
-# frozen_string_literal: false
+# frozen_string_literal: true
#
# irb/inspector.rb - inspect methods
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
@@ -46,7 +46,7 @@ module IRB # :nodoc:
# Determines the inspector to use where +inspector+ is one of the keys passed
# during inspector definition.
def self.keys_with_inspector(inspector)
- INSPECTORS.select{|k,v| v == inspector}.collect{|k, v| k}
+ INSPECTORS.select{|k, v| v == inspector}.collect{|k, v| k}
end
# Example
@@ -113,7 +113,7 @@ module IRB # :nodoc:
Color.colorize_code(v.inspect, colorable: Color.colorable? && Color.inspect_colorable?(v))
}
Inspector.def_inspector([true, :pp, :pretty_inspect], proc{require_relative "color_printer"}){|v|
- IRB::ColorPrinter.pp(v, '').chomp
+ IRB::ColorPrinter.pp(v, +'').chomp
}
Inspector.def_inspector([:yaml, :YAML], proc{require "yaml"}){|v|
begin
diff --git a/lib/irb/irb.gemspec b/lib/irb/irb.gemspec
index a008a39f9d..b29002f593 100644
--- a/lib/irb/irb.gemspec
+++ b/lib/irb/irb.gemspec
@@ -41,6 +41,6 @@ Gem::Specification.new do |spec|
spec.required_ruby_version = Gem::Requirement.new(">= 2.7")
- spec.add_dependency "reline", ">= 0.3.8"
- spec.add_dependency "rdoc"
+ spec.add_dependency "reline", ">= 0.4.2"
+ spec.add_dependency "rdoc", ">= 4.0.0"
end
diff --git a/lib/irb/lc/error.rb b/lib/irb/lc/error.rb
index a5ec150865..ee0f047822 100644
--- a/lib/irb/lc/error.rb
+++ b/lib/irb/lc/error.rb
@@ -1,4 +1,4 @@
-# frozen_string_literal: false
+# frozen_string_literal: true
#
# irb/lc/error.rb -
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
@@ -12,11 +12,6 @@ module IRB
super("Unrecognized switch: #{val}")
end
end
- class NotImplementedError < StandardError
- def initialize(val)
- super("Need to define `#{val}'")
- end
- end
class CantReturnToNormalMode < StandardError
def initialize
super("Can't return to normal mode.")
@@ -52,11 +47,6 @@ module IRB
super("Undefined prompt mode(#{val}).")
end
end
- class IllegalRCGenerator < StandardError
- def initialize
- super("Define illegal RC_NAME_GENERATOR.")
- end
- end
# :startdoc:
end
diff --git a/lib/irb/lc/ja/error.rb b/lib/irb/lc/ja/error.rb
index 50d72c4a10..9e2e5b8870 100644
--- a/lib/irb/lc/ja/error.rb
+++ b/lib/irb/lc/ja/error.rb
@@ -1,4 +1,4 @@
-# frozen_string_literal: false
+# frozen_string_literal: true
#
# irb/lc/ja/error.rb -
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
@@ -12,11 +12,6 @@ module IRB
super("スイッチ(#{val})が分りません")
end
end
- class NotImplementedError < StandardError
- def initialize(val)
- super("`#{val}'の定義が必要です")
- end
- end
class CantReturnToNormalMode < StandardError
def initialize
super("Normalモードに戻れません.")
@@ -52,11 +47,6 @@ module IRB
super("プロンプトモード(#{val})は定義されていません.")
end
end
- class IllegalRCGenerator < StandardError
- def initialize
- super("RC_NAME_GENERATORが正しく定義されていません.")
- end
- end
# :startdoc:
end
diff --git a/lib/irb/lc/ja/help-message b/lib/irb/lc/ja/help-message
index cec339cf2f..99f4449b3b 100644
--- a/lib/irb/lc/ja/help-message
+++ b/lib/irb/lc/ja/help-message
@@ -9,10 +9,18 @@ Usage: irb.rb [options] [programfile] [arguments]
-W[level=2] ruby -W と同じ.
--context-mode n 新しいワークスペースを作成した時に関連する Binding
オブジェクトの作成方法を 0 から 3 のいずれかに設定する.
+ --extra-doc-dir 指定したディレクトリのドキュメントを追加で読み込む.
--echo 実行結果を表示する(デフォルト).
--noecho 実行結果を表示しない.
+ --echo-on-assignment
+ 代入結果を表示する.
+ --noecho-on-assignment
+ 代入結果を表示しない.
+ --truncate-echo-on-assignment
+ truncateされた代入結果を表示する(デフォルト).
--inspect 結果出力にinspectを用いる.
--noinspect 結果出力にinspectを用いない.
+ --no-pager ページャを使用しない.
--multiline マルチラインエディタを利用する.
--nomultiline マルチラインエディタを利用しない.
--singleline シングルラインエディタを利用する.
@@ -34,6 +42,8 @@ Usage: irb.rb [options] [programfile] [arguments]
--sample-book-mode/--simple-prompt
非常にシンプルなプロンプトを用いるモードです.
--noprompt プロンプト表示を行なわない.
+ --script スクリプトモード(最初の引数をスクリプトファイルとして扱う、デフォルト)
+ --noscript 引数をargvとして扱う.
--single-irb irb 中で self を実行して得られるオブジェクトをサ
ブ irb と共有する.
--tracer コマンド実行時にトレースを行なう.
diff --git a/lib/irb/locale.rb b/lib/irb/locale.rb
index f94aa0f40b..2abcc7354b 100644
--- a/lib/irb/locale.rb
+++ b/lib/irb/locale.rb
@@ -1,4 +1,4 @@
-# frozen_string_literal: false
+# frozen_string_literal: true
#
# irb/locale.rb - internationalization module
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
@@ -94,7 +94,7 @@ module IRB # :nodoc:
end
end
- def find(file , paths = $:)
+ def find(file, paths = $:)
dir = File.dirname(file)
dir = "" if dir == "."
base = File.basename(file)
diff --git a/lib/irb/nesting_parser.rb b/lib/irb/nesting_parser.rb
index 3d4db82444..5aa940cc28 100644
--- a/lib/irb/nesting_parser.rb
+++ b/lib/irb/nesting_parser.rb
@@ -12,6 +12,8 @@ module IRB
skip = false
last_tok, state, args = opens.last
case state
+ when :in_alias_undef
+ skip = t.event == :on_kw
when :in_unquoted_symbol
unless IGNORE_TOKENS.include?(t.event)
opens.pop
@@ -61,17 +63,17 @@ module IRB
if args.include?(:arg)
case t.event
when :on_nl, :on_semicolon
- # def recever.f;
+ # def receiver.f;
body = :normal
when :on_lparen
- # def recever.f()
+ # def receiver.f()
next_args << :eq
else
if t.event == :on_op && t.tok == '='
# def receiver.f =
body = :oneliner
else
- # def recever.f arg
+ # def receiver.f arg
next_args << :arg_without_paren
end
end
@@ -130,6 +132,10 @@ module IRB
opens.pop
opens << [t, nil]
end
+ when 'alias'
+ opens << [t, :in_alias_undef, 2]
+ when 'undef'
+ opens << [t, :in_alias_undef, 1]
when 'elsif', 'else', 'when'
opens.pop
opens << [t, nil]
@@ -174,6 +180,10 @@ module IRB
pending_heredocs.reverse_each { |t| opens << [t, nil] }
pending_heredocs = []
end
+ if opens.last && opens.last[1] == :in_alias_undef && !IGNORE_TOKENS.include?(t.event) && t.event != :on_heredoc_end
+ tok, state, arg = opens.pop
+ opens << [tok, state, arg - 1] if arg >= 1
+ end
yield t, opens if block_given?
end
opens.map(&:first) + pending_heredocs.reverse
diff --git a/lib/irb/notifier.rb b/lib/irb/notifier.rb
index 612de3df16..dc1b9ef14b 100644
--- a/lib/irb/notifier.rb
+++ b/lib/irb/notifier.rb
@@ -1,4 +1,4 @@
-# frozen_string_literal: false
+# frozen_string_literal: true
#
# notifier.rb - output methods used by irb
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
diff --git a/lib/irb/output-method.rb b/lib/irb/output-method.rb
index f5ea57111d..69942f47a2 100644
--- a/lib/irb/output-method.rb
+++ b/lib/irb/output-method.rb
@@ -1,4 +1,4 @@
-# frozen_string_literal: false
+# frozen_string_literal: true
#
# output-method.rb - output methods used by irb
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
@@ -9,16 +9,10 @@ module IRB
# IRB::Notifier. You can define your own output method to use with Irb.new,
# or Context.new
class OutputMethod
- class NotImplementedError < StandardError
- def initialize(val)
- super("Need to define `#{val}'")
- end
- end
-
# Open this method to implement your own output method, raises a
# NotImplementedError if you don't define #print in your own class.
def print(*opts)
- raise NotImplementedError, "print"
+ raise NotImplementedError
end
# Prints the given +opts+, with a newline delimiter.
diff --git a/lib/irb/ruby-lex.rb b/lib/irb/ruby-lex.rb
index 4bce2aa6b2..cfe36be83f 100644
--- a/lib/irb/ruby-lex.rb
+++ b/lib/irb/ruby-lex.rb
@@ -1,4 +1,4 @@
-# frozen_string_literal: false
+# frozen_string_literal: true
#
# irb/ruby-lex.rb - ruby lexcal analyzer
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
@@ -290,7 +290,7 @@ module IRB
when :on_embdoc_beg
indent_level = 0
else
- indent_level += 1
+ indent_level += 1 unless t.tok == 'alias' || t.tok == 'undef'
end
end
indent_level
diff --git a/lib/irb/source_finder.rb b/lib/irb/source_finder.rb
index 659d4200fd..5d7d729d19 100644
--- a/lib/irb/source_finder.rb
+++ b/lib/irb/source_finder.rb
@@ -4,12 +4,63 @@ require_relative "ruby-lex"
module IRB
class SourceFinder
- Source = Struct.new(
- :file, # @param [String] - file name
- :first_line, # @param [String] - first line
- :last_line, # @param [String] - last line
- keyword_init: true,
- )
+ class EvaluationError < StandardError; end
+
+ class Source
+ attr_reader :file, :line
+ def initialize(file, line, ast_source = nil)
+ @file = file
+ @line = line
+ @ast_source = ast_source
+ end
+
+ def file_exist?
+ File.exist?(@file)
+ end
+
+ def binary_file?
+ # If the line is zero, it means that the target's source is probably in a binary file.
+ @line.zero?
+ end
+
+ def file_content
+ @file_content ||= File.read(@file)
+ end
+
+ def colorized_content
+ if !binary_file? && file_exist?
+ end_line = find_end
+ # To correctly colorize, we need to colorize full content and extract the relevant lines.
+ colored = IRB::Color.colorize_code(file_content)
+ colored.lines[@line - 1...end_line].join
+ elsif @ast_source
+ IRB::Color.colorize_code(@ast_source)
+ end
+ end
+
+ private
+
+ def find_end
+ lex = RubyLex.new
+ code = file_content
+ lines = code.lines[(@line - 1)..-1]
+ tokens = RubyLex.ripper_lex_without_warning(lines.join)
+ prev_tokens = []
+
+ # chunk with line number
+ tokens.chunk { |tok| tok.pos[0] }.each do |lnum, chunk|
+ code = lines[0..lnum].join
+ prev_tokens.concat chunk
+ continue = lex.should_continue?(prev_tokens)
+ syntax = lex.check_code_syntax(code, local_variables: [])
+ if !continue && syntax == :valid
+ return @line + lnum
+ end
+ end
+ @line
+ end
+ end
+
private_constant :Source
def initialize(irb_context)
@@ -17,49 +68,47 @@ module IRB
end
def find_source(signature, super_level = 0)
- context_binding = @irb_context.workspace.binding
case signature
- when /\A[A-Z]\w*(::[A-Z]\w*)*\z/ # Const::Name
- eval(signature, context_binding) # trigger autoload
- base = context_binding.receiver.yield_self { |r| r.is_a?(Module) ? r : Object }
- file, line = base.const_source_location(signature)
+ when /\A(::)?[A-Z]\w*(::[A-Z]\w*)*\z/ # ConstName, ::ConstName, ConstPath::ConstName
+ eval_receiver_or_owner(signature) # trigger autoload
+ *parts, name = signature.split('::', -1)
+ base =
+ if parts.empty? # ConstName
+ find_const_owner(name)
+ elsif parts == [''] # ::ConstName
+ Object
+ else # ConstPath::ConstName
+ eval_receiver_or_owner(parts.join('::'))
+ end
+ file, line = base.const_source_location(name)
when /\A(?<owner>[A-Z]\w*(::[A-Z]\w*)*)#(?<method>[^ :.]+)\z/ # Class#method
- owner = eval(Regexp.last_match[:owner], context_binding)
+ owner = eval_receiver_or_owner(Regexp.last_match[:owner])
method = Regexp.last_match[:method]
return unless owner.respond_to?(:instance_method)
- file, line = method_target(owner, super_level, method, "owner")
+ method = method_target(owner, super_level, method, "owner")
+ file, line = method&.source_location
when /\A((?<receiver>.+)(\.|::))?(?<method>[^ :.]+)\z/ # method, receiver.method, receiver::method
- receiver = eval(Regexp.last_match[:receiver] || 'self', context_binding)
+ receiver = eval_receiver_or_owner(Regexp.last_match[:receiver] || 'self')
method = Regexp.last_match[:method]
return unless receiver.respond_to?(method, true)
- file, line = method_target(receiver, super_level, method, "receiver")
+ method = method_target(receiver, super_level, method, "receiver")
+ file, line = method&.source_location
end
- if file && line && File.exist?(file)
- Source.new(file: file, first_line: line, last_line: find_end(file, line))
+ return unless file && line
+
+ if File.exist?(file)
+ Source.new(file, line)
+ elsif method
+ # Method defined with eval, probably in IRB session
+ source = RubyVM::AbstractSyntaxTree.of(method)&.source rescue nil
+ Source.new(file, line, source)
end
+ rescue EvaluationError
+ nil
end
private
- def find_end(file, first_line)
- lex = RubyLex.new
- lines = File.read(file).lines[(first_line - 1)..-1]
- tokens = RubyLex.ripper_lex_without_warning(lines.join)
- prev_tokens = []
-
- # chunk with line number
- tokens.chunk { |tok| tok.pos[0] }.each do |lnum, chunk|
- code = lines[0..lnum].join
- prev_tokens.concat chunk
- continue = lex.should_continue?(prev_tokens)
- syntax = lex.check_code_syntax(code, local_variables: [])
- if !continue && syntax == :valid
- return first_line + lnum
- end
- end
- first_line
- end
-
def method_target(owner_receiver, super_level, method, type)
case type
when "owner"
@@ -70,9 +119,21 @@ module IRB
super_level.times do |s|
target_method = target_method.super_method if target_method
end
- target_method.nil? ? nil : target_method.source_location
+ target_method
rescue NameError
nil
end
+
+ def eval_receiver_or_owner(code)
+ context_binding = @irb_context.workspace.binding
+ eval(code, context_binding)
+ rescue NameError
+ raise EvaluationError
+ end
+
+ def find_const_owner(name)
+ module_nesting = @irb_context.workspace.binding.eval('::Module.nesting')
+ module_nesting.find { |mod| mod.const_defined?(name, false) } || module_nesting.find { |mod| mod.const_defined?(name) } || Object
+ end
end
end
diff --git a/lib/irb/statement.rb b/lib/irb/statement.rb
index b12110600c..a3391c12a3 100644
--- a/lib/irb/statement.rb
+++ b/lib/irb/statement.rb
@@ -16,8 +16,23 @@ module IRB
raise NotImplementedError
end
- def evaluable_code
- raise NotImplementedError
+ class EmptyInput < Statement
+ def is_assignment?
+ false
+ end
+
+ def suppresses_echo?
+ true
+ end
+
+ # Debugger takes empty input to repeat the last command
+ def should_be_handled_by_debugger?
+ true
+ end
+
+ def code
+ ""
+ end
end
class Expression < Statement
@@ -37,18 +52,15 @@ module IRB
def is_assignment?
@is_assignment
end
-
- def evaluable_code
- @code
- end
end
class Command < Statement
- def initialize(code, command, arg, command_class)
- @code = code
- @command = command
- @arg = arg
+ attr_reader :command_class, :arg
+
+ def initialize(original_code, command_class, arg)
+ @code = original_code
@command_class = command_class
+ @arg = arg
end
def is_assignment?
@@ -60,20 +72,8 @@ module IRB
end
def should_be_handled_by_debugger?
- require_relative 'cmd/help'
- require_relative 'cmd/debug'
- IRB::ExtendCommand::DebugCommand > @command_class || IRB::ExtendCommand::Help == @command_class
- end
-
- def evaluable_code
- # Hook command-specific transformation to return valid Ruby code
- if @command_class.respond_to?(:transform_args)
- arg = @command_class.transform_args(@arg)
- else
- arg = @arg
- end
-
- [@command, arg].compact.join(' ')
+ require_relative 'command/debug'
+ IRB::Command::DebugCommand > @command_class
end
end
end
diff --git a/lib/irb/version.rb b/lib/irb/version.rb
index c0be6d91e5..c41917329c 100644
--- a/lib/irb/version.rb
+++ b/lib/irb/version.rb
@@ -1,11 +1,11 @@
-# frozen_string_literal: false
+# frozen_string_literal: true
#
# irb/version.rb - irb version definition file
# by Keiju ISHITSUKA(keiju@ishitsuka.com)
#
module IRB # :nodoc:
- VERSION = "1.11.0"
+ VERSION = "1.13.1"
@RELEASE_VERSION = VERSION
- @LAST_UPDATE_DATE = "2023-12-19"
+ @LAST_UPDATE_DATE = "2024-05-05"
end
diff --git a/lib/irb/workspace.rb b/lib/irb/workspace.rb
index 2bf3d5e0f1..d24d1cc38d 100644
--- a/lib/irb/workspace.rb
+++ b/lib/irb/workspace.rb
@@ -1,4 +1,4 @@
-# frozen_string_literal: false
+# frozen_string_literal: true
#
# irb/workspace-binding.rb -
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
@@ -6,6 +6,8 @@
require "delegate"
+require_relative "helper_method"
+
IRB::TOPLEVEL_BINDING = binding
module IRB # :nodoc:
class WorkSpace
@@ -90,11 +92,11 @@ EOF
IRB.conf[:__MAIN__] = @main
@main.singleton_class.class_eval do
private
- define_method(:exit) do |*a, &b|
- # Do nothing, will be overridden
- end
define_method(:binding, Kernel.instance_method(:binding))
define_method(:local_variables, Kernel.instance_method(:local_variables))
+ # Define empty method to avoid delegator warning, will be overridden.
+ define_method(:exit) {|*a, &b| }
+ define_method(:exit!) {|*a, &b| }
end
@binding = eval("IRB.conf[:__MAIN__].instance_eval('binding', __FILE__, __LINE__)", @binding, *@binding.source_location)
end
@@ -108,8 +110,10 @@ EOF
# <code>IRB.conf[:__MAIN__]</code>
attr_reader :main
- def load_commands_to_main
- main.extend ExtendCommandBundle
+ def load_helper_methods_to_main
+ ancestors = class<<main;ancestors;end
+ main.extend ExtendCommandBundle if !ancestors.include?(ExtendCommandBundle)
+ main.extend HelpersContainer if !ancestors.include?(HelpersContainer)
end
# Evaluate the given +statements+ within the context of this workspace.
@@ -170,4 +174,16 @@ EOF
"\nFrom: #{file} @ line #{pos + 1} :\n\n#{body}#{Color.clear}\n"
end
end
+
+ module HelpersContainer
+ def self.install_helper_methods
+ HelperMethod.helper_methods.each do |name, helper_method_class|
+ define_method name do |*args, **opts, &block|
+ helper_method_class.instance.execute(*args, **opts, &block)
+ end unless method_defined?(name)
+ end
+ end
+
+ install_helper_methods
+ end
end
diff --git a/lib/irb/ws-for-case-2.rb b/lib/irb/ws-for-case-2.rb
index a0f617e4ed..03f42d73d9 100644
--- a/lib/irb/ws-for-case-2.rb
+++ b/lib/irb/ws-for-case-2.rb
@@ -1,4 +1,4 @@
-# frozen_string_literal: false
+# frozen_string_literal: true
#
# irb/ws-for-case-2.rb -
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
diff --git a/lib/irb/xmp.rb b/lib/irb/xmp.rb
index de29089429..b1bc53283e 100644
--- a/lib/irb/xmp.rb
+++ b/lib/irb/xmp.rb
@@ -1,4 +1,4 @@
-# frozen_string_literal: false
+# frozen_string_literal: true
#
# xmp.rb - irb version of gotoken xmp
# by Keiju ISHITSUKA(Nippon Rational Inc.)
diff --git a/lib/reline.rb b/lib/reline.rb
index fb19982081..fb00b96531 100644
--- a/lib/reline.rb
+++ b/lib/reline.rb
@@ -75,10 +75,10 @@ module Reline
def initialize
self.output = STDOUT
+ @mutex = Mutex.new
@dialog_proc_list = {}
yield self
@completion_quote_character = nil
- @bracketed_paste_finished = false
end
def io_gate
@@ -220,32 +220,25 @@ module Reline
Reline::DEFAULT_DIALOG_PROC_AUTOCOMPLETE = ->() {
# autocomplete
- return nil unless config.autocompletion
- if just_cursor_moving and completion_journey_data.nil?
- # Auto complete starts only when edited
- return nil
- end
- pre, target, post = retrieve_completion_block(true)
- if target.nil? or target.empty? or (completion_journey_data&.pointer == -1 and target.size <= 3)
- return nil
- end
- if completion_journey_data and completion_journey_data.list
- result = completion_journey_data.list.dup
- result.shift
- pointer = completion_journey_data.pointer - 1
- else
- result = call_completion_proc_with_checking_args(pre, target, post)
- pointer = nil
- end
- if result and result.size == 1 and result[0] == target and pointer != 0
- result = nil
- end
+ return unless config.autocompletion
+
+ journey_data = completion_journey_data
+ return unless journey_data
+
+ target = journey_data.list.first
+ completed = journey_data.list[journey_data.pointer]
+ result = journey_data.list.drop(1)
+ pointer = journey_data.pointer - 1
+ return if completed.empty? || (result == [completed] && pointer < 0)
+
target_width = Reline::Unicode.calculate_width(target)
- x = cursor_pos.x - target_width
- if x < 0
- x = screen_width + x
+ completed_width = Reline::Unicode.calculate_width(completed)
+ if cursor_pos.x <= completed_width - target_width
+ # When target is rendered on the line above cursor position
+ x = screen_width - completed_width
y = -1
else
+ x = [cursor_pos.x - completed_width, 0].max
y = 0
end
cursor_pos_to_render = Reline::CursorPos.new(x, y)
@@ -265,12 +258,15 @@ module Reline
Reline::DEFAULT_DIALOG_CONTEXT = Array.new
def readmultiline(prompt = '', add_hist = false, &confirm_multiline_termination)
- Reline.update_iogate
- io_gate.with_raw_input do
+ @mutex.synchronize do
unless confirm_multiline_termination
raise ArgumentError.new('#readmultiline needs block to confirm multiline termination')
end
- inner_readline(prompt, add_hist, true, &confirm_multiline_termination)
+
+ Reline.update_iogate
+ io_gate.with_raw_input do
+ inner_readline(prompt, add_hist, true, &confirm_multiline_termination)
+ end
whole_buffer = line_editor.whole_buffer.dup
whole_buffer.taint if RUBY_VERSION < '2.7'
@@ -278,23 +274,32 @@ module Reline
Reline::HISTORY << whole_buffer
end
- line_editor.reset_line if line_editor.whole_buffer.nil?
- whole_buffer
+ if line_editor.eof?
+ line_editor.reset_line
+ # Return nil if the input is aborted by C-d.
+ nil
+ else
+ whole_buffer
+ end
end
end
def readline(prompt = '', add_hist = false)
- Reline.update_iogate
- inner_readline(prompt, add_hist, false)
+ @mutex.synchronize do
+ Reline.update_iogate
+ io_gate.with_raw_input do
+ inner_readline(prompt, add_hist, false)
+ end
- line = line_editor.line.dup
- line.taint if RUBY_VERSION < '2.7'
- if add_hist and line and line.chomp("\n").size > 0
- Reline::HISTORY << line.chomp("\n")
- end
+ line = line_editor.line.dup
+ line.taint if RUBY_VERSION < '2.7'
+ 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?
- line
+ line_editor.reset_line if line_editor.line.nil?
+ line
+ end
end
private def inner_readline(prompt, add_hist, multiline, &confirm_multiline_termination)
@@ -307,6 +312,10 @@ module Reline
$stderr.sync = true
$stderr.puts "Reline is used by #{Process.pid}"
end
+ unless config.test_mode or config.loaded?
+ config.read
+ io_gate.set_default_key_bindings(config)
+ end
otio = io_gate.prep
may_req_ambiguous_char_width
@@ -326,58 +335,47 @@ module Reline
line_editor.prompt_proc = prompt_proc
line_editor.auto_indent_proc = auto_indent_proc
line_editor.dig_perfect_match_proc = dig_perfect_match_proc
- line_editor.pre_input_hook = pre_input_hook
- @dialog_proc_list.each_pair do |name_sym, d|
- line_editor.add_dialog_proc(name_sym, d.dialog_proc, d.context)
- end
-
- unless config.test_mode
- config.read
- config.reset_default_key_bindings
- io_gate.set_default_key_bindings(config)
+ pre_input_hook&.call
+ unless Reline::IOGate == Reline::GeneralIO
+ @dialog_proc_list.each_pair do |name_sym, d|
+ line_editor.add_dialog_proc(name_sym, d.dialog_proc, d.context)
+ end
end
+ line_editor.print_nomultiline_prompt(prompt)
+ line_editor.update_dialogs
line_editor.rerender
begin
line_editor.set_signal_handlers
- prev_pasting_state = false
loop do
- prev_pasting_state = io_gate.in_pasting?
read_io(config.keyseq_timeout) { |inputs|
line_editor.set_pasting_state(io_gate.in_pasting?)
- inputs.each { |c|
- line_editor.input_key(c)
- line_editor.rerender
- }
- if @bracketed_paste_finished
- line_editor.rerender_all
- @bracketed_paste_finished = false
+ inputs.each do |key|
+ if key.char == :bracketed_paste_start
+ text = io_gate.read_bracketed_paste
+ line_editor.insert_pasted_text(text)
+ line_editor.scroll_into_view
+ else
+ line_editor.update(key)
+ end
end
}
- if prev_pasting_state == true and not io_gate.in_pasting? and not line_editor.finished?
- line_editor.set_pasting_state(false)
- prev_pasting_state = false
- line_editor.rerender_all
+ if line_editor.finished?
+ line_editor.render_finished
+ break
+ else
+ line_editor.set_pasting_state(io_gate.in_pasting?)
+ line_editor.rerender
end
- break if line_editor.finished?
end
io_gate.move_cursor_column(0)
rescue Errno::EIO
# Maybe the I/O has been closed.
- rescue StandardError => e
+ ensure
line_editor.finalize
io_gate.deprep(otio)
- raise e
- rescue Exception
- # Including Interrupt
- line_editor.finalize
- io_gate.deprep(otio)
- raise
end
-
- line_editor.finalize
- io_gate.deprep(otio)
end
# GNU Readline waits for "keyseq-timeout" milliseconds to see if the ESC
@@ -395,7 +393,6 @@ module Reline
c = io_gate.getc(Float::INFINITY)
if c == -1
result = :unmatched
- @bracketed_paste_finished = true
else
buffer << c
result = key_stroke.match_status(buffer)
diff --git a/lib/reline/ansi.rb b/lib/reline/ansi.rb
index c2e5075ea8..45a475a787 100644
--- a/lib/reline/ansi.rb
+++ b/lib/reline/ansi.rb
@@ -3,6 +3,8 @@ require 'io/wait'
require_relative 'terminfo'
class Reline::ANSI
+ RESET_COLOR = "\e[0m"
+
CAPNAME_KEY_BINDINGS = {
'khome' => :ed_move_to_beg,
'kend' => :ed_move_to_end,
@@ -43,6 +45,7 @@ class Reline::ANSI
end
def self.set_default_key_bindings(config, allow_terminfo: true)
+ set_bracketed_paste_key_bindings(config)
set_default_key_bindings_ansi_cursor(config)
if allow_terminfo && Reline::Terminfo.enabled?
set_default_key_bindings_terminfo(config)
@@ -64,6 +67,12 @@ class Reline::ANSI
end
end
+ def self.set_bracketed_paste_key_bindings(config)
+ [:emacs, :vi_insert, :vi_command].each do |keymap|
+ config.add_default_key_binding_by_keymap(keymap, START_BRACKETED_PASTE.bytes, :bracketed_paste_start)
+ end
+ end
+
def self.set_default_key_bindings_ansi_cursor(config)
ANSI_CURSOR_KEY_BINDINGS.each do |char, (default_func, modifiers)|
bindings = [["\e[#{char}", default_func]] # CSI + char
@@ -149,7 +158,11 @@ class Reline::ANSI
end
def self.with_raw_input
- @@input.raw { yield }
+ if @@input.tty?
+ @@input.raw(intr: true) { yield }
+ else
+ yield
+ end
end
@@buf = []
@@ -157,11 +170,13 @@ class Reline::ANSI
unless @@buf.empty?
return @@buf.shift
end
- until c = @@input.raw(intr: true) { @@input.wait_readable(0.1) && @@input.getbyte }
- timeout_second -= 0.1
+ until @@input.wait_readable(0.01)
+ timeout_second -= 0.01
return nil if timeout_second <= 0
- Reline.core.line_editor.resize
+
+ Reline.core.line_editor.handle_signal
end
+ c = @@input.getbyte
(c == 0x16 && @@input.raw(min: 0, time: 0, &:getbyte)) || c
rescue Errno::EIO
# Maybe the I/O has been closed.
@@ -170,46 +185,26 @@ class Reline::ANSI
nil
end
- @@in_bracketed_paste_mode = false
- START_BRACKETED_PASTE = String.new("\e[200~,", encoding: Encoding::ASCII_8BIT)
- END_BRACKETED_PASTE = String.new("\e[200~.", encoding: Encoding::ASCII_8BIT)
- def self.getc_with_bracketed_paste(timeout_second)
+ START_BRACKETED_PASTE = String.new("\e[200~", encoding: Encoding::ASCII_8BIT)
+ END_BRACKETED_PASTE = String.new("\e[201~", encoding: Encoding::ASCII_8BIT)
+ def self.read_bracketed_paste
buffer = String.new(encoding: Encoding::ASCII_8BIT)
- buffer << inner_getc(timeout_second)
- while START_BRACKETED_PASTE.start_with?(buffer) or END_BRACKETED_PASTE.start_with?(buffer) do
- if START_BRACKETED_PASTE == buffer
- @@in_bracketed_paste_mode = true
- return inner_getc(timeout_second)
- elsif END_BRACKETED_PASTE == buffer
- @@in_bracketed_paste_mode = false
- ungetc(-1)
- return inner_getc(timeout_second)
- end
- succ_c = inner_getc(Reline.core.config.keyseq_timeout)
-
- if succ_c
- buffer << succ_c
- else
- break
- end
+ until buffer.end_with?(END_BRACKETED_PASTE)
+ c = inner_getc(Float::INFINITY)
+ break unless c
+ buffer << c
end
- buffer.bytes.reverse_each do |ch|
- ungetc ch
- end
- inner_getc(timeout_second)
+ string = buffer.delete_suffix(END_BRACKETED_PASTE).force_encoding(encoding)
+ string.valid_encoding? ? string : ''
end
# if the usage expects to wait indefinitely, use Float::INFINITY for timeout_second
def self.getc(timeout_second)
- if Reline.core.config.enable_bracketed_paste
- getc_with_bracketed_paste(timeout_second)
- else
- inner_getc(timeout_second)
- end
+ inner_getc(timeout_second)
end
def self.in_pasting?
- @@in_bracketed_paste_mode or (not empty_buffer?)
+ not empty_buffer?
end
def self.empty_buffer?
@@ -276,7 +271,7 @@ class Reline::ANSI
buf = @@output.pread(@@output.pos, 0)
row = buf.count("\n")
column = buf.rindex("\n") ? (buf.size - buf.rindex("\n")) - 1 : 0
- rescue Errno::ESPIPE
+ rescue Errno::ESPIPE, IOError
# Just returns column 1 for ambiguous width because this I/O is not
# tty and can't seek.
row = 0
@@ -307,7 +302,7 @@ class Reline::ANSI
end
def self.hide_cursor
- if Reline::Terminfo.enabled?
+ if Reline::Terminfo.enabled? && Reline::Terminfo.term_supported?
begin
@@output.write Reline::Terminfo.tigetstr('civis')
rescue Reline::Terminfo::TerminfoError
@@ -319,7 +314,7 @@ class Reline::ANSI
end
def self.show_cursor
- if Reline::Terminfo.enabled?
+ if Reline::Terminfo.enabled? && Reline::Terminfo.term_supported?
begin
@@output.write Reline::Terminfo.tigetstr('cnorm')
rescue Reline::Terminfo::TerminfoError
@@ -353,11 +348,15 @@ class Reline::ANSI
end
def self.prep
+ # Enable bracketed paste
+ @@output.write "\e[?2004h" if Reline.core.config.enable_bracketed_paste
retrieve_keybuffer
nil
end
def self.deprep(otio)
+ # Disable bracketed paste
+ @@output.write "\e[?2004l" if Reline.core.config.enable_bracketed_paste
Signal.trap('WINCH', @@old_winch_handler) if @@old_winch_handler
end
end
diff --git a/lib/reline/config.rb b/lib/reline/config.rb
index 4c07a73701..d44c2675ab 100644
--- a/lib/reline/config.rb
+++ b/lib/reline/config.rb
@@ -8,31 +8,12 @@ class Reline::Config
end
VARIABLE_NAMES = %w{
- bind-tty-special-chars
- blink-matching-paren
- byte-oriented
completion-ignore-case
convert-meta
disable-completion
- enable-keypad
- expand-tilde
- history-preserve-point
history-size
- horizontal-scroll-mode
- input-meta
keyseq-timeout
- mark-directories
- mark-modified-lines
- mark-symlinked-directories
- match-hidden-files
- meta-flag
- output-meta
- page-completions
- prefer-visible-bell
- print-completions-horizontally
show-all-if-ambiguous
- show-all-if-unmodified
- visible-stats
show-mode-in-prompt
vi-cmd-mode-string
vi-ins-mode-string
@@ -53,8 +34,6 @@ class Reline::Config
@additional_key_bindings[:vi_insert] = {}
@additional_key_bindings[:vi_command] = {}
@oneshot_key_bindings = {}
- @skip_section = nil
- @if_stack = nil
@editing_mode_label = :emacs
@keymap_label = :emacs
@keymap_prefix = []
@@ -71,17 +50,15 @@ class Reline::Config
@test_mode = false
@autocompletion = false
@convert_meta = true if seven_bit_encoding?(Reline::IOGate.encoding)
+ @loaded = false
+ @enable_bracketed_paste = true
end
def reset
if editing_mode_is?(:vi_command)
@editing_mode_label = :vi_insert
end
- @additional_key_bindings.keys.each do |key|
- @additional_key_bindings[key].clear
- end
@oneshot_key_bindings.clear
- reset_default_key_bindings
end
def editing_mode
@@ -100,6 +77,10 @@ class Reline::Config
@key_actors[@keymap_label]
end
+ def loaded?
+ @loaded
+ end
+
def inputrc_path
case ENV['INPUTRC']
when nil, ''
@@ -131,6 +112,7 @@ class Reline::Config
end
def read(file = nil)
+ @loaded = true
file ||= default_inputrc_path
begin
if file.respond_to?(:readlines)
@@ -173,12 +155,6 @@ class Reline::Config
@key_actors[@keymap_label].default_key_bindings[keystroke] = target
end
- def reset_default_key_bindings
- @key_actors.values.each do |ka|
- ka.reset_default_key_bindings
- end
- end
-
def read_lines(lines, file = nil)
if not lines.empty? and lines.first.encoding != Reline.encoding_system_needs
begin
@@ -190,9 +166,7 @@ class Reline::Config
end
end
end
- conditions = [@skip_section, @if_stack]
- @skip_section = nil
- @if_stack = []
+ if_stack = []
lines.each_with_index do |line, no|
next if line.match(/\A\s*#/)
@@ -201,11 +175,11 @@ class Reline::Config
line = line.chomp.lstrip
if line.start_with?('$')
- handle_directive(line[1..-1], file, no)
+ handle_directive(line[1..-1], file, no, if_stack)
next
end
- next if @skip_section
+ next if if_stack.any? { |_no, skip| skip }
case line
when /^set +([^ ]+) +([^ ]+)/i
@@ -214,43 +188,47 @@ class Reline::Config
next
when /\s*("#{KEYSEQ_PATTERN}+")\s*:\s*(.*)\s*$/o
key, func_name = $1, $2
+ func_name = func_name.split.first
keystroke, func = bind_key(key, func_name)
next unless keystroke
@additional_key_bindings[@keymap_label][@keymap_prefix + keystroke] = func
end
end
- unless @if_stack.empty?
- raise InvalidInputrc, "#{file}:#{@if_stack.last[1]}: unclosed if"
+ unless if_stack.empty?
+ raise InvalidInputrc, "#{file}:#{if_stack.last[0]}: unclosed if"
end
- ensure
- @skip_section, @if_stack = conditions
end
- def handle_directive(directive, file, no)
+ def handle_directive(directive, file, no, if_stack)
directive, args = directive.split(' ')
case directive
when 'if'
condition = false
case args
- when 'mode'
+ when /^mode=(vi|emacs)$/i
+ mode = $1.downcase
+ # NOTE: mode=vi means vi-insert mode
+ mode = 'vi_insert' if mode == 'vi'
+ if @editing_mode_label == mode.to_sym
+ condition = true
+ end
when 'term'
when 'version'
else # application name
condition = true if args == 'Ruby'
condition = true if args == 'Reline'
end
- @if_stack << [file, no, @skip_section]
- @skip_section = !condition
+ if_stack << [no, !condition]
when 'else'
- if @if_stack.empty?
+ if if_stack.empty?
raise InvalidInputrc, "#{file}:#{no}: unmatched else"
end
- @skip_section = !@skip_section
+ if_stack.last[1] = !if_stack.last[1]
when 'endif'
- if @if_stack.empty?
+ if if_stack.empty?
raise InvalidInputrc, "#{file}:#{no}: unmatched endif"
end
- @skip_section = @if_stack.pop
+ if_stack.pop
when 'include'
read(File.expand_path(args))
end
diff --git a/lib/reline/face.rb b/lib/reline/face.rb
index e18ec957e8..d07196e2e7 100644
--- a/lib/reline/face.rb
+++ b/lib/reline/face.rb
@@ -186,9 +186,9 @@ class Reline::Face
conf.define :scrollbar, style: :reset
end
config(:completion_dialog) do |conf|
- conf.define :default, foreground: :white, background: :cyan
- conf.define :enhanced, foreground: :white, background: :magenta
- conf.define :scrollbar, foreground: :white, background: :cyan
+ conf.define :default, foreground: :bright_white, background: :gray
+ conf.define :enhanced, foreground: :black, background: :white
+ conf.define :scrollbar, foreground: :white, background: :gray
end
end
diff --git a/lib/reline/general_io.rb b/lib/reline/general_io.rb
index eaae63f925..d52151ad3c 100644
--- a/lib/reline/general_io.rb
+++ b/lib/reline/general_io.rb
@@ -1,6 +1,8 @@
require 'io/wait'
class Reline::GeneralIO
+ RESET_COLOR = '' # Do not send color reset sequence
+
def self.reset(encoding: nil)
@@pasting = false
if encoding
@@ -44,6 +46,7 @@ class Reline::GeneralIO
end
c = nil
loop do
+ Reline.core.line_editor.handle_signal
result = @@input.wait_readable(0.1)
next if result.nil?
c = @@input.read(1)
@@ -57,7 +60,7 @@ class Reline::GeneralIO
end
def self.get_screen_size
- [1, 1]
+ [24, 80]
end
def self.cursor_pos
@@ -100,14 +103,6 @@ class Reline::GeneralIO
@@pasting
end
- def self.start_pasting
- @@pasting = true
- end
-
- def self.finish_pasting
- @@pasting = false
- end
-
def self.prep
end
diff --git a/lib/reline/history.rb b/lib/reline/history.rb
index 7a1ed6b90b..3f3b65fea6 100644
--- a/lib/reline/history.rb
+++ b/lib/reline/history.rb
@@ -62,7 +62,7 @@ class Reline::History < Array
private def check_index(index)
index += size if index < 0
if index < -2147483648 or 2147483647 < index
- raise RangeError.new("integer #{index} too big to convert to `int'")
+ raise RangeError.new("integer #{index} too big to convert to 'int'")
end
# If history_size is negative, history size is unlimited.
if @config.history_size.positive?
diff --git a/lib/reline/key_actor/base.rb b/lib/reline/key_actor/base.rb
index a1cd7fb2a1..194e98938c 100644
--- a/lib/reline/key_actor/base.rb
+++ b/lib/reline/key_actor/base.rb
@@ -12,8 +12,4 @@ class Reline::KeyActor::Base
def default_key_bindings
@default_key_bindings
end
-
- def reset_default_key_bindings
- @default_key_bindings.clear
- end
end
diff --git a/lib/reline/key_actor/emacs.rb b/lib/reline/key_actor/emacs.rb
index a561feee57..edd88289a3 100644
--- a/lib/reline/key_actor/emacs.rb
+++ b/lib/reline/key_actor/emacs.rb
@@ -19,7 +19,7 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base
# 8 ^H
:em_delete_prev_char,
# 9 ^I
- :ed_unassigned,
+ :complete,
# 10 ^J
:ed_newline,
# 11 ^K
@@ -49,13 +49,13 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base
# 23 ^W
:em_kill_region,
# 24 ^X
- :ed_sequence_lead_in,
+ :ed_unassigned,
# 25 ^Y
:em_yank,
# 26 ^Z
:ed_ignore,
# 27 ^[
- :em_meta_next,
+ :ed_unassigned,
# 28 ^\
:ed_ignore,
# 29 ^]
@@ -63,7 +63,7 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base
# 30 ^^
:ed_unassigned,
# 31 ^_
- :ed_unassigned,
+ :undo,
# 32 SPACE
:ed_insert,
# 33 !
@@ -319,9 +319,9 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base
# 158 M-^^
:ed_unassigned,
# 159 M-^_
- :em_copy_prev_word,
- # 160 M-SPACE
:ed_unassigned,
+ # 160 M-SPACE
+ :em_set_mark,
# 161 M-!
:ed_unassigned,
# 162 M-"
@@ -415,7 +415,7 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base
# 206 M-N
:vi_search_next,
# 207 M-O
- :ed_sequence_lead_in,
+ :ed_unassigned,
# 208 M-P
:vi_search_prev,
# 209 M-Q
@@ -431,15 +431,15 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base
# 214 M-V
:ed_unassigned,
# 215 M-W
- :em_copy_region,
+ :ed_unassigned,
# 216 M-X
- :ed_command,
- # 217 M-Y
:ed_unassigned,
+ # 217 M-Y
+ :em_yank_pop,
# 218 M-Z
:ed_unassigned,
# 219 M-[
- :ed_sequence_lead_in,
+ :ed_unassigned,
# 220 M-\
:ed_unassigned,
# 221 M-]
@@ -495,9 +495,9 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base
# 246 M-v
:ed_unassigned,
# 247 M-w
- :em_copy_region,
+ :ed_unassigned,
# 248 M-x
- :ed_command,
+ :ed_unassigned,
# 249 M-y
:ed_unassigned,
# 250 M-z
diff --git a/lib/reline/key_actor/vi_command.rb b/lib/reline/key_actor/vi_command.rb
index 98146d2f77..06bb0ba8e4 100644
--- a/lib/reline/key_actor/vi_command.rb
+++ b/lib/reline/key_actor/vi_command.rb
@@ -17,7 +17,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
# 7 ^G
:ed_unassigned,
# 8 ^H
- :ed_unassigned,
+ :ed_prev_char,
# 9 ^I
:ed_unassigned,
# 10 ^J
@@ -41,7 +41,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
# 19 ^S
:ed_ignore,
# 20 ^T
- :ed_unassigned,
+ :ed_transpose_chars,
# 21 ^U
:vi_kill_line_prev,
# 22 ^V
@@ -51,7 +51,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
# 24 ^X
:ed_unassigned,
# 25 ^Y
- :ed_unassigned,
+ :em_yank,
# 26 ^Z
:ed_unassigned,
# 27 ^[
@@ -75,7 +75,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
# 36 $
:ed_move_to_end,
# 37 %
- :vi_match,
+ :ed_unassigned,
# 38 &
:ed_unassigned,
# 39 '
@@ -89,11 +89,11 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
# 43 +
:ed_next_history,
# 44 ,
- :vi_repeat_prev_char,
+ :ed_unassigned,
# 45 -
:ed_prev_history,
# 46 .
- :vi_redo,
+ :ed_unassigned,
# 47 /
:vi_search_prev,
# 48 0
@@ -117,9 +117,9 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
# 57 9
:ed_argument_digit,
# 58 :
- :ed_command,
+ :ed_unassigned,
# 59 ;
- :vi_repeat_next_char,
+ :ed_unassigned,
# 60 <
:ed_unassigned,
# 61 =
@@ -157,21 +157,21 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
# 77 M
:ed_unassigned,
# 78 N
- :vi_repeat_search_prev,
+ :ed_unassigned,
# 79 O
- :ed_sequence_lead_in,
+ :ed_unassigned,
# 80 P
:vi_paste_prev,
# 81 Q
:ed_unassigned,
# 82 R
- :vi_replace_mode,
+ :ed_unassigned,
# 83 S
- :vi_substitute_line,
+ :ed_unassigned,
# 84 T
:vi_to_prev_char,
# 85 U
- :vi_undo_line,
+ :ed_unassigned,
# 86 V
:ed_unassigned,
# 87 W
@@ -179,11 +179,11 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
# 88 X
:ed_delete_prev_char,
# 89 Y
- :vi_yank_end,
+ :ed_unassigned,
# 90 Z
:ed_unassigned,
# 91 [
- :ed_sequence_lead_in,
+ :ed_unassigned,
# 92 \
:ed_unassigned,
# 93 ]
@@ -191,7 +191,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
# 94 ^
:vi_first_print,
# 95 _
- :vi_history_word,
+ :ed_unassigned,
# 96 `
:ed_unassigned,
# 97 a
@@ -221,7 +221,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
# 109 m
:ed_unassigned,
# 110 n
- :vi_repeat_search_next,
+ :ed_unassigned,
# 111 o
:ed_unassigned,
# 112 p
@@ -231,11 +231,11 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
# 114 r
:vi_replace_char,
# 115 s
- :vi_substitute_char,
+ :ed_unassigned,
# 116 t
:vi_to_next_char,
# 117 u
- :vi_undo,
+ :ed_unassigned,
# 118 v
:vi_histedit,
# 119 w
@@ -253,9 +253,9 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
# 125 }
:ed_unassigned,
# 126 ~
- :vi_change_case,
- # 127 ^?
:ed_unassigned,
+ # 127 ^?
+ :em_delete_prev_char,
# 128 M-^@
:ed_unassigned,
# 129 M-^A
@@ -415,7 +415,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
# 206 M-N
:ed_unassigned,
# 207 M-O
- :ed_sequence_lead_in,
+ :ed_unassigned,
# 208 M-P
:ed_unassigned,
# 209 M-Q
@@ -439,7 +439,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
# 218 M-Z
:ed_unassigned,
# 219 M-[
- :ed_sequence_lead_in,
+ :ed_unassigned,
# 220 M-\
:ed_unassigned,
# 221 M-]
diff --git a/lib/reline/key_actor/vi_insert.rb b/lib/reline/key_actor/vi_insert.rb
index b8e89f81d8..f8ccf468c6 100644
--- a/lib/reline/key_actor/vi_insert.rb
+++ b/lib/reline/key_actor/vi_insert.rb
@@ -19,7 +19,7 @@ class Reline::KeyActor::ViInsert < Reline::KeyActor::Base
# 8 ^H
:vi_delete_prev_char,
# 9 ^I
- :ed_insert,
+ :complete,
# 10 ^J
:ed_newline,
# 11 ^K
@@ -29,11 +29,11 @@ class Reline::KeyActor::ViInsert < Reline::KeyActor::Base
# 13 ^M
:ed_newline,
# 14 ^N
- :ed_insert,
+ :menu_complete,
# 15 ^O
:ed_insert,
# 16 ^P
- :ed_insert,
+ :menu_complete_backward,
# 17 ^Q
:ed_ignore,
# 18 ^R
@@ -41,7 +41,7 @@ class Reline::KeyActor::ViInsert < Reline::KeyActor::Base
# 19 ^S
:vi_search_next,
# 20 ^T
- :ed_insert,
+ :ed_transpose_chars,
# 21 ^U
:vi_kill_line_prev,
# 22 ^V
@@ -51,7 +51,7 @@ class Reline::KeyActor::ViInsert < Reline::KeyActor::Base
# 24 ^X
:ed_insert,
# 25 ^Y
- :ed_insert,
+ :em_yank,
# 26 ^Z
:ed_insert,
# 27 ^[
diff --git a/lib/reline/kill_ring.rb b/lib/reline/kill_ring.rb
index bb3684b42b..201f6f3ca0 100644
--- a/lib/reline/kill_ring.rb
+++ b/lib/reline/kill_ring.rb
@@ -14,7 +14,7 @@ class Reline::KillRing
end
def ==(other)
- object_id == other.object_id
+ equal?(other)
end
end
@@ -68,7 +68,7 @@ class Reline::KillRing
def append(string, before_p = false)
case @state
when State::FRESH, State::YANK
- @ring << RingPoint.new(string)
+ @ring << RingPoint.new(+string)
@state = State::CONTINUED
when State::CONTINUED, State::PROCESSED
if before_p
diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb
index d71b903701..23ece60220 100644
--- a/lib/reline/line_editor.rb
+++ b/lib/reline/line_editor.rb
@@ -4,9 +4,7 @@ require 'reline/unicode'
require 'tempfile'
class Reline::LineEditor
- # TODO: undo
# TODO: Use "private alias_method" idiom after drop Ruby 2.5.
- attr_reader :line
attr_reader :byte_pointer
attr_accessor :confirm_multiline_termination_proc
attr_accessor :completion_proc
@@ -14,7 +12,6 @@ class Reline::LineEditor
attr_accessor :output_modifier_proc
attr_accessor :prompt_proc
attr_accessor :auto_indent_proc
- attr_accessor :pre_input_hook
attr_accessor :dig_perfect_match_proc
attr_writer :output
@@ -35,28 +32,49 @@ class Reline::LineEditor
vi_next_big_word
vi_prev_big_word
vi_end_big_word
- vi_repeat_next_char
- vi_repeat_prev_char
}
module CompletionState
NORMAL = :normal
COMPLETION = :completion
MENU = :menu
- JOURNEY = :journey
MENU_WITH_PERFECT_MATCH = :menu_with_perfect_match
PERFECT_MATCH = :perfect_match
end
- CompletionJourneyData = Struct.new(:preposing, :postposing, :list, :pointer)
- MenuInfo = Struct.new(:target, :list)
+ RenderedScreen = Struct.new(:base_y, :lines, :cursor_y, keyword_init: true)
+
+ CompletionJourneyState = Struct.new(:line_index, :pre, :target, :post, :list, :pointer)
+
+ class MenuInfo
+ attr_reader :list
+
+ def initialize(list)
+ @list = list
+ end
+
+ def lines(screen_width)
+ return [] if @list.empty?
+
+ list = @list.sort
+ sizes = list.map { |item| Reline::Unicode.calculate_width(item) }
+ item_width = sizes.max + 2
+ num_cols = [screen_width / item_width, 1].max
+ num_rows = list.size.fdiv(num_cols).ceil
+ list_with_padding = list.zip(sizes).map { |item, size| item + ' ' * (item_width - size) }
+ aligned = (list_with_padding + [nil] * (num_rows * num_cols - list_with_padding.size)).each_slice(num_rows).to_a.transpose
+ aligned.map do |row|
+ row.join.rstrip
+ end
+ end
+ end
- PROMPT_LIST_CACHE_TIMEOUT = 0.5
MINIMUM_SCROLLBAR_HEIGHT = 1
def initialize(config, encoding)
@config = config
@completion_append_character = ''
+ @screen_size = [0, 0] # Should be initialized with actual winsize in LineEditor#reset
reset_variables(encoding: encoding)
end
@@ -65,73 +83,42 @@ class Reline::LineEditor
end
def set_pasting_state(in_pasting)
+ # While pasting, text to be inserted is stored to @continuous_insertion_buffer.
+ # After pasting, this buffer should be force inserted.
+ process_insert(force: true) if @in_pasting && !in_pasting
@in_pasting = in_pasting
end
- def simplified_rendering?
- if finished?
- false
- elsif @just_cursor_moving and not @rerender_all
- true
- else
- not @rerender_all and not finished? and @in_pasting
- end
- end
-
private def check_mode_string
- mode_string = nil
if @config.show_mode_in_prompt
if @config.editing_mode_is?(:vi_command)
- mode_string = @config.vi_cmd_mode_string
+ @config.vi_cmd_mode_string
elsif @config.editing_mode_is?(:vi_insert)
- mode_string = @config.vi_ins_mode_string
+ @config.vi_ins_mode_string
elsif @config.editing_mode_is?(:emacs)
- mode_string = @config.emacs_mode_string
+ @config.emacs_mode_string
else
- mode_string = '?'
+ '?'
end
end
- if mode_string != @prev_mode_string
- @rerender_all = true
- end
- @prev_mode_string = mode_string
- mode_string
end
- private def check_multiline_prompt(buffer, force_recalc: false)
+ private def check_multiline_prompt(buffer, mode_string)
if @vi_arg
prompt = "(arg: #{@vi_arg}) "
- @rerender_all = true
elsif @searching_prompt
prompt = @searching_prompt
- @rerender_all = true
else
prompt = @prompt
end
- if simplified_rendering? && !force_recalc
+ if !@is_multiline
mode_string = check_mode_string
prompt = mode_string + prompt if mode_string
- return [prompt, calculate_width(prompt, true), [prompt] * buffer.size]
- end
- if @prompt_proc
- use_cached_prompt_list = false
- if @cached_prompt_list
- if @just_cursor_moving
- use_cached_prompt_list = true
- elsif Time.now.to_f < (@prompt_cache_time + PROMPT_LIST_CACHE_TIMEOUT) and buffer.size == @cached_prompt_list.size
- use_cached_prompt_list = true
- end
- end
- use_cached_prompt_list = false if @rerender_all
- if use_cached_prompt_list
- prompt_list = @cached_prompt_list
- else
- prompt_list = @cached_prompt_list = @prompt_proc.(buffer).map { |pr| pr.gsub("\n", "\\n") }
- @prompt_cache_time = Time.now.to_f
- end
+ [prompt] + [''] * (buffer.size - 1)
+ elsif @prompt_proc
+ prompt_list = @prompt_proc.(buffer).map { |pr| pr.gsub("\n", "\\n") }
prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
prompt_list = [prompt] if prompt_list.empty?
- mode_string = check_mode_string
prompt_list = prompt_list.map{ |pr| mode_string + pr } if mode_string
prompt = prompt_list[@line_index]
prompt = prompt_list[0] if prompt.nil?
@@ -141,24 +128,17 @@ class Reline::LineEditor
prompt_list << prompt_list.last
end
end
- prompt_width = calculate_width(prompt, true)
- [prompt, prompt_width, prompt_list]
+ prompt_list
else
- mode_string = check_mode_string
prompt = mode_string + prompt if mode_string
- prompt_width = calculate_width(prompt, true)
- [prompt, prompt_width, nil]
+ [prompt] * buffer.size
end
end
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
- @screen_height = @screen_size.first
reset_variables(prompt, encoding: encoding)
- Reline::IOGate.set_winch_handler do
- @resized = true
- end
+ @rendered_screen.base_y = Reline::IOGate.cursor_pos.y
if ENV.key?('RELINE_ALT_SCROLLBAR')
@full_block = '::'
@upper_half_block = "''"
@@ -182,67 +162,53 @@ class Reline::LineEditor
end
end
- def resize
+ def handle_signal
+ handle_interrupted
+ handle_resized
+ end
+
+ private def handle_resized
return unless @resized
- @resized = false
- @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
- old_screen_size = @screen_size
+
@screen_size = Reline::IOGate.get_screen_size
- @screen_height = @screen_size.first
- if old_screen_size.last < @screen_size.last # columns increase
- @rerender_all = true
- rerender
+ @resized = false
+ scroll_into_view
+ Reline::IOGate.move_cursor_up @rendered_screen.cursor_y
+ @rendered_screen.base_y = Reline::IOGate.cursor_pos.y
+ @rendered_screen.lines = []
+ @rendered_screen.cursor_y = 0
+ render_differential
+ end
+
+ private def handle_interrupted
+ return unless @interrupted
+
+ @interrupted = false
+ clear_dialogs
+ scrolldown = render_differential
+ Reline::IOGate.scroll_down scrolldown
+ Reline::IOGate.move_cursor_column 0
+ @rendered_screen.lines = []
+ @rendered_screen.cursor_y = 0
+ case @old_trap
+ when 'DEFAULT', 'SYSTEM_DEFAULT'
+ raise Interrupt
+ when 'IGNORE'
+ # Do nothing
+ when 'EXIT'
+ exit
else
- back = 0
- new_buffer = whole_lines
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer)
- new_buffer.each_with_index do |line, index|
- prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
- width = prompt_width + calculate_width(line)
- height = calculate_height_by_width(width)
- back += height
- end
- @highest_in_all = back
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
- @first_line_started_from =
- if @line_index.zero?
- 0
- else
- calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
- end
- if @prompt_proc
- prompt = prompt_list[@line_index]
- prompt_width = calculate_width(prompt, true)
- end
- calculate_nearest_cursor
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
- @rerender_all = true
+ @old_trap.call if @old_trap.respond_to?(:call)
end
end
def set_signal_handlers
- @old_trap = Signal.trap('INT') {
- clear_dialog(0)
- if @scroll_partial_screen
- move_cursor_down(@screen_height - (@line_index - @scroll_partial_screen) - 1)
- else
- move_cursor_down(@highest_in_all - @line_index - 1)
- end
- Reline::IOGate.move_cursor_column(0)
- scroll_down(1)
- case @old_trap
- when 'DEFAULT', 'SYSTEM_DEFAULT'
- raise Interrupt
- when 'IGNORE'
- # Do nothing
- when 'EXIT'
- exit
- else
- @old_trap.call if @old_trap.respond_to?(:call)
- end
- }
+ Reline::IOGate.set_winch_handler do
+ @resized = true
+ end
+ @old_trap = Signal.trap('INT') do
+ @interrupted = true
+ end
end
def finalize
@@ -259,56 +225,43 @@ class Reline::LineEditor
@encoding = encoding
@is_multiline = false
@finished = false
- @cleared = false
- @rerender_all = false
@history_pointer = nil
@kill_ring ||= Reline::KillRing.new
@vi_clipboard = ''
@vi_arg = nil
@waiting_proc = nil
- @waiting_operator_proc = nil
- @waiting_operator_vi_arg = nil
- @completion_journey_data = nil
+ @vi_waiting_operator = nil
+ @vi_waiting_operator_arg = nil
+ @completion_journey_state = nil
@completion_state = CompletionState::NORMAL
@perfect_matched = nil
@menu_info = nil
- @first_prompt = true
@searching_prompt = nil
@first_char = true
- @add_newline_to_end_of_buffer = false
- @just_cursor_moving = nil
- @cached_prompt_list = nil
- @prompt_cache_time = nil
+ @just_cursor_moving = false
@eof = false
@continuous_insertion_buffer = String.new(encoding: @encoding)
- @scroll_partial_screen = nil
- @prev_mode_string = nil
+ @scroll_partial_screen = 0
@drop_terminate_spaces = false
@in_pasting = false
@auto_indent_proc = nil
@dialogs = []
- @previous_rendered_dialog_y = 0
- @last_key = nil
+ @interrupted = false
@resized = false
+ @cache = {}
+ @rendered_screen = RenderedScreen.new(base_y: 0, lines: [], cursor_y: 0)
+ @past_lines = []
+ @undoing = false
reset_line
end
def reset_line
- @cursor = 0
- @cursor_max = 0
@byte_pointer = 0
@buffer_of_lines = [String.new(encoding: @encoding)]
@line_index = 0
- @previous_line_index = nil
- @line = @buffer_of_lines[0]
- @first_line_started_from = 0
- @move_up = 0
- @started_from = 0
- @highest_in_this = 1
- @highest_in_all = 1
+ @cache.clear
@line_backup_in_history = nil
@multibyte_buffer = String.new(encoding: 'ASCII-8BIT')
- @check_new_auto_indent = false
end
def multiline_on
@@ -319,68 +272,44 @@ class Reline::LineEditor
@is_multiline = false
end
- private def calculate_height_by_lines(lines, prompt)
- result = 0
- prompt_list = prompt.is_a?(Array) ? prompt : nil
- lines.each_with_index { |line, i|
- prompt = prompt_list[i] if prompt_list and prompt_list[i]
- result += calculate_height_by_width(calculate_width(prompt, true) + calculate_width(line))
- }
- result
- end
-
private def insert_new_line(cursor_line, next_line)
- @line = cursor_line
@buffer_of_lines.insert(@line_index + 1, String.new(next_line, encoding: @encoding))
- @previous_line_index = @line_index
+ @buffer_of_lines[@line_index] = cursor_line
@line_index += 1
- @just_cursor_moving = false
- end
-
- private def calculate_height_by_width(width)
- width.div(@screen_size.last) + 1
- end
-
- private def split_by_width(str, max_width)
- Reline::Unicode.split_by_width(str, max_width, @encoding)
- end
-
- private def scroll_down(val)
- if val <= @rest_height
- Reline::IOGate.move_cursor_down(val)
- @rest_height -= val
- else
- Reline::IOGate.move_cursor_down(@rest_height)
- Reline::IOGate.scroll_down(val - @rest_height)
- @rest_height = 0
+ @byte_pointer = 0
+ if @auto_indent_proc && !@in_pasting
+ if next_line.empty?
+ (
+ # For compatibility, use this calculation instead of just `process_auto_indent @line_index - 1, cursor_dependent: false`
+ indent1 = @auto_indent_proc.(@buffer_of_lines.take(@line_index - 1).push(''), @line_index - 1, 0, true)
+ indent2 = @auto_indent_proc.(@buffer_of_lines.take(@line_index), @line_index - 1, @buffer_of_lines[@line_index - 1].bytesize, false)
+ indent = indent2 || indent1
+ @buffer_of_lines[@line_index - 1] = ' ' * indent + @buffer_of_lines[@line_index - 1].gsub(/\A\s*/, '')
+ )
+ process_auto_indent @line_index, add_newline: true
+ else
+ process_auto_indent @line_index - 1, cursor_dependent: false
+ process_auto_indent @line_index, add_newline: true # Need for compatibility
+ process_auto_indent @line_index, cursor_dependent: false
+ end
end
end
- private def move_cursor_up(val)
- if val > 0
- Reline::IOGate.move_cursor_up(val)
- @rest_height += val
- elsif val < 0
- move_cursor_down(-val)
- end
+ private def split_by_width(str, max_width, offset: 0)
+ Reline::Unicode.split_by_width(str, max_width, @encoding, offset: offset)
end
- private def move_cursor_down(val)
- if val > 0
- Reline::IOGate.move_cursor_down(val)
- @rest_height -= val
- @rest_height = 0 if @rest_height < 0
- elsif val < 0
- move_cursor_up(-val)
- end
+ def current_byte_pointer_cursor
+ calculate_width(current_line.byteslice(0, @byte_pointer))
end
- private def calculate_nearest_cursor(line_to_calc = @line, cursor = @cursor, started_from = @started_from, byte_pointer = @byte_pointer, update = true)
+ private def calculate_nearest_cursor(cursor)
+ line_to_calc = current_line
new_cursor_max = calculate_width(line_to_calc)
new_cursor = 0
new_byte_pointer = 0
height = 1
- max_width = @screen_size.last
+ max_width = screen_width
if @config.editing_mode_is?(:vi_command)
last_byte_size = Reline::Unicode.get_prev_mbchar_size(line_to_calc, line_to_calc.bytesize)
if last_byte_size > 0
@@ -406,113 +335,242 @@ class Reline::LineEditor
end
new_byte_pointer += gc.bytesize
end
- new_started_from = height - 1
- if update
- @cursor = new_cursor
- @cursor_max = new_cursor_max
- @started_from = new_started_from
- @byte_pointer = new_byte_pointer
- else
- [new_cursor, new_cursor_max, new_started_from, new_byte_pointer]
+ @byte_pointer = new_byte_pointer
+ end
+
+ def with_cache(key, *deps)
+ cached_deps, value = @cache[key]
+ if cached_deps != deps
+ @cache[key] = [deps, value = yield(*deps, cached_deps, value)]
end
+ value
end
- def rerender_all
- @rerender_all = true
- process_insert(force: true)
- rerender
+ def modified_lines
+ with_cache(__method__, whole_lines, finished?) do |whole, complete|
+ modify_lines(whole, complete)
+ end
end
- def rerender
- return if @line.nil?
- if @menu_info
- scroll_down(@highest_in_all - @first_line_started_from)
- @rerender_all = true
+ def prompt_list
+ with_cache(__method__, whole_lines, check_mode_string, @vi_arg, @searching_prompt) do |lines, mode_string|
+ check_multiline_prompt(lines, mode_string)
end
- if @menu_info
- show_menu
- @menu_info = nil
- end
- prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
- cursor_column = (prompt_width + @cursor) % @screen_size.last
- if @cleared
- clear_screen_buffer(prompt, prompt_list, prompt_width)
- @cleared = false
- return
+ end
+
+ def screen_height
+ @screen_size.first
+ end
+
+ def screen_width
+ @screen_size.last
+ end
+
+ def screen_scroll_top
+ @scroll_partial_screen
+ end
+
+ def wrapped_prompt_and_input_lines
+ with_cache(__method__, @buffer_of_lines.size, modified_lines, prompt_list, screen_width) do |n, lines, prompts, width, prev_cache_key, cached_value|
+ prev_n, prev_lines, prev_prompts, prev_width = prev_cache_key
+ cached_wraps = {}
+ if prev_width == width
+ prev_n.times do |i|
+ cached_wraps[[prev_prompts[i], prev_lines[i]]] = cached_value[i]
+ end
+ end
+
+ n.times.map do |i|
+ prompt = prompts[i] || ''
+ line = lines[i] || ''
+ if (cached = cached_wraps[[prompt, line]])
+ next cached
+ end
+ *wrapped_prompts, code_line_prompt = split_by_width(prompt, width).first.compact
+ wrapped_lines = split_by_width(line, width, offset: calculate_width(code_line_prompt, true)).first.compact
+ wrapped_prompts.map { |p| [p, ''] } + [[code_line_prompt, wrapped_lines.first]] + wrapped_lines.drop(1).map { |c| ['', c] }
+ end
end
- if @is_multiline and finished? and @scroll_partial_screen
- # Re-output all code higher than the screen when finished.
- Reline::IOGate.move_cursor_up(@first_line_started_from + @started_from - @scroll_partial_screen)
- Reline::IOGate.move_cursor_column(0)
- @scroll_partial_screen = nil
- new_lines = whole_lines
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
- modify_lines(new_lines).each_with_index do |line, index|
- @output.write "#{prompt_list ? prompt_list[index] : prompt}#{line}\r\n"
- Reline::IOGate.erase_after_cursor
- end
- @output.flush
- clear_dialog(cursor_column)
- return
+ end
+
+ def calculate_overlay_levels(overlay_levels)
+ levels = []
+ overlay_levels.each do |x, w, l|
+ levels.fill(l, x, w)
end
- new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
- rendered = false
- if @add_newline_to_end_of_buffer
- clear_dialog_with_trap_key(cursor_column)
- rerender_added_newline(prompt, prompt_width, prompt_list)
- @add_newline_to_end_of_buffer = false
- else
- if @just_cursor_moving and not @rerender_all
- clear_dialog_with_trap_key(cursor_column)
- rendered = just_move_cursor
- @just_cursor_moving = false
- return
- elsif @previous_line_index or new_highest_in_this != @highest_in_this
- clear_dialog_with_trap_key(cursor_column)
- rerender_changed_current_line
- @previous_line_index = nil
- rendered = true
- elsif @rerender_all
- rerender_all_lines
- @rerender_all = false
- rendered = true
+ levels
+ end
+
+ def render_line_differential(old_items, new_items)
+ old_levels = calculate_overlay_levels(old_items.zip(new_items).each_with_index.map {|((x, w, c), (nx, _nw, nc)), i| [x, w, c == nc && x == nx ? i : -1] if x }.compact)
+ new_levels = calculate_overlay_levels(new_items.each_with_index.map { |(x, w), i| [x, w, i] if x }.compact).take(screen_width)
+ base_x = 0
+ new_levels.zip(old_levels).chunk { |n, o| n == o ? :skip : n || :blank }.each do |level, chunk|
+ width = chunk.size
+ if level == :skip
+ # do nothing
+ elsif level == :blank
+ Reline::IOGate.move_cursor_column base_x
+ @output.write "#{Reline::IOGate::RESET_COLOR}#{' ' * width}"
else
+ x, w, content = new_items[level]
+ cover_begin = base_x != 0 && new_levels[base_x - 1] == level
+ cover_end = new_levels[base_x + width] == level
+ pos = 0
+ unless x == base_x && w == width
+ content, pos = Reline::Unicode.take_mbchar_range(content, base_x - x, width, cover_begin: cover_begin, cover_end: cover_end, padding: true)
+ end
+ Reline::IOGate.move_cursor_column x + pos
+ @output.write "#{Reline::IOGate::RESET_COLOR}#{content}#{Reline::IOGate::RESET_COLOR}"
end
+ base_x += width
end
- if @is_multiline
- if finished?
- # Always rerender on finish because output_modifier_proc may return a different output.
- new_lines = whole_lines
- line = modify_lines(new_lines)[@line_index]
- clear_dialog(cursor_column)
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
- render_partial(prompt, prompt_width, line, @first_line_started_from)
- move_cursor_down(@highest_in_all - (@first_line_started_from + @highest_in_this - 1) - 1)
- scroll_down(1)
- Reline::IOGate.move_cursor_column(0)
- Reline::IOGate.erase_after_cursor
- else
- if not rendered and not @in_pasting
- line = modify_lines(whole_lines)[@line_index]
- prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
- render_partial(prompt, prompt_width, line, @first_line_started_from)
- end
- render_dialog(cursor_column)
+ if old_levels.size > new_levels.size
+ Reline::IOGate.move_cursor_column new_levels.size
+ Reline::IOGate.erase_after_cursor
+ end
+ end
+
+ # Calculate cursor position in word wrapped content.
+ def wrapped_cursor_position
+ prompt_width = calculate_width(prompt_list[@line_index], true)
+ line_before_cursor = whole_lines[@line_index].byteslice(0, @byte_pointer)
+ wrapped_line_before_cursor = split_by_width(' ' * prompt_width + line_before_cursor, screen_width).first.compact
+ wrapped_cursor_y = wrapped_prompt_and_input_lines[0...@line_index].sum(&:size) + wrapped_line_before_cursor.size - 1
+ wrapped_cursor_x = calculate_width(wrapped_line_before_cursor.last)
+ [wrapped_cursor_x, wrapped_cursor_y]
+ end
+
+ def clear_dialogs
+ @dialogs.each do |dialog|
+ dialog.contents = nil
+ dialog.trap_key = nil
+ end
+ end
+
+ def update_dialogs(key = nil)
+ wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position
+ @dialogs.each do |dialog|
+ dialog.trap_key = nil
+ update_each_dialog(dialog, wrapped_cursor_x, wrapped_cursor_y - screen_scroll_top, key)
+ end
+ end
+
+ def render_finished
+ clear_rendered_lines
+ render_full_content
+ end
+
+ def clear_rendered_lines
+ Reline::IOGate.move_cursor_up @rendered_screen.cursor_y
+ Reline::IOGate.move_cursor_column 0
+
+ num_lines = @rendered_screen.lines.size
+ return unless num_lines && num_lines >= 1
+
+ Reline::IOGate.move_cursor_down num_lines - 1
+ (num_lines - 1).times do
+ Reline::IOGate.erase_after_cursor
+ Reline::IOGate.move_cursor_up 1
+ end
+ Reline::IOGate.erase_after_cursor
+ @rendered_screen.lines = []
+ @rendered_screen.cursor_y = 0
+ end
+
+ def render_full_content
+ lines = @buffer_of_lines.size.times.map do |i|
+ line = prompt_list[i] + modified_lines[i]
+ wrapped_lines, = split_by_width(line, screen_width)
+ wrapped_lines.last.empty? ? "#{line} " : line
+ end
+ @output.puts lines.map { |l| "#{l}\r\n" }.join
+ end
+
+ def print_nomultiline_prompt(prompt)
+ return unless prompt && !@is_multiline
+
+ # Readline's test `TestRelineAsReadline#test_readline` requires first output to be prompt, not cursor reset escape sequence.
+ @rendered_screen.lines = [[[0, Reline::Unicode.calculate_width(prompt, true), prompt]]]
+ @rendered_screen.cursor_y = 0
+ @output.write prompt
+ end
+
+ def render_differential
+ wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position
+
+ rendered_lines = @rendered_screen.lines
+ new_lines = wrapped_prompt_and_input_lines.flatten(1)[screen_scroll_top, screen_height].map do |prompt, line|
+ prompt_width = Reline::Unicode.calculate_width(prompt, true)
+ [[0, prompt_width, prompt], [prompt_width, Reline::Unicode.calculate_width(line, true), line]]
+ end
+ if @menu_info
+ @menu_info.lines(screen_width).each do |item|
+ new_lines << [[0, Reline::Unicode.calculate_width(item), item]]
end
- @buffer_of_lines[@line_index] = @line
- @rest_height = 0 if @scroll_partial_screen
- else
- line = modify_lines(whole_lines)[@line_index]
- render_partial(prompt, prompt_width, line, 0)
- if finished?
- scroll_down(1)
- Reline::IOGate.move_cursor_column(0)
- Reline::IOGate.erase_after_cursor
+ @menu_info = nil # TODO: do not change state here
+ end
+
+ @dialogs.each_with_index do |dialog, index|
+ next unless dialog.contents
+
+ x_range, y_range = dialog_range dialog, wrapped_cursor_y - screen_scroll_top
+ y_range.each do |row|
+ next if row < 0 || row >= screen_height
+ dialog_rows = new_lines[row] ||= []
+ # index 0 is for prompt, index 1 is for line, index 2.. is for dialog
+ dialog_rows[index + 2] = [x_range.begin, dialog.width, dialog.contents[row - y_range.begin]]
+ end
+ end
+
+ cursor_y = @rendered_screen.cursor_y
+ if new_lines != rendered_lines
+ # Hide cursor while rendering to avoid cursor flickering.
+ Reline::IOGate.hide_cursor
+ num_lines = [[new_lines.size, rendered_lines.size].max, screen_height].min
+ if @rendered_screen.base_y + num_lines > screen_height
+ Reline::IOGate.scroll_down(num_lines - cursor_y - 1)
+ @rendered_screen.base_y = screen_height - num_lines
+ cursor_y = num_lines - 1
end
+ num_lines.times do |i|
+ rendered_line = rendered_lines[i] || []
+ line_to_render = new_lines[i] || []
+ next if rendered_line == line_to_render
+
+ Reline::IOGate.move_cursor_down i - cursor_y
+ cursor_y = i
+ unless rendered_lines[i]
+ Reline::IOGate.move_cursor_column 0
+ Reline::IOGate.erase_after_cursor
+ end
+ render_line_differential(rendered_line, line_to_render)
+ end
+ @rendered_screen.lines = new_lines
+ Reline::IOGate.show_cursor
end
+ y = wrapped_cursor_y - screen_scroll_top
+ Reline::IOGate.move_cursor_column wrapped_cursor_x
+ Reline::IOGate.move_cursor_down y - cursor_y
+ @rendered_screen.cursor_y = y
+ new_lines.size - y
+ end
+
+ def upper_space_height(wrapped_cursor_y)
+ wrapped_cursor_y - screen_scroll_top
+ end
+
+ def rest_height(wrapped_cursor_y)
+ screen_height - wrapped_cursor_y + screen_scroll_top - @rendered_screen.base_y - 1
+ end
+
+ def rerender
+ render_differential unless @in_pasting
end
class DialogProcScope
+ CompletionJourneyData = Struct.new(:preposing, :postposing, :list, :pointer)
+
def initialize(line_editor, config, proc_to_exec, context)
@line_editor = line_editor
@config = config
@@ -563,21 +621,20 @@ class Reline::LineEditor
end
def screen_width
- @line_editor.instance_variable_get(:@screen_size).last
+ @line_editor.screen_width
end
def screen_height
- @line_editor.instance_variable_get(:@screen_size).first
+ @line_editor.screen_height
end
def preferred_dialog_height
- rest_height = @line_editor.instance_variable_get(:@rest_height)
- scroll_partial_screen = @line_editor.instance_variable_get(:@scroll_partial_screen) || 0
- [cursor_pos.y - scroll_partial_screen, rest_height, (screen_height + 6) / 5].max
+ _wrapped_cursor_x, wrapped_cursor_y = @line_editor.wrapped_cursor_position
+ [@line_editor.upper_space_height(wrapped_cursor_y), @line_editor.rest_height(wrapped_cursor_y), (screen_height + 6) / 5].max
end
def completion_journey_data
- @line_editor.instance_variable_get(:@completion_journey_data)
+ @line_editor.dialog_proc_scope_completion_journey_data
end
def config
@@ -646,27 +703,6 @@ class Reline::LineEditor
end
DIALOG_DEFAULT_HEIGHT = 20
- private def render_dialog(cursor_column)
- changes = @dialogs.map do |dialog|
- old_dialog = dialog.dup
- update_each_dialog(dialog, cursor_column)
- [old_dialog, dialog]
- end
- render_dialog_changes(changes, cursor_column)
- end
-
- private def padding_space_with_escape_sequences(str, width)
- padding_width = width - calculate_width(str, true)
- # padding_width should be only positive value. But macOS and Alacritty returns negative value.
- padding_width = 0 if padding_width < 0
- str + (' ' * padding_width)
- end
-
- private def range_subtract(base_ranges, subtract_ranges)
- indices = base_ranges.flat_map(&:to_a).uniq.sort - subtract_ranges.flat_map(&:to_a)
- chunks = indices.chunk_while { |a, b| a + 1 == b }
- chunks.map { |a| a.first...a.last + 1 }
- end
private def dialog_range(dialog, dialog_y)
x_range = dialog.column...dialog.column + dialog.width
@@ -674,106 +710,9 @@ class Reline::LineEditor
[x_range, y_range]
end
- private def render_dialog_changes(changes, cursor_column)
- # Collect x-coordinate range and content of previous and current dialogs for each line
- old_dialog_ranges = {}
- new_dialog_ranges = {}
- new_dialog_contents = {}
- changes.each do |old_dialog, new_dialog|
- if old_dialog.contents
- x_range, y_range = dialog_range(old_dialog, @previous_rendered_dialog_y)
- y_range.each do |y|
- (old_dialog_ranges[y] ||= []) << x_range
- end
- end
- if new_dialog.contents
- x_range, y_range = dialog_range(new_dialog, @first_line_started_from + @started_from)
- y_range.each do |y|
- (new_dialog_ranges[y] ||= []) << x_range
- (new_dialog_contents[y] ||= []) << [x_range, new_dialog.contents[y - y_range.begin]]
- end
- end
- end
- return if old_dialog_ranges.empty? && new_dialog_ranges.empty?
-
- # Calculate x-coordinate ranges to restore text that was hidden behind dialogs for each line
- ranges_to_restore = {}
- subtract_cache = {}
- old_dialog_ranges.each do |y, old_x_ranges|
- new_x_ranges = new_dialog_ranges[y] || []
- ranges = subtract_cache[[old_x_ranges, new_x_ranges]] ||= range_subtract(old_x_ranges, new_x_ranges)
- ranges_to_restore[y] = ranges if ranges.any?
- end
-
- # Create visual_lines for restoring text hidden behind dialogs
- if ranges_to_restore.any?
- lines = whole_lines
- prompt, _prompt_width, prompt_list = check_multiline_prompt(lines, force_recalc: true)
- modified_lines = modify_lines(lines, force_recalc: true)
- visual_lines = []
- modified_lines.each_with_index { |l, i|
- pr = prompt_list ? prompt_list[i] : prompt
- vl, = split_by_width(pr + l, @screen_size.last)
- vl.compact!
- visual_lines.concat(vl)
- }
- end
-
- # Clear and rerender all dialogs line by line
- Reline::IOGate.hide_cursor
- ymin, ymax = (ranges_to_restore.keys + new_dialog_ranges.keys).minmax
- scroll_partial_screen = @scroll_partial_screen || 0
- screen_y_range = scroll_partial_screen..(scroll_partial_screen + @screen_height - 1)
- ymin = ymin.clamp(screen_y_range.begin, screen_y_range.end)
- ymax = ymax.clamp(screen_y_range.begin, screen_y_range.end)
- dialog_y = @first_line_started_from + @started_from
- cursor_y = dialog_y
- if @highest_in_all <= ymax
- scroll_down(ymax - cursor_y)
- move_cursor_up(ymax - cursor_y)
- end
- (ymin..ymax).each do |y|
- move_cursor_down(y - cursor_y)
- cursor_y = y
- new_x_ranges = new_dialog_ranges[y]
- restore_ranges = ranges_to_restore[y]
- # Restore text that was hidden behind dialogs
- if restore_ranges
- line = visual_lines[y] || ''
- restore_ranges.each do |range|
- col = range.begin
- width = range.end - range.begin
- s = padding_space_with_escape_sequences(Reline::Unicode.take_range(line, col, width), width)
- Reline::IOGate.move_cursor_column(col)
- @output.write "\e[0m#{s}\e[0m"
- end
- max_column = [calculate_width(line, true), new_x_ranges&.map(&:end)&.max || 0].max
- if max_column < restore_ranges.map(&:end).max
- Reline::IOGate.move_cursor_column(max_column)
- Reline::IOGate.erase_after_cursor
- end
- end
- # Render dialog contents
- new_dialog_contents[y]&.each do |x_range, content|
- Reline::IOGate.move_cursor_column(x_range.begin)
- @output.write "\e[0m#{content}\e[0m"
- end
- end
- move_cursor_up(cursor_y - dialog_y)
- Reline::IOGate.move_cursor_column(cursor_column)
- Reline::IOGate.show_cursor
-
- @previous_rendered_dialog_y = dialog_y
- end
-
- private def update_each_dialog(dialog, cursor_column)
- if @in_pasting
- dialog.contents = nil
- dialog.trap_key = nil
- return
- end
- dialog.set_cursor_pos(cursor_column, @first_line_started_from + @started_from)
- dialog_render_info = dialog.call(@last_key)
+ private def update_each_dialog(dialog, cursor_column, cursor_row, key = nil)
+ dialog.set_cursor_pos(cursor_column, cursor_row)
+ dialog_render_info = dialog.call(key)
if dialog_render_info.nil? or dialog_render_info.contents.nil? or dialog_render_info.contents.empty?
dialog.contents = nil
dialog.trap_key = nil
@@ -813,23 +752,22 @@ class Reline::LineEditor
else
scrollbar_pos = nil
end
- upper_space = @first_line_started_from - @started_from
dialog.column = dialog_render_info.pos.x
dialog.width += @block_elem_width if scrollbar_pos
- diff = (dialog.column + dialog.width) - (@screen_size.last)
+ diff = (dialog.column + dialog.width) - screen_width
if diff > 0
dialog.column -= diff
end
- if (@rest_height - dialog_render_info.pos.y) >= height
+ if rest_height(screen_scroll_top + cursor_row) - dialog_render_info.pos.y >= height
dialog.vertical_offset = dialog_render_info.pos.y + 1
- elsif upper_space >= height
+ elsif cursor_row >= height
dialog.vertical_offset = dialog_render_info.pos.y - height
else
dialog.vertical_offset = dialog_render_info.pos.y + 1
end
if dialog.column < 0
dialog.column = 0
- dialog.width = @screen_size.last
+ dialog.width = screen_width
end
face = Reline::Face[dialog_render_info.face || :default]
scrollbar_sgr = face[:scrollbar]
@@ -838,7 +776,7 @@ class Reline::LineEditor
dialog.contents = contents.map.with_index do |item, i|
line_sgr = i == pointer ? enhanced_sgr : default_sgr
str_width = dialog.width - (scrollbar_pos.nil? ? 0 : @block_elem_width)
- str = padding_space_with_escape_sequences(Reline::Unicode.take_range(item, 0, str_width), str_width)
+ str, = Reline::Unicode.take_mbchar_range(item, 0, str_width, padding: true)
colored_content = "#{line_sgr}#{str}"
if scrollbar_pos
if scrollbar_pos <= (i * 2) and (i * 2 + 1) < (scrollbar_pos + bar_height)
@@ -856,379 +794,20 @@ class Reline::LineEditor
end
end
- private def clear_dialog(cursor_column)
- changes = @dialogs.map do |dialog|
- old_dialog = dialog.dup
- dialog.contents = nil
- [old_dialog, dialog]
- end
- render_dialog_changes(changes, cursor_column)
- end
-
- private def clear_dialog_with_trap_key(cursor_column)
- clear_dialog(cursor_column)
- @dialogs.each do |dialog|
- dialog.trap_key = nil
- end
- end
-
- private def calculate_scroll_partial_screen(highest_in_all, cursor_y)
- if @screen_height < highest_in_all
- old_scroll_partial_screen = @scroll_partial_screen
- if cursor_y == 0
- @scroll_partial_screen = 0
- elsif cursor_y == (highest_in_all - 1)
- @scroll_partial_screen = highest_in_all - @screen_height
- else
- if @scroll_partial_screen
- if cursor_y <= @scroll_partial_screen
- @scroll_partial_screen = cursor_y
- elsif (@scroll_partial_screen + @screen_height - 1) < cursor_y
- @scroll_partial_screen = cursor_y - (@screen_height - 1)
- end
- else
- if cursor_y > (@screen_height - 1)
- @scroll_partial_screen = cursor_y - (@screen_height - 1)
- else
- @scroll_partial_screen = 0
- end
- end
- end
- if @scroll_partial_screen != old_scroll_partial_screen
- @rerender_all = true
- end
- else
- if @scroll_partial_screen
- @rerender_all = true
- end
- @scroll_partial_screen = nil
- end
- end
-
- private def rerender_added_newline(prompt, prompt_width, prompt_list)
- @buffer_of_lines[@previous_line_index] = @line
- @line = @buffer_of_lines[@line_index]
- @previous_line_index = nil
- if @in_pasting
- scroll_down(1)
- else
- lines = whole_lines
- prev_line_prompt = @prompt_proc ? prompt_list[@line_index - 1] : prompt
- prev_line_prompt_width = @prompt_proc ? calculate_width(prev_line_prompt, true) : prompt_width
- prev_line = modify_lines(lines)[@line_index - 1]
- move_cursor_up(@started_from)
- render_partial(prev_line_prompt, prev_line_prompt_width, prev_line, @first_line_started_from + @started_from, with_control: false)
- scroll_down(1)
- render_partial(prompt, prompt_width, @line, @first_line_started_from + @started_from + 1, with_control: false)
- end
- @cursor = @cursor_max = calculate_width(@line)
- @byte_pointer = @line.bytesize
- @highest_in_all += @highest_in_this
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
- @first_line_started_from += @started_from + 1
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
- end
-
- def just_move_cursor
- prompt, prompt_width, prompt_list = check_multiline_prompt(@buffer_of_lines)
- move_cursor_up(@started_from)
- new_first_line_started_from =
- if @line_index.zero?
- 0
- else
- calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
- end
- first_line_diff = new_first_line_started_from - @first_line_started_from
- @cursor, @cursor_max, _, @byte_pointer = calculate_nearest_cursor(@buffer_of_lines[@line_index], @cursor, @started_from, @byte_pointer, false)
- new_started_from = calculate_height_by_width(prompt_width + @cursor) - 1
- calculate_scroll_partial_screen(@highest_in_all, new_first_line_started_from + new_started_from)
- @previous_line_index = nil
- @line = @buffer_of_lines[@line_index]
- if @rerender_all
- rerender_all_lines
- @rerender_all = false
- true
- else
- @first_line_started_from = new_first_line_started_from
- @started_from = new_started_from
- move_cursor_down(first_line_diff + @started_from)
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
- false
- end
- end
-
- private def rerender_changed_current_line
- new_lines = whole_lines
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
- all_height = calculate_height_by_lines(new_lines, prompt_list || prompt)
- diff = all_height - @highest_in_all
- move_cursor_down(@highest_in_all - @first_line_started_from - @started_from - 1)
- if diff > 0
- scroll_down(diff)
- move_cursor_up(all_height - 1)
- elsif diff < 0
- (-diff).times do
- Reline::IOGate.move_cursor_column(0)
- Reline::IOGate.erase_after_cursor
- move_cursor_up(1)
- end
- move_cursor_up(all_height - 1)
- else
- move_cursor_up(all_height - 1)
- end
- @highest_in_all = all_height
- back = render_whole_lines(new_lines, prompt_list || prompt, prompt_width)
- move_cursor_up(back)
- if @previous_line_index
- @buffer_of_lines[@previous_line_index] = @line
- @line = @buffer_of_lines[@line_index]
- end
- @first_line_started_from =
- if @line_index.zero?
- 0
- else
- calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
- end
- if @prompt_proc
- prompt = prompt_list[@line_index]
- prompt_width = calculate_width(prompt, true)
- end
- move_cursor_down(@first_line_started_from)
- calculate_nearest_cursor
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
- move_cursor_down(@started_from)
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
- end
-
- private def rerender_all_lines
- move_cursor_up(@first_line_started_from + @started_from)
- Reline::IOGate.move_cursor_column(0)
- back = 0
- new_buffer = whole_lines
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer)
- new_buffer.each_with_index do |line, index|
- prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
- width = prompt_width + calculate_width(line)
- height = calculate_height_by_width(width)
- back += height
- end
- old_highest_in_all = @highest_in_all
- if @line_index.zero?
- new_first_line_started_from = 0
- else
- new_first_line_started_from = calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list || prompt)
- end
- new_started_from = calculate_height_by_width(prompt_width + @cursor) - 1
- calculate_scroll_partial_screen(back, new_first_line_started_from + new_started_from)
- if @scroll_partial_screen
- move_cursor_up(@first_line_started_from + @started_from)
- scroll_down(@screen_height - 1)
- move_cursor_up(@screen_height)
- Reline::IOGate.move_cursor_column(0)
- elsif back > old_highest_in_all
- scroll_down(back - 1)
- move_cursor_up(back - 1)
- elsif back < old_highest_in_all
- scroll_down(back)
- Reline::IOGate.erase_after_cursor
- (old_highest_in_all - back - 1).times do
- scroll_down(1)
- Reline::IOGate.erase_after_cursor
- end
- move_cursor_up(old_highest_in_all - 1)
- end
- render_whole_lines(new_buffer, prompt_list || prompt, prompt_width)
- if @prompt_proc
- prompt = prompt_list[@line_index]
- prompt_width = calculate_width(prompt, true)
- end
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
- @highest_in_all = back
- @first_line_started_from = new_first_line_started_from
- @started_from = new_started_from
- if @scroll_partial_screen
- Reline::IOGate.move_cursor_up(@screen_height - (@first_line_started_from + @started_from - @scroll_partial_screen) - 1)
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
- else
- move_cursor_down(@first_line_started_from + @started_from - back + 1)
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
- end
- end
-
- private def render_whole_lines(lines, prompt, prompt_width)
- rendered_height = 0
- modify_lines(lines).each_with_index do |line, index|
- if prompt.is_a?(Array)
- line_prompt = prompt[index]
- prompt_width = calculate_width(line_prompt, true)
- else
- line_prompt = prompt
- end
- height = render_partial(line_prompt, prompt_width, line, rendered_height, with_control: false)
- if index < (lines.size - 1)
- if @scroll_partial_screen
- if (@scroll_partial_screen - height) < rendered_height and (@scroll_partial_screen + @screen_height - 1) >= (rendered_height + height)
- move_cursor_down(1)
- end
- else
- scroll_down(1)
- end
- rendered_height += height
- else
- rendered_height += height - 1
- end
- end
- rendered_height
- end
-
- private def render_partial(prompt, prompt_width, line_to_render, this_started_from, with_control: true)
- visual_lines, height = split_by_width(line_to_render.nil? ? prompt : prompt + line_to_render, @screen_size.last)
- cursor_up_from_last_line = 0
- if @scroll_partial_screen
- last_visual_line = this_started_from + (height - 1)
- last_screen_line = @scroll_partial_screen + (@screen_height - 1)
- if (@scroll_partial_screen - this_started_from) >= height
- # Render nothing because this line is before the screen.
- visual_lines = []
- elsif this_started_from > last_screen_line
- # Render nothing because this line is after the screen.
- visual_lines = []
- else
- deleted_lines_before_screen = []
- if @scroll_partial_screen > this_started_from and last_visual_line >= @scroll_partial_screen
- # A part of visual lines are before the screen.
- deleted_lines_before_screen = visual_lines.shift((@scroll_partial_screen - this_started_from) * 2)
- deleted_lines_before_screen.compact!
- end
- if this_started_from <= last_screen_line and last_screen_line < last_visual_line
- # A part of visual lines are after the screen.
- visual_lines.pop((last_visual_line - last_screen_line) * 2)
- end
- move_cursor_up(deleted_lines_before_screen.size - @started_from)
- cursor_up_from_last_line = @started_from - deleted_lines_before_screen.size
- end
- end
- if with_control
- if height > @highest_in_this
- diff = height - @highest_in_this
- scroll_down(diff)
- @highest_in_all += diff
- @highest_in_this = height
- move_cursor_up(diff)
- elsif height < @highest_in_this
- diff = @highest_in_this - height
- @highest_in_all -= diff
- @highest_in_this = height
- end
- move_cursor_up(@started_from)
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
- cursor_up_from_last_line = height - 1 - @started_from
- end
- if Reline::Unicode::CSI_REGEXP.match?(prompt + line_to_render)
- @output.write "\e[0m" # clear character decorations
- end
- visual_lines.each_with_index do |line, index|
- Reline::IOGate.move_cursor_column(0)
- if line.nil?
- if calculate_width(visual_lines[index - 1], true) == Reline::IOGate.get_screen_size.last
- # reaches the end of line
- if Reline::IOGate.win? and Reline::IOGate.win_legacy_console?
- # A newline is automatically inserted if a character is rendered at
- # eol on command prompt.
- else
- # When the cursor is at the end of the line and erases characters
- # after the cursor, some terminals delete the character at the
- # cursor position.
- move_cursor_down(1)
- Reline::IOGate.move_cursor_column(0)
- end
- else
- Reline::IOGate.erase_after_cursor
- move_cursor_down(1)
- Reline::IOGate.move_cursor_column(0)
- end
- next
- end
- @output.write line
- if Reline::IOGate.win? and Reline::IOGate.win_legacy_console? 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
- @pre_input_hook&.call
- end
- end
- unless visual_lines.empty?
- Reline::IOGate.erase_after_cursor
- Reline::IOGate.move_cursor_column(0)
- end
- if with_control
- # Just after rendring, so the cursor is on the last line.
- if finished?
- Reline::IOGate.move_cursor_column(0)
- else
- # Moves up from bottom of lines to the cursor position.
- move_cursor_up(cursor_up_from_last_line)
- # This logic is buggy if a fullwidth char is wrapped because there is only one halfwidth at end of a line.
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
- end
- end
- height
- end
-
- private def modify_lines(before, force_recalc: false)
- return before if !force_recalc && (before.nil? || before.empty? || simplified_rendering?)
-
- if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: finished?)
+ private def modify_lines(before, complete)
+ if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: complete)
after.lines("\n").map { |l| l.chomp('') }
else
- before
- end
- end
-
- private def show_menu
- scroll_down(@highest_in_all - @first_line_started_from)
- @rerender_all = true
- @menu_info.list.sort!.each do |item|
- Reline::IOGate.move_cursor_column(0)
- @output.write item
- @output.flush
- scroll_down(1)
- end
- scroll_down(@highest_in_all - 1)
- move_cursor_up(@highest_in_all - 1 - @first_line_started_from)
- end
-
- private def clear_screen_buffer(prompt, prompt_list, prompt_width)
- Reline::IOGate.clear_screen
- back = 0
- modify_lines(whole_lines).each_with_index do |line, index|
- if @prompt_proc
- pr = prompt_list[index]
- height = render_partial(pr, calculate_width(pr), line, back, with_control: false)
- else
- height = render_partial(prompt, prompt_width, line, back, with_control: false)
- end
- if index < (@buffer_of_lines.size - 1)
- move_cursor_down(1)
- back += height
- end
+ before.map { |l| Reline::Unicode.escape_for_print(l) }
end
- move_cursor_up(back)
- move_cursor_down(@first_line_started_from + @started_from)
- @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
end
def editing_mode
@config.editing_mode
end
- private def menu(target, list)
- @menu_info = MenuInfo.new(target, list)
+ private def menu(_target, list)
+ @menu_info = MenuInfo.new(list)
end
private def complete_internal_proc(list, is_menu)
@@ -1256,7 +835,7 @@ class Reline::LineEditor
item_mbchars = item.grapheme_clusters
end
size = [memo_mbchars.size, item_mbchars.size].min
- result = ''
+ result = +''
size.times do |i|
if @config.completion_ignore_case
if memo_mbchars[i].casecmp?(item_mbchars[i])
@@ -1277,9 +856,9 @@ class Reline::LineEditor
[target, preposing, completed, postposing]
end
- private def complete(list, just_show_list = false)
+ private def perform_completion(list, just_show_list)
case @completion_state
- when CompletionState::NORMAL, CompletionState::JOURNEY
+ when CompletionState::NORMAL
@completion_state = CompletionState::COMPLETION
when CompletionState::PERFECT_MATCH
@dig_perfect_match_proc&.(@perfect_matched)
@@ -1306,100 +885,80 @@ class Reline::LineEditor
@completion_state = CompletionState::PERFECT_MATCH
else
@completion_state = CompletionState::MENU_WITH_PERFECT_MATCH
+ perform_completion(list, true) if @config.show_all_if_ambiguous
end
@perfect_matched = completed
else
@completion_state = CompletionState::MENU
+ perform_completion(list, true) if @config.show_all_if_ambiguous
end
if not just_show_list and target < completed
- @line = (preposing + completed + completion_append_character.to_s + postposing).split("\n")[@line_index] || String.new(encoding: @encoding)
- line_to_pointer = (preposing + completed + completion_append_character.to_s).split("\n").last || String.new(encoding: @encoding)
- @cursor_max = calculate_width(@line)
- @cursor = calculate_width(line_to_pointer)
+ @buffer_of_lines[@line_index] = (preposing + completed + completion_append_character.to_s + postposing).split("\n")[@line_index] || String.new(encoding: @encoding)
+ line_to_pointer = (preposing + completed + completion_append_character.to_s).split("\n")[@line_index] || String.new(encoding: @encoding)
@byte_pointer = line_to_pointer.bytesize
end
end
end
- private def move_completed_list(list, direction)
- case @completion_state
- when CompletionState::NORMAL, CompletionState::COMPLETION,
- CompletionState::MENU, CompletionState::MENU_WITH_PERFECT_MATCH
- @completion_state = CompletionState::JOURNEY
- result = retrieve_completion_block
- return if result.nil?
- preposing, target, postposing = result
- @completion_journey_data = CompletionJourneyData.new(
- preposing, postposing,
- [target] + list.select{ |item| item.start_with?(target) }, 0)
- if @completion_journey_data.list.size == 1
- @completion_journey_data.pointer = 0
- else
- case direction
- when :up
- @completion_journey_data.pointer = @completion_journey_data.list.size - 1
- when :down
- @completion_journey_data.pointer = 1
- end
- end
- @completion_state = CompletionState::JOURNEY
- else
- case direction
- when :up
- @completion_journey_data.pointer -= 1
- if @completion_journey_data.pointer < 0
- @completion_journey_data.pointer = @completion_journey_data.list.size - 1
- end
- when :down
- @completion_journey_data.pointer += 1
- if @completion_journey_data.pointer >= @completion_journey_data.list.size
- @completion_journey_data.pointer = 0
- end
- end
+ def dialog_proc_scope_completion_journey_data
+ return nil unless @completion_journey_state
+ line_index = @completion_journey_state.line_index
+ pre_lines = @buffer_of_lines[0...line_index].map { |line| line + "\n" }
+ post_lines = @buffer_of_lines[(line_index + 1)..-1].map { |line| line + "\n" }
+ DialogProcScope::CompletionJourneyData.new(
+ pre_lines.join + @completion_journey_state.pre,
+ @completion_journey_state.post + post_lines.join,
+ @completion_journey_state.list,
+ @completion_journey_state.pointer
+ )
+ end
+
+ private def move_completed_list(direction)
+ @completion_journey_state ||= retrieve_completion_journey_state
+ return false unless @completion_journey_state
+
+ if (delta = { up: -1, down: +1 }[direction])
+ @completion_journey_state.pointer = (@completion_journey_state.pointer + delta) % @completion_journey_state.list.size
end
- completed = @completion_journey_data.list[@completion_journey_data.pointer]
- new_line = (@completion_journey_data.preposing + completed + @completion_journey_data.postposing).split("\n")[@line_index]
- @line = new_line.nil? ? String.new(encoding: @encoding) : new_line
- line_to_pointer = (@completion_journey_data.preposing + completed).split("\n").last
- line_to_pointer = String.new(encoding: @encoding) if line_to_pointer.nil?
- @cursor_max = calculate_width(@line)
- @cursor = calculate_width(line_to_pointer)
- @byte_pointer = line_to_pointer.bytesize
+ completed = @completion_journey_state.list[@completion_journey_state.pointer]
+ set_current_line(@completion_journey_state.pre + completed + @completion_journey_state.post, @completion_journey_state.pre.bytesize + completed.bytesize)
+ true
+ end
+
+ private def retrieve_completion_journey_state
+ preposing, target, postposing = retrieve_completion_block
+ list = call_completion_proc
+ return unless list.is_a?(Array)
+
+ candidates = list.select{ |item| item.start_with?(target) }
+ return if candidates.empty?
+
+ pre = preposing.split("\n", -1).last || ''
+ post = postposing.split("\n", -1).first || ''
+ CompletionJourneyState.new(
+ @line_index, pre, target, post, [target] + candidates, 0
+ )
end
private def run_for_operators(key, method_symbol, &block)
- if @waiting_operator_proc
+ if @vi_waiting_operator
if VI_MOTIONS.include?(method_symbol)
- old_cursor, old_byte_pointer = @cursor, @byte_pointer
- @vi_arg = @waiting_operator_vi_arg if @waiting_operator_vi_arg&.> 1
+ old_byte_pointer = @byte_pointer
+ @vi_arg = (@vi_arg || 1) * @vi_waiting_operator_arg
block.(true)
unless @waiting_proc
- cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
- @cursor, @byte_pointer = old_cursor, old_byte_pointer
- @waiting_operator_proc.(cursor_diff, byte_pointer_diff)
- else
- old_waiting_proc = @waiting_proc
- old_waiting_operator_proc = @waiting_operator_proc
- current_waiting_operator_proc = @waiting_operator_proc
- @waiting_proc = proc { |k|
- old_cursor, old_byte_pointer = @cursor, @byte_pointer
- old_waiting_proc.(k)
- cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
- @cursor, @byte_pointer = old_cursor, old_byte_pointer
- current_waiting_operator_proc.(cursor_diff, byte_pointer_diff)
- @waiting_operator_proc = old_waiting_operator_proc
- }
+ byte_pointer_diff = @byte_pointer - old_byte_pointer
+ @byte_pointer = old_byte_pointer
+ method_obj = method(@vi_waiting_operator)
+ wrap_method_call(@vi_waiting_operator, method_obj, byte_pointer_diff)
+ cleanup_waiting
end
else
# Ignores operator when not motion is given.
block.(false)
+ cleanup_waiting
end
- @waiting_operator_proc = nil
- @waiting_operator_vi_arg = nil
- if @vi_arg
- @rerender_all = true
- @vi_arg = nil
- end
+ @vi_arg = nil
else
block.(false)
end
@@ -1416,7 +975,7 @@ class Reline::LineEditor
end
def wrap_method_call(method_symbol, method_obj, key, with_operator = false)
- if @config.editing_mode_is?(:emacs, :vi_insert) and @waiting_proc.nil? and @waiting_operator_proc.nil?
+ if @config.editing_mode_is?(:emacs, :vi_insert) and @vi_waiting_operator.nil?
not_insertion = method_symbol != :ed_insert
process_insert(force: not_insertion)
end
@@ -1435,11 +994,33 @@ class Reline::LineEditor
end
end
+ private def cleanup_waiting
+ @waiting_proc = nil
+ @vi_waiting_operator = nil
+ @vi_waiting_operator_arg = nil
+ @searching_prompt = nil
+ @drop_terminate_spaces = false
+ end
+
private def process_key(key, method_symbol)
+ if key.is_a?(Symbol)
+ cleanup_waiting
+ elsif @waiting_proc
+ old_byte_pointer = @byte_pointer
+ @waiting_proc.call(key)
+ if @vi_waiting_operator
+ byte_pointer_diff = @byte_pointer - old_byte_pointer
+ @byte_pointer = old_byte_pointer
+ method_obj = method(@vi_waiting_operator)
+ wrap_method_call(@vi_waiting_operator, method_obj, byte_pointer_diff)
+ cleanup_waiting
+ end
+ @kill_ring.process
+ return
+ end
+
if method_symbol and respond_to?(method_symbol, true)
method_obj = method(method_symbol)
- else
- method_obj = nil
end
if method_symbol and key.is_a?(Symbol)
if @vi_arg and argumentable?(method_obj)
@@ -1451,7 +1032,6 @@ class Reline::LineEditor
end
@kill_ring.process
if @vi_arg
- @rerender_al = true
@vi_arg = nil
end
elsif @vi_arg
@@ -1462,8 +1042,6 @@ class Reline::LineEditor
run_for_operators(key, method_symbol) do |with_operator|
wrap_method_call(method_symbol, method_obj, key, with_operator)
end
- elsif @waiting_proc
- @waiting_proc.(key)
elsif method_obj
wrap_method_call(method_symbol, method_obj, key)
else
@@ -1471,13 +1049,9 @@ class Reline::LineEditor
end
@kill_ring.process
if @vi_arg
- @rerender_all = true
@vi_arg = nil
end
end
- elsif @waiting_proc
- @waiting_proc.(key)
- @kill_ring.process
elsif method_obj
if method_symbol == :ed_argument_digit
wrap_method_call(method_symbol, method_obj, key)
@@ -1493,11 +1067,6 @@ class Reline::LineEditor
end
private def normal_char(key)
- method_symbol = method_obj = nil
- if key.combined_char.is_a?(Symbol)
- process_key(key.combined_char, key.combined_char)
- return
- end
@multibyte_buffer << key.combined_char
if @multibyte_buffer.size > 1
if @multibyte_buffer.dup.force_encoding(@encoding).valid_encoding?
@@ -1523,87 +1092,95 @@ class Reline::LineEditor
end
@multibyte_buffer.clear
end
- if @config.editing_mode_is?(:vi_command) and @cursor > 0 and @cursor == @cursor_max
- byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
+ if @config.editing_mode_is?(:vi_command) and @byte_pointer > 0 and @byte_pointer == current_line.bytesize
+ byte_size = Reline::Unicode.get_prev_mbchar_size(@buffer_of_lines[@line_index], @byte_pointer)
@byte_pointer -= byte_size
- mbchar = @line.byteslice(@byte_pointer, byte_size)
- width = Reline::Unicode.get_mbchar_width(mbchar)
- @cursor -= width
+ end
+ end
+
+ def update(key)
+ modified = input_key(key)
+ unless @in_pasting
+ scroll_into_view
+ @just_cursor_moving = !modified
+ update_dialogs(key)
+ @just_cursor_moving = false
end
end
def input_key(key)
- @last_key = key
+ save_old_buffer
@config.reset_oneshot_key_bindings
@dialogs.each do |dialog|
if key.char.instance_of?(Symbol) and key.char == dialog.name
return
end
end
- @just_cursor_moving = nil
if key.char.nil?
+ process_insert(force: true)
if @first_char
- @line = nil
+ @eof = true
end
finish
return
end
- old_line = @line.dup
@first_char = false
- completion_occurs = false
- if @config.editing_mode_is?(:emacs, :vi_insert) and key.char == "\C-i".ord
- unless @config.disable_completion
- result = call_completion_proc
- if result.is_a?(Array)
- completion_occurs = true
- process_insert
- if @config.autocompletion
- move_completed_list(result, :down)
- else
- complete(result)
- end
- end
- end
- elsif @config.editing_mode_is?(:emacs, :vi_insert) and key.char == :completion_journey_up
- if not @config.disable_completion and @config.autocompletion
- result = call_completion_proc
- if result.is_a?(Array)
- completion_occurs = true
- process_insert
- move_completed_list(result, :up)
- end
- end
- elsif not @config.disable_completion and @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key.char)
- unless @config.disable_completion
- result = call_completion_proc
- if result.is_a?(Array)
- completion_occurs = true
- process_insert
- move_completed_list(result, "\C-p".ord == key.char ? :up : :down)
- end
- end
- elsif Symbol === key.char and respond_to?(key.char, true)
+ @completion_occurs = false
+
+ if key.char.is_a?(Symbol)
process_key(key.char, key.char)
else
normal_char(key)
end
- unless completion_occurs
+ unless @completion_occurs
@completion_state = CompletionState::NORMAL
- @completion_journey_data = nil
+ @completion_journey_state = nil
end
- if not @in_pasting and @just_cursor_moving.nil?
- if @previous_line_index and @buffer_of_lines[@previous_line_index] == @line
- @just_cursor_moving = true
- elsif @previous_line_index.nil? and @buffer_of_lines[@line_index] == @line and old_line == @line
- @just_cursor_moving = true
- else
- @just_cursor_moving = false
- end
- else
- @just_cursor_moving = false
+
+ push_past_lines unless @undoing
+ @undoing = false
+
+ if @in_pasting
+ clear_dialogs
+ return
end
- if @is_multiline and @auto_indent_proc and not simplified_rendering? and @line
- process_auto_indent
+
+ modified = @old_buffer_of_lines != @buffer_of_lines
+ if !@completion_occurs && modified && !@config.disable_completion && @config.autocompletion
+ # Auto complete starts only when edited
+ process_insert(force: true)
+ @completion_journey_state = retrieve_completion_journey_state
+ end
+ modified
+ end
+
+ def save_old_buffer
+ @old_buffer_of_lines = @buffer_of_lines.dup
+ @old_byte_pointer = @byte_pointer.dup
+ @old_line_index = @line_index.dup
+ end
+
+ def push_past_lines
+ if @old_buffer_of_lines != @buffer_of_lines
+ @past_lines.push([@old_buffer_of_lines, @old_byte_pointer, @old_line_index])
+ end
+ trim_past_lines
+ end
+
+ MAX_PAST_LINES = 100
+ def trim_past_lines
+ if @past_lines.size > MAX_PAST_LINES
+ @past_lines.shift
+ end
+ end
+
+ def scroll_into_view
+ _wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position
+ if wrapped_cursor_y < screen_scroll_top
+ @scroll_partial_screen = wrapped_cursor_y
+ end
+ if wrapped_cursor_y >= screen_scroll_top + screen_height
+ @scroll_partial_screen = wrapped_cursor_y - screen_height + 1
end
end
@@ -1637,43 +1214,52 @@ class Reline::LineEditor
result
end
- private def process_auto_indent
- return if not @check_new_auto_indent and @previous_line_index # move cursor up or down
- if @check_new_auto_indent and @previous_line_index and @previous_line_index > 0 and @line_index > @previous_line_index
- # Fix indent of a line when a newline is inserted to the next
- new_lines = whole_lines
- new_indent = @auto_indent_proc.(new_lines[0..-3].push(''), @line_index - 1, 0, true)
- md = @line.match(/\A */)
- prev_indent = md[0].count(' ')
- @line = ' ' * new_indent + @line.lstrip
-
- new_indent = nil
- result = @auto_indent_proc.(new_lines[0..-2], @line_index - 1, (new_lines[@line_index - 1].bytesize + 1), false)
- if result
- new_indent = result
- end
- if new_indent&.>= 0
- @line = ' ' * new_indent + @line.lstrip
- end
- end
- new_lines = whole_lines
- new_indent = @auto_indent_proc.(new_lines, @line_index, @byte_pointer, @check_new_auto_indent)
- if new_indent&.>= 0
- md = new_lines[@line_index].match(/\A */)
- prev_indent = md[0].count(' ')
- if @check_new_auto_indent
- line = @buffer_of_lines[@line_index] = ' ' * new_indent + @buffer_of_lines[@line_index].lstrip
- @cursor = new_indent
- @cursor_max = calculate_width(line)
- @byte_pointer = new_indent
- else
- @line = ' ' * new_indent + @line.lstrip
- @cursor += new_indent - prev_indent
- @cursor_max = calculate_width(@line)
- @byte_pointer += new_indent - prev_indent
- end
+ private def process_auto_indent(line_index = @line_index, cursor_dependent: true, add_newline: false)
+ return if @in_pasting
+ return unless @auto_indent_proc
+
+ line = @buffer_of_lines[line_index]
+ byte_pointer = cursor_dependent && @line_index == line_index ? @byte_pointer : line.bytesize
+ new_indent = @auto_indent_proc.(@buffer_of_lines.take(line_index + 1).push(''), line_index, byte_pointer, add_newline)
+ return unless new_indent
+
+ new_line = ' ' * new_indent + line.lstrip
+ @buffer_of_lines[line_index] = new_line
+ if @line_index == line_index
+ indent_diff = new_line.bytesize - line.bytesize
+ @byte_pointer = [@byte_pointer + indent_diff, 0].max
+ end
+ end
+
+ def line()
+ @buffer_of_lines.join("\n") unless eof?
+ end
+
+ def current_line
+ @buffer_of_lines[@line_index]
+ end
+
+ def set_current_line(line, byte_pointer = nil)
+ cursor = current_byte_pointer_cursor
+ @buffer_of_lines[@line_index] = line
+ if byte_pointer
+ @byte_pointer = byte_pointer
+ else
+ calculate_nearest_cursor(cursor)
end
- @check_new_auto_indent = false
+ process_auto_indent
+ end
+
+ def set_current_lines(lines, byte_pointer = nil, line_index = 0)
+ cursor = current_byte_pointer_cursor
+ @buffer_of_lines = lines
+ @line_index = line_index
+ if byte_pointer
+ @byte_pointer = byte_pointer
+ else
+ calculate_nearest_cursor(cursor)
+ end
+ process_auto_indent
end
def retrieve_completion_block(set_completion_quote_character = false)
@@ -1687,7 +1273,7 @@ class Reline::LineEditor
else
quote_characters_regexp = /\A[#{Regexp.escape(Reline.completer_quote_characters)}]/
end
- before = @line.byteslice(0, @byte_pointer)
+ before = current_line.byteslice(0, @byte_pointer)
rest = nil
break_pointer = nil
quote = nil
@@ -1695,7 +1281,7 @@ class Reline::LineEditor
escaped_quote = nil
i = 0
while i < @byte_pointer do
- slice = @line.byteslice(i, @byte_pointer - i)
+ slice = current_line.byteslice(i, @byte_pointer - i)
unless slice.valid_encoding?
i += 1
next
@@ -1717,15 +1303,15 @@ class Reline::LineEditor
elsif word_break_regexp and not quote and slice =~ word_break_regexp
rest = $'
i += 1
- before = @line.byteslice(i, @byte_pointer - i)
+ before = current_line.byteslice(i, @byte_pointer - i)
break_pointer = i
else
i += 1
end
end
- postposing = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer)
+ postposing = current_line.byteslice(@byte_pointer, current_line.bytesize - @byte_pointer)
if rest
- preposing = @line.byteslice(0, break_pointer)
+ preposing = current_line.byteslice(0, break_pointer)
target = rest
if set_completion_quote_character and quote
Reline.core.instance_variable_set(:@completion_quote_character, quote)
@@ -1736,126 +1322,93 @@ class Reline::LineEditor
else
preposing = ''
if break_pointer
- preposing = @line.byteslice(0, break_pointer)
+ preposing = current_line.byteslice(0, break_pointer)
else
preposing = ''
end
target = before
end
- if @is_multiline
- lines = whole_lines
- if @line_index > 0
- preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing
- end
- if (lines.size - 1) > @line_index
- postposing = postposing + "\n" + lines[(@line_index + 1)..-1].join("\n")
- end
+ lines = whole_lines
+ if @line_index > 0
+ preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing
+ end
+ if (lines.size - 1) > @line_index
+ postposing = postposing + "\n" + lines[(@line_index + 1)..-1].join("\n")
end
[preposing.encode(@encoding), target.encode(@encoding), postposing.encode(@encoding)]
end
def confirm_multiline_termination
temp_buffer = @buffer_of_lines.dup
- if @previous_line_index and @line_index == (@buffer_of_lines.size - 1)
- temp_buffer[@previous_line_index] = @line
- else
- temp_buffer[@line_index] = @line
- end
@confirm_multiline_termination_proc.(temp_buffer.join("\n") + "\n")
end
+ def insert_pasted_text(text)
+ save_old_buffer
+ pre = @buffer_of_lines[@line_index].byteslice(0, @byte_pointer)
+ post = @buffer_of_lines[@line_index].byteslice(@byte_pointer..)
+ lines = (pre + text.gsub(/\r\n?/, "\n") + post).split("\n", -1)
+ lines << '' if lines.empty?
+ @buffer_of_lines[@line_index, 1] = lines
+ @line_index += lines.size - 1
+ @byte_pointer = @buffer_of_lines[@line_index].bytesize - post.bytesize
+ push_past_lines
+ end
+
def insert_text(text)
- width = calculate_width(text)
- if @cursor == @cursor_max
- @line += text
+ if @buffer_of_lines[@line_index].bytesize == @byte_pointer
+ @buffer_of_lines[@line_index] += text
else
- @line = byteinsert(@line, @byte_pointer, text)
+ @buffer_of_lines[@line_index] = byteinsert(@buffer_of_lines[@line_index], @byte_pointer, text)
end
@byte_pointer += text.bytesize
- @cursor += width
- @cursor_max += width
+ process_auto_indent
end
def delete_text(start = nil, length = nil)
if start.nil? and length.nil?
- if @is_multiline
- if @buffer_of_lines.size == 1
- @line&.clear
- @byte_pointer = 0
- @cursor = 0
- @cursor_max = 0
- elsif @line_index == (@buffer_of_lines.size - 1) and @line_index > 0
- @buffer_of_lines.pop
- @line_index -= 1
- @line = @buffer_of_lines[@line_index]
- @byte_pointer = 0
- @cursor = 0
- @cursor_max = calculate_width(@line)
- elsif @line_index < (@buffer_of_lines.size - 1)
- @buffer_of_lines.delete_at(@line_index)
- @line = @buffer_of_lines[@line_index]
- @byte_pointer = 0
- @cursor = 0
- @cursor_max = calculate_width(@line)
- end
- else
- @line&.clear
+ if @buffer_of_lines.size == 1
+ @buffer_of_lines[@line_index] = ''
+ @byte_pointer = 0
+ elsif @line_index == (@buffer_of_lines.size - 1) and @line_index > 0
+ @buffer_of_lines.pop
+ @line_index -= 1
+ @byte_pointer = 0
+ elsif @line_index < (@buffer_of_lines.size - 1)
+ @buffer_of_lines.delete_at(@line_index)
@byte_pointer = 0
- @cursor = 0
- @cursor_max = 0
end
elsif not start.nil? and not length.nil?
- if @line
- before = @line.byteslice(0, start)
- after = @line.byteslice(start + length, @line.bytesize)
- @line = before + after
- @byte_pointer = @line.bytesize if @byte_pointer > @line.bytesize
- str = @line.byteslice(0, @byte_pointer)
- @cursor = calculate_width(str)
- @cursor_max = calculate_width(@line)
+ if current_line
+ before = current_line.byteslice(0, start)
+ after = current_line.byteslice(start + length, current_line.bytesize)
+ set_current_line(before + after)
end
elsif start.is_a?(Range)
range = start
first = range.first
last = range.last
- last = @line.bytesize - 1 if last > @line.bytesize
- last += @line.bytesize if last < 0
- first += @line.bytesize if first < 0
+ last = current_line.bytesize - 1 if last > current_line.bytesize
+ last += current_line.bytesize if last < 0
+ first += current_line.bytesize if first < 0
range = range.exclude_end? ? first...last : first..last
- @line = @line.bytes.reject.with_index{ |c, i| range.include?(i) }.map{ |c| c.chr(Encoding::ASCII_8BIT) }.join.force_encoding(@encoding)
- @byte_pointer = @line.bytesize if @byte_pointer > @line.bytesize
- str = @line.byteslice(0, @byte_pointer)
- @cursor = calculate_width(str)
- @cursor_max = calculate_width(@line)
+ line = current_line.bytes.reject.with_index{ |c, i| range.include?(i) }.map{ |c| c.chr(Encoding::ASCII_8BIT) }.join.force_encoding(@encoding)
+ set_current_line(line)
else
- @line = @line.byteslice(0, start)
- @byte_pointer = @line.bytesize if @byte_pointer > @line.bytesize
- str = @line.byteslice(0, @byte_pointer)
- @cursor = calculate_width(str)
- @cursor_max = calculate_width(@line)
+ set_current_line(current_line.byteslice(0, start))
end
end
def byte_pointer=(val)
@byte_pointer = val
- str = @line.byteslice(0, @byte_pointer)
- @cursor = calculate_width(str)
- @cursor_max = calculate_width(@line)
end
def whole_lines
- index = @previous_line_index || @line_index
- temp_lines = @buffer_of_lines.dup
- temp_lines[index] = @line
- temp_lines
+ @buffer_of_lines.dup
end
def whole_buffer
- if @buffer_of_lines.size == 1 and @line.nil?
- nil
- else
- whole_lines.join("\n")
- end
+ whole_lines.join("\n")
end
def finished?
@@ -1864,7 +1417,6 @@ class Reline::LineEditor
def finish
@finished = true
- @rerender_all = true
@config.reset
end
@@ -1895,33 +1447,56 @@ class Reline::LineEditor
private def key_newline(key)
if @is_multiline
- if (@buffer_of_lines.size - 1) == @line_index and @line.bytesize == @byte_pointer
- @add_newline_to_end_of_buffer = true
- end
- next_line = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer)
- cursor_line = @line.byteslice(0, @byte_pointer)
+ next_line = current_line.byteslice(@byte_pointer, current_line.bytesize - @byte_pointer)
+ cursor_line = current_line.byteslice(0, @byte_pointer)
insert_new_line(cursor_line, next_line)
- @cursor = 0
- @check_new_auto_indent = true unless @in_pasting
end
end
+ private def complete(_key)
+ return if @config.disable_completion
+
+ process_insert(force: true)
+ if @config.autocompletion
+ @completion_state = CompletionState::NORMAL
+ @completion_occurs = move_completed_list(:down)
+ else
+ @completion_journey_state = nil
+ result = call_completion_proc
+ if result.is_a?(Array)
+ @completion_occurs = true
+ perform_completion(result, false)
+ end
+ end
+ end
+
+ private def completion_journey_move(direction)
+ return if @config.disable_completion
+
+ process_insert(force: true)
+ @completion_state = CompletionState::NORMAL
+ @completion_occurs = move_completed_list(direction)
+ end
+
+ private def menu_complete(_key)
+ completion_journey_move(:down)
+ end
+
+ private def menu_complete_backward(_key)
+ completion_journey_move(:up)
+ end
+
+ private def completion_journey_up(_key)
+ completion_journey_move(:up) if @config.autocompletion
+ end
+
# Editline:: +ed-unassigned+ This editor command always results in an error.
# GNU Readline:: There is no corresponding macro.
private def ed_unassigned(key) end # do nothing
private def process_insert(force: false)
return if @continuous_insertion_buffer.empty? or (@in_pasting and not force)
- width = Reline::Unicode.calculate_width(@continuous_insertion_buffer)
- bytesize = @continuous_insertion_buffer.bytesize
- if @cursor == @cursor_max
- @line += @continuous_insertion_buffer
- else
- @line = byteinsert(@line, @byte_pointer, @continuous_insertion_buffer)
- end
- @byte_pointer += bytesize
- @cursor += width
- @cursor_max += width
+ insert_text(@continuous_insertion_buffer)
@continuous_insertion_buffer.clear
end
@@ -1939,9 +1514,6 @@ class Reline::LineEditor
# million.
# GNU Readline:: +self-insert+ (a, b, A, 1, !, …) Insert yourself.
private def ed_insert(key)
- str = nil
- width = nil
- bytesize = nil
if key.instance_of?(String)
begin
key.encode(Encoding::UTF_8)
@@ -1949,7 +1521,6 @@ class Reline::LineEditor
return
end
str = key
- bytesize = key.bytesize
else
begin
key.chr.encode(Encoding::UTF_8)
@@ -1957,7 +1528,6 @@ class Reline::LineEditor
return
end
str = key.chr
- bytesize = 1
end
if @in_pasting
@continuous_insertion_buffer << str
@@ -1965,28 +1535,8 @@ class Reline::LineEditor
elsif not @continuous_insertion_buffer.empty?
process_insert
end
- width = Reline::Unicode.get_mbchar_width(str)
- if @cursor == @cursor_max
- @line += str
- else
- @line = byteinsert(@line, @byte_pointer, str)
- end
- last_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
- @byte_pointer += bytesize
- last_mbchar = @line.byteslice((@byte_pointer - bytesize - last_byte_size), last_byte_size)
- combined_char = last_mbchar + str
- if last_byte_size != 0 and combined_char.grapheme_clusters.size == 1
- # combined char
- last_mbchar_width = Reline::Unicode.get_mbchar_width(last_mbchar)
- combined_char_width = Reline::Unicode.get_mbchar_width(combined_char)
- if combined_char_width > last_mbchar_width
- width = combined_char_width - last_mbchar_width
- else
- width = 0
- end
- end
- @cursor += width
- @cursor_max += width
+
+ insert_text(str)
end
alias_method :ed_digit, :ed_insert
alias_method :self_insert, :ed_insert
@@ -2008,18 +1558,11 @@ class Reline::LineEditor
alias_method :quoted_insert, :ed_quoted_insert
private def ed_next_char(key, arg: 1)
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
- if (@byte_pointer < @line.bytesize)
- mbchar = @line.byteslice(@byte_pointer, byte_size)
- width = Reline::Unicode.get_mbchar_width(mbchar)
- @cursor += width if width
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
+ if (@byte_pointer < current_line.bytesize)
@byte_pointer += byte_size
- elsif @is_multiline and @config.editing_mode_is?(:emacs) and @byte_pointer == @line.bytesize and @line_index < @buffer_of_lines.size - 1
- next_line = @buffer_of_lines[@line_index + 1]
- @cursor = 0
+ elsif @config.editing_mode_is?(:emacs) and @byte_pointer == current_line.bytesize and @line_index < @buffer_of_lines.size - 1
@byte_pointer = 0
- @cursor_max = calculate_width(next_line)
- @previous_line_index = @line_index
@line_index += 1
end
arg -= 1
@@ -2028,19 +1571,12 @@ class Reline::LineEditor
alias_method :forward_char, :ed_next_char
private def ed_prev_char(key, arg: 1)
- if @cursor > 0
- byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
+ if @byte_pointer > 0
+ byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
@byte_pointer -= byte_size
- mbchar = @line.byteslice(@byte_pointer, byte_size)
- width = Reline::Unicode.get_mbchar_width(mbchar)
- @cursor -= width
- elsif @is_multiline and @config.editing_mode_is?(:emacs) and @byte_pointer == 0 and @line_index > 0
- prev_line = @buffer_of_lines[@line_index - 1]
- @cursor = calculate_width(prev_line)
- @byte_pointer = prev_line.bytesize
- @cursor_max = calculate_width(prev_line)
- @previous_line_index = @line_index
+ elsif @config.editing_mode_is?(:emacs) and @byte_pointer == 0 and @line_index > 0
@line_index -= 1
+ @byte_pointer = current_line.bytesize
end
arg -= 1
ed_prev_char(key, arg: arg) if arg > 0
@@ -2048,157 +1584,109 @@ class Reline::LineEditor
alias_method :backward_char, :ed_prev_char
private def vi_first_print(key)
- @byte_pointer, @cursor = Reline::Unicode.vi_first_print(@line)
+ @byte_pointer, = Reline::Unicode.vi_first_print(current_line)
end
private def ed_move_to_beg(key)
- @byte_pointer = @cursor = 0
+ @byte_pointer = 0
end
alias_method :beginning_of_line, :ed_move_to_beg
+ alias_method :vi_zero, :ed_move_to_beg
private def ed_move_to_end(key)
- @byte_pointer = 0
- @cursor = 0
- byte_size = 0
- while @byte_pointer < @line.bytesize
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
- if byte_size > 0
- mbchar = @line.byteslice(@byte_pointer, byte_size)
- @cursor += Reline::Unicode.get_mbchar_width(mbchar)
- end
- @byte_pointer += byte_size
- end
+ @byte_pointer = current_line.bytesize
end
alias_method :end_of_line, :ed_move_to_end
- private def generate_searcher
- Fiber.new do |first_key|
- prev_search_key = first_key
- search_word = String.new(encoding: @encoding)
- multibyte_buf = String.new(encoding: 'ASCII-8BIT')
- last_hit = nil
- case first_key
- when "\C-r".ord
- prompt_name = 'reverse-i-search'
- when "\C-s".ord
- prompt_name = 'i-search'
+ private def generate_searcher(search_key)
+ search_word = String.new(encoding: @encoding)
+ multibyte_buf = String.new(encoding: 'ASCII-8BIT')
+ hit_pointer = nil
+ lambda do |key|
+ search_again = false
+ case key
+ when "\C-h".ord, "\C-?".ord
+ grapheme_clusters = search_word.grapheme_clusters
+ if grapheme_clusters.size > 0
+ grapheme_clusters.pop
+ search_word = grapheme_clusters.join
+ end
+ when "\C-r".ord, "\C-s".ord
+ search_again = true if search_key == key
+ search_key = key
+ else
+ multibyte_buf << key
+ if multibyte_buf.dup.force_encoding(@encoding).valid_encoding?
+ search_word << multibyte_buf.dup.force_encoding(@encoding)
+ multibyte_buf.clear
+ end
end
- loop do
- key = Fiber.yield(search_word)
- search_again = false
- case key
- when -1 # determined
- Reline.last_incremental_search = search_word
- break
- when "\C-h".ord, "\C-?".ord
- grapheme_clusters = search_word.grapheme_clusters
- if grapheme_clusters.size > 0
- grapheme_clusters.pop
- search_word = grapheme_clusters.join
- end
- when "\C-r".ord, "\C-s".ord
- search_again = true if prev_search_key == key
- prev_search_key = key
- else
- multibyte_buf << key
- if multibyte_buf.dup.force_encoding(@encoding).valid_encoding?
- search_word << multibyte_buf.dup.force_encoding(@encoding)
- multibyte_buf.clear
+ hit = nil
+ if not search_word.empty? and @line_backup_in_history&.include?(search_word)
+ hit_pointer = Reline::HISTORY.size
+ hit = @line_backup_in_history
+ else
+ if search_again
+ if search_word.empty? and Reline.last_incremental_search
+ search_word = Reline.last_incremental_search
end
- end
- hit = nil
- if not search_word.empty? and @line_backup_in_history&.include?(search_word)
- @history_pointer = nil
- hit = @line_backup_in_history
- else
- if search_again
- if search_word.empty? and Reline.last_incremental_search
- search_word = Reline.last_incremental_search
- end
- if @history_pointer
- case prev_search_key
- when "\C-r".ord
- history_pointer_base = 0
- history = Reline::HISTORY[0..(@history_pointer - 1)]
- when "\C-s".ord
- history_pointer_base = @history_pointer + 1
- history = Reline::HISTORY[(@history_pointer + 1)..-1]
- end
- else
- history_pointer_base = 0
- history = Reline::HISTORY
- end
- elsif @history_pointer
- case prev_search_key
+ if @history_pointer
+ case search_key
when "\C-r".ord
history_pointer_base = 0
- history = Reline::HISTORY[0..@history_pointer]
+ history = Reline::HISTORY[0..(@history_pointer - 1)]
when "\C-s".ord
- history_pointer_base = @history_pointer
- history = Reline::HISTORY[@history_pointer..-1]
+ history_pointer_base = @history_pointer + 1
+ history = Reline::HISTORY[(@history_pointer + 1)..-1]
end
else
history_pointer_base = 0
history = Reline::HISTORY
end
- case prev_search_key
+ elsif @history_pointer
+ case search_key
when "\C-r".ord
- hit_index = history.rindex { |item|
- item.include?(search_word)
- }
+ history_pointer_base = 0
+ history = Reline::HISTORY[0..@history_pointer]
when "\C-s".ord
- hit_index = history.index { |item|
- item.include?(search_word)
- }
- end
- if hit_index
- @history_pointer = history_pointer_base + hit_index
- hit = Reline::HISTORY[@history_pointer]
+ history_pointer_base = @history_pointer
+ history = Reline::HISTORY[@history_pointer..-1]
end
+ else
+ history_pointer_base = 0
+ history = Reline::HISTORY
end
- case prev_search_key
+ case search_key
when "\C-r".ord
- prompt_name = 'reverse-i-search'
+ hit_index = history.rindex { |item|
+ item.include?(search_word)
+ }
when "\C-s".ord
- prompt_name = 'i-search'
+ hit_index = history.index { |item|
+ item.include?(search_word)
+ }
end
- if hit
- if @is_multiline
- @buffer_of_lines = hit.split("\n")
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
- @line_index = @buffer_of_lines.size - 1
- @line = @buffer_of_lines.last
- @byte_pointer = @line.bytesize
- @cursor = @cursor_max = calculate_width(@line)
- @rerender_all = true
- @searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
- else
- @line = hit
- @searching_prompt = "(%s)`%s': %s" % [prompt_name, search_word, hit]
- end
- last_hit = hit
- else
- if @is_multiline
- @rerender_all = true
- @searching_prompt = "(failed %s)`%s'" % [prompt_name, search_word]
- else
- @searching_prompt = "(failed %s)`%s': %s" % [prompt_name, search_word, last_hit]
- end
+ if hit_index
+ hit_pointer = history_pointer_base + hit_index
+ hit = Reline::HISTORY[hit_pointer]
end
end
+ case search_key
+ when "\C-r".ord
+ prompt_name = 'reverse-i-search'
+ when "\C-s".ord
+ prompt_name = 'i-search'
+ end
+ prompt_name = "failed #{prompt_name}" unless hit
+ [search_word, prompt_name, hit_pointer]
end
end
private def incremental_search_history(key)
unless @history_pointer
- if @is_multiline
- @line_backup_in_history = whole_buffer
- else
- @line_backup_in_history = @line
- end
+ @line_backup_in_history = whole_buffer
end
- searcher = generate_searcher
- searcher.resume(key)
+ searcher = generate_searcher(key)
@searching_prompt = "(reverse-i-search)`': "
termination_keys = ["\C-j".ord]
termination_keys.concat(@config.isearch_terminators&.chars&.map(&:ord)) if @config.isearch_terminators
@@ -2210,67 +1698,41 @@ class Reline::LineEditor
else
buffer = @line_backup_in_history
end
- if @is_multiline
- @buffer_of_lines = buffer.split("\n")
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
- @line_index = @buffer_of_lines.size - 1
- @line = @buffer_of_lines.last
- @rerender_all = true
- else
- @line = buffer
- end
+ @buffer_of_lines = buffer.split("\n")
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
+ @line_index = @buffer_of_lines.size - 1
@searching_prompt = nil
@waiting_proc = nil
- @cursor_max = calculate_width(@line)
- @cursor = @byte_pointer = 0
- @rerender_all = true
- @cached_prompt_list = nil
- searcher.resume(-1)
+ @byte_pointer = 0
when "\C-g".ord
- if @is_multiline
- @buffer_of_lines = @line_backup_in_history.split("\n")
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
- @line_index = @buffer_of_lines.size - 1
- @line = @buffer_of_lines.last
- @rerender_all = true
- else
- @line = @line_backup_in_history
- end
- @history_pointer = nil
+ @buffer_of_lines = @line_backup_in_history.split("\n")
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
+ @line_index = @buffer_of_lines.size - 1
+ move_history(nil, line: :end, cursor: :end, save_buffer: false)
@searching_prompt = nil
@waiting_proc = nil
- @line_backup_in_history = nil
- @cursor_max = calculate_width(@line)
- @cursor = @byte_pointer = 0
- @rerender_all = true
+ @byte_pointer = 0
else
chr = k.is_a?(String) ? k : k.chr(Encoding::ASCII_8BIT)
if chr.match?(/[[:print:]]/) or k == "\C-h".ord or k == "\C-?".ord or k == "\C-r".ord or k == "\C-s".ord
- searcher.resume(k)
+ search_word, prompt_name, hit_pointer = searcher.call(k)
+ Reline.last_incremental_search = search_word
+ @searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
+ @searching_prompt += ': ' unless @is_multiline
+ move_history(hit_pointer, line: :end, cursor: :end, save_buffer: false) if hit_pointer
else
if @history_pointer
line = Reline::HISTORY[@history_pointer]
else
line = @line_backup_in_history
end
- if @is_multiline
- @line_backup_in_history = whole_buffer
- @buffer_of_lines = line.split("\n")
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
- @line_index = @buffer_of_lines.size - 1
- @line = @buffer_of_lines.last
- @rerender_all = true
- else
- @line_backup_in_history = @line
- @line = line
- end
+ @line_backup_in_history = whole_buffer
+ @buffer_of_lines = line.split("\n")
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
+ @line_index = @buffer_of_lines.size - 1
@searching_prompt = nil
@waiting_proc = nil
- @cursor_max = calculate_width(@line)
- @cursor = @byte_pointer = 0
- @rerender_all = true
- @cached_prompt_list = nil
- searcher.resume(-1)
+ @byte_pointer = 0
end
end
}
@@ -2286,199 +1748,95 @@ class Reline::LineEditor
end
alias_method :forward_search_history, :vi_search_next
- private def ed_search_prev_history(key, arg: 1)
- history = nil
- h_pointer = nil
- line_no = nil
- substr = @line.slice(0, @byte_pointer)
- if @history_pointer.nil?
- return if not @line.empty? and substr.empty?
- history = Reline::HISTORY
- elsif @history_pointer.zero?
- history = nil
- h_pointer = nil
- else
- history = Reline::HISTORY.slice(0, @history_pointer)
- end
- return if history.nil?
- if @is_multiline
- h_pointer = history.rindex { |h|
- h.split("\n").each_with_index { |l, i|
- if l.start_with?(substr)
- line_no = i
- break
- end
- }
- not line_no.nil?
- }
- else
- h_pointer = history.rindex { |l|
- l.start_with?(substr)
- }
- end
- return if h_pointer.nil?
- @history_pointer = h_pointer
- if @is_multiline
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
- @line_index = line_no
- @line = @buffer_of_lines[@line_index]
- @rerender_all = true
- else
- @line = Reline::HISTORY[@history_pointer]
+ private def search_history(prefix, pointer_range)
+ pointer_range.each do |pointer|
+ lines = Reline::HISTORY[pointer].split("\n")
+ lines.each_with_index do |line, index|
+ return [pointer, index] if line.start_with?(prefix)
+ end
end
- @cursor_max = calculate_width(@line)
+ nil
+ end
+
+ private def ed_search_prev_history(key, arg: 1)
+ substr = current_line.byteslice(0, @byte_pointer)
+ return if @history_pointer == 0
+ return if @history_pointer.nil? && substr.empty? && !current_line.empty?
+
+ history_range = 0...(@history_pointer || Reline::HISTORY.size)
+ h_pointer, line_index = search_history(substr, history_range.reverse_each)
+ return unless h_pointer
+ move_history(h_pointer, line: line_index || :start, cursor: @byte_pointer)
arg -= 1
ed_search_prev_history(key, arg: arg) if arg > 0
end
alias_method :history_search_backward, :ed_search_prev_history
private def ed_search_next_history(key, arg: 1)
- substr = @line.slice(0, @byte_pointer)
- if @history_pointer.nil?
- return
- elsif @history_pointer == (Reline::HISTORY.size - 1) and not substr.empty?
- return
- end
- history = Reline::HISTORY.slice((@history_pointer + 1)..-1)
- h_pointer = nil
- line_no = nil
- if @is_multiline
- h_pointer = history.index { |h|
- h.split("\n").each_with_index { |l, i|
- if l.start_with?(substr)
- line_no = i
- break
- end
- }
- not line_no.nil?
- }
- else
- h_pointer = history.index { |l|
- l.start_with?(substr)
- }
- end
- h_pointer += @history_pointer + 1 if h_pointer and @history_pointer
+ substr = current_line.byteslice(0, @byte_pointer)
+ return if @history_pointer.nil?
+
+ history_range = @history_pointer + 1...Reline::HISTORY.size
+ h_pointer, line_index = search_history(substr, history_range)
return if h_pointer.nil? and not substr.empty?
- @history_pointer = h_pointer
- if @is_multiline
- if @history_pointer.nil? and substr.empty?
- @buffer_of_lines = []
- @line_index = 0
- else
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
- @line_index = line_no
- end
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
- @line = @buffer_of_lines[@line_index]
- @rerender_all = true
- else
- if @history_pointer.nil? and substr.empty?
- @line = ''
- else
- @line = Reline::HISTORY[@history_pointer]
- end
- end
- @cursor_max = calculate_width(@line)
+
+ move_history(h_pointer, line: line_index || :start, cursor: @byte_pointer)
arg -= 1
ed_search_next_history(key, arg: arg) if arg > 0
end
alias_method :history_search_forward, :ed_search_next_history
- private def ed_prev_history(key, arg: 1)
- if @is_multiline and @line_index > 0
- @previous_line_index = @line_index
- @line_index -= 1
- return
- end
- if Reline::HISTORY.empty?
- return
+ private def move_history(history_pointer, line:, cursor:, save_buffer: true)
+ history_pointer ||= Reline::HISTORY.size
+ return if history_pointer < 0 || history_pointer > Reline::HISTORY.size
+ old_history_pointer = @history_pointer || Reline::HISTORY.size
+ if old_history_pointer == Reline::HISTORY.size
+ @line_backup_in_history = save_buffer ? whole_buffer : ''
+ else
+ Reline::HISTORY[old_history_pointer] = whole_buffer if save_buffer
end
- if @history_pointer.nil?
- @history_pointer = Reline::HISTORY.size - 1
- if @is_multiline
- @line_backup_in_history = whole_buffer
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
- @line_index = @buffer_of_lines.size - 1
- @line = @buffer_of_lines.last
- @rerender_all = true
- else
- @line_backup_in_history = @line
- @line = Reline::HISTORY[@history_pointer]
- end
- elsif @history_pointer.zero?
- return
+ if history_pointer == Reline::HISTORY.size
+ buf = @line_backup_in_history
+ @history_pointer = @line_backup_in_history = nil
else
- if @is_multiline
- Reline::HISTORY[@history_pointer] = whole_buffer
- @history_pointer -= 1
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
- @line_index = @buffer_of_lines.size - 1
- @line = @buffer_of_lines.last
- @rerender_all = true
- else
- Reline::HISTORY[@history_pointer] = @line
- @history_pointer -= 1
- @line = Reline::HISTORY[@history_pointer]
- end
+ buf = Reline::HISTORY[history_pointer]
+ @history_pointer = history_pointer
end
- if @config.editing_mode_is?(:emacs, :vi_insert)
- @cursor_max = @cursor = calculate_width(@line)
- @byte_pointer = @line.bytesize
- elsif @config.editing_mode_is?(:vi_command)
- @byte_pointer = @cursor = 0
- @cursor_max = calculate_width(@line)
+ @buffer_of_lines = buf.split("\n")
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
+ @line_index = line == :start ? 0 : line == :end ? @buffer_of_lines.size - 1 : line
+ @byte_pointer = cursor == :start ? 0 : cursor == :end ? current_line.bytesize : cursor
+ end
+
+ private def ed_prev_history(key, arg: 1)
+ if @line_index > 0
+ cursor = current_byte_pointer_cursor
+ @line_index -= 1
+ calculate_nearest_cursor(cursor)
+ return
end
+ move_history(
+ (@history_pointer || Reline::HISTORY.size) - 1,
+ line: :end,
+ cursor: @config.editing_mode_is?(:vi_command) ? :start : :end,
+ )
arg -= 1
ed_prev_history(key, arg: arg) if arg > 0
end
alias_method :previous_history, :ed_prev_history
private def ed_next_history(key, arg: 1)
- if @is_multiline and @line_index < (@buffer_of_lines.size - 1)
- @previous_line_index = @line_index
+ if @line_index < (@buffer_of_lines.size - 1)
+ cursor = current_byte_pointer_cursor
@line_index += 1
+ calculate_nearest_cursor(cursor)
return
end
- if @history_pointer.nil?
- return
- elsif @history_pointer == (Reline::HISTORY.size - 1)
- if @is_multiline
- @history_pointer = nil
- @buffer_of_lines = @line_backup_in_history.split("\n")
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
- @line_index = 0
- @line = @buffer_of_lines.first
- @rerender_all = true
- else
- @history_pointer = nil
- @line = @line_backup_in_history
- end
- else
- if @is_multiline
- Reline::HISTORY[@history_pointer] = whole_buffer
- @history_pointer += 1
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
- @line_index = 0
- @line = @buffer_of_lines.first
- @rerender_all = true
- else
- Reline::HISTORY[@history_pointer] = @line
- @history_pointer += 1
- @line = Reline::HISTORY[@history_pointer]
- end
- end
- @line = '' unless @line
- if @config.editing_mode_is?(:emacs, :vi_insert)
- @cursor_max = @cursor = calculate_width(@line)
- @byte_pointer = @line.bytesize
- elsif @config.editing_mode_is?(:vi_command)
- @byte_pointer = @cursor = 0
- @cursor_max = calculate_width(@line)
- end
+ move_history(
+ (@history_pointer || Reline::HISTORY.size) + 1,
+ line: :start,
+ cursor: @config.editing_mode_is?(:vi_command) ? :start : :end,
+ )
arg -= 1
ed_next_history(key, arg: arg) if arg > 0
end
@@ -2503,40 +1861,29 @@ class Reline::LineEditor
end
else
# should check confirm_multiline_termination to finish?
- @previous_line_index = @line_index
@line_index = @buffer_of_lines.size - 1
+ @byte_pointer = current_line.bytesize
finish
end
end
else
- if @history_pointer
- Reline::HISTORY[@history_pointer] = @line
- @history_pointer = nil
- end
finish
end
end
private def em_delete_prev_char(key, arg: 1)
- if @is_multiline and @cursor == 0 and @line_index > 0
- @buffer_of_lines[@line_index] = @line
- @cursor = calculate_width(@buffer_of_lines[@line_index - 1])
- @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
- @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
- @line_index -= 1
- @line = @buffer_of_lines[@line_index]
- @cursor_max = calculate_width(@line)
- @rerender_all = true
- elsif @cursor > 0
- byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
- @byte_pointer -= byte_size
- @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
- width = Reline::Unicode.get_mbchar_width(mbchar)
- @cursor -= width
- @cursor_max -= width
+ arg.times do
+ if @byte_pointer == 0 and @line_index > 0
+ @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
+ @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
+ @line_index -= 1
+ elsif @byte_pointer > 0
+ byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
+ line, = byteslice!(current_line, @byte_pointer - byte_size, byte_size)
+ set_current_line(line, @byte_pointer - byte_size)
+ end
end
- arg -= 1
- em_delete_prev_char(key, arg: arg) if arg > 0
+ process_auto_indent
end
alias_method :backward_delete_char, :em_delete_prev_char
@@ -2546,23 +1893,23 @@ class Reline::LineEditor
# the line. With a negative numeric argument, kill backward
# from the cursor to the beginning of the current line.
private def ed_kill_line(key)
- if @line.bytesize > @byte_pointer
- @line, deleted = byteslice!(@line, @byte_pointer, @line.bytesize - @byte_pointer)
- @byte_pointer = @line.bytesize
- @cursor = @cursor_max = calculate_width(@line)
+ if current_line.bytesize > @byte_pointer
+ line, deleted = byteslice!(current_line, @byte_pointer, current_line.bytesize - @byte_pointer)
+ set_current_line(line, line.bytesize)
@kill_ring.append(deleted)
- elsif @is_multiline and @byte_pointer == @line.bytesize and @buffer_of_lines.size > @line_index + 1
- @cursor = calculate_width(@line)
- @byte_pointer = @line.bytesize
- @line += @buffer_of_lines.delete_at(@line_index + 1)
- @cursor_max = calculate_width(@line)
- @buffer_of_lines[@line_index] = @line
- @rerender_all = true
- @rest_height += 1
+ elsif @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1
+ set_current_line(current_line + @buffer_of_lines.delete_at(@line_index + 1), current_line.bytesize)
end
end
alias_method :kill_line, :ed_kill_line
+ # Editline:: +vi_change_to_eol+ (vi command: +C+) + Kill and change from the cursor to the end of the line.
+ private def vi_change_to_eol(key)
+ ed_kill_line(key)
+
+ @config.editing_mode = :vi_insert
+ end
+
# Editline:: +vi-kill-line-prev+ (vi: +Ctrl-U+) Delete the string from the
# beginning of the edit buffer to the cursor and save it to the
# cut buffer.
@@ -2570,11 +1917,9 @@ class Reline::LineEditor
# to the beginning of the current line.
private def vi_kill_line_prev(key)
if @byte_pointer > 0
- @line, deleted = byteslice!(@line, 0, @byte_pointer)
- @byte_pointer = 0
+ line, deleted = byteslice!(current_line, 0, @byte_pointer)
+ set_current_line(line, 0)
@kill_ring.append(deleted, true)
- @cursor_max = calculate_width(@line)
- @cursor = 0
end
end
alias_method :unix_line_discard, :vi_kill_line_prev
@@ -2584,50 +1929,35 @@ class Reline::LineEditor
# GNU Readline:: +kill-whole-line+ (not bound) Kill all characters on the
# current line, no matter where point is.
private def em_kill_line(key)
- if @line.size > 0
- @kill_ring.append(@line.dup, true)
- @line.clear
- @byte_pointer = 0
- @cursor_max = 0
- @cursor = 0
+ if current_line.size > 0
+ @kill_ring.append(current_line.dup, true)
+ set_current_line('', 0)
end
end
alias_method :kill_whole_line, :em_kill_line
private def em_delete(key)
- if @line.empty? and (not @is_multiline or @buffer_of_lines.size == 1) and key == "\C-d".ord
- @line = nil
- if @buffer_of_lines.size > 1
- scroll_down(@highest_in_all - @first_line_started_from)
- end
- Reline::IOGate.move_cursor_column(0)
+ if current_line.empty? and @buffer_of_lines.size == 1 and key == "\C-d".ord
@eof = true
finish
- elsif @byte_pointer < @line.bytesize
- splitted_last = @line.byteslice(@byte_pointer, @line.bytesize)
+ elsif @byte_pointer < current_line.bytesize
+ splitted_last = current_line.byteslice(@byte_pointer, current_line.bytesize)
mbchar = splitted_last.grapheme_clusters.first
- width = Reline::Unicode.get_mbchar_width(mbchar)
- @cursor_max -= width
- @line, = byteslice!(@line, @byte_pointer, mbchar.bytesize)
- elsif @is_multiline and @byte_pointer == @line.bytesize and @buffer_of_lines.size > @line_index + 1
- @cursor = calculate_width(@line)
- @byte_pointer = @line.bytesize
- @line += @buffer_of_lines.delete_at(@line_index + 1)
- @cursor_max = calculate_width(@line)
- @buffer_of_lines[@line_index] = @line
- @rerender_all = true
- @rest_height += 1
+ line, = byteslice!(current_line, @byte_pointer, mbchar.bytesize)
+ set_current_line(line)
+ elsif @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1
+ set_current_line(current_line + @buffer_of_lines.delete_at(@line_index + 1), current_line.bytesize)
end
end
alias_method :delete_char, :em_delete
private def em_delete_or_list(key)
- if @line.empty? or @byte_pointer < @line.bytesize
+ if current_line.empty? or @byte_pointer < current_line.bytesize
em_delete(key)
- else # show completed list
+ elsif !@config.autocompletion # show completed list
result = call_completion_proc
if result.is_a?(Array)
- complete(result, true)
+ perform_completion(result, true)
end
end
end
@@ -2635,164 +1965,136 @@ class Reline::LineEditor
private def em_yank(key)
yanked = @kill_ring.yank
- if yanked
- @line = byteinsert(@line, @byte_pointer, yanked)
- yanked_width = calculate_width(yanked)
- @cursor += yanked_width
- @cursor_max += yanked_width
- @byte_pointer += yanked.bytesize
- end
+ insert_text(yanked) if yanked
end
alias_method :yank, :em_yank
private def em_yank_pop(key)
yanked, prev_yank = @kill_ring.yank_pop
if yanked
- prev_yank_width = calculate_width(prev_yank)
- @cursor -= prev_yank_width
- @cursor_max -= prev_yank_width
- @byte_pointer -= prev_yank.bytesize
- @line, = byteslice!(@line, @byte_pointer, prev_yank.bytesize)
- @line = byteinsert(@line, @byte_pointer, yanked)
- yanked_width = calculate_width(yanked)
- @cursor += yanked_width
- @cursor_max += yanked_width
- @byte_pointer += yanked.bytesize
+ line, = byteslice!(current_line, @byte_pointer - prev_yank.bytesize, prev_yank.bytesize)
+ set_current_line(line, @byte_pointer - prev_yank.bytesize)
+ insert_text(yanked)
end
end
alias_method :yank_pop, :em_yank_pop
private def ed_clear_screen(key)
- @cleared = true
+ Reline::IOGate.clear_screen
+ @screen_size = Reline::IOGate.get_screen_size
+ @rendered_screen.lines = []
+ @rendered_screen.base_y = 0
+ @rendered_screen.cursor_y = 0
end
alias_method :clear_screen, :ed_clear_screen
private def em_next_word(key)
- if @line.bytesize > @byte_pointer
- byte_size, width = Reline::Unicode.em_forward_word(@line, @byte_pointer)
+ if current_line.bytesize > @byte_pointer
+ byte_size, _ = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
@byte_pointer += byte_size
- @cursor += width
end
end
alias_method :forward_word, :em_next_word
private def ed_prev_word(key)
if @byte_pointer > 0
- byte_size, width = Reline::Unicode.em_backward_word(@line, @byte_pointer)
+ byte_size, _ = Reline::Unicode.em_backward_word(current_line, @byte_pointer)
@byte_pointer -= byte_size
- @cursor -= width
end
end
alias_method :backward_word, :ed_prev_word
private def em_delete_next_word(key)
- if @line.bytesize > @byte_pointer
- byte_size, width = Reline::Unicode.em_forward_word(@line, @byte_pointer)
- @line, word = byteslice!(@line, @byte_pointer, byte_size)
+ if current_line.bytesize > @byte_pointer
+ byte_size, _ = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
+ line, word = byteslice!(current_line, @byte_pointer, byte_size)
+ set_current_line(line)
@kill_ring.append(word)
- @cursor_max -= width
end
end
alias_method :kill_word, :em_delete_next_word
private def ed_delete_prev_word(key)
if @byte_pointer > 0
- byte_size, width = Reline::Unicode.em_backward_word(@line, @byte_pointer)
- @line, word = byteslice!(@line, @byte_pointer - byte_size, byte_size)
+ byte_size, _ = Reline::Unicode.em_backward_word(current_line, @byte_pointer)
+ line, word = byteslice!(current_line, @byte_pointer - byte_size, byte_size)
+ set_current_line(line, @byte_pointer - byte_size)
@kill_ring.append(word, true)
- @byte_pointer -= byte_size
- @cursor -= width
- @cursor_max -= width
end
end
alias_method :backward_kill_word, :ed_delete_prev_word
private def ed_transpose_chars(key)
if @byte_pointer > 0
- if @cursor_max > @cursor
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
- mbchar = @line.byteslice(@byte_pointer, byte_size)
- width = Reline::Unicode.get_mbchar_width(mbchar)
- @cursor += width
+ if @byte_pointer < current_line.bytesize
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
@byte_pointer += byte_size
end
- back1_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
+ back1_byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
if (@byte_pointer - back1_byte_size) > 0
- back2_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer - back1_byte_size)
+ back2_byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer - back1_byte_size)
back2_pointer = @byte_pointer - back1_byte_size - back2_byte_size
- @line, back2_mbchar = byteslice!(@line, back2_pointer, back2_byte_size)
- @line = byteinsert(@line, @byte_pointer - back2_byte_size, back2_mbchar)
+ line, back2_mbchar = byteslice!(current_line, back2_pointer, back2_byte_size)
+ set_current_line(byteinsert(line, @byte_pointer - back2_byte_size, back2_mbchar))
end
end
end
alias_method :transpose_chars, :ed_transpose_chars
private def ed_transpose_words(key)
- left_word_start, middle_start, right_word_start, after_start = Reline::Unicode.ed_transpose_words(@line, @byte_pointer)
- before = @line.byteslice(0, left_word_start)
- left_word = @line.byteslice(left_word_start, middle_start - left_word_start)
- middle = @line.byteslice(middle_start, right_word_start - middle_start)
- right_word = @line.byteslice(right_word_start, after_start - right_word_start)
- after = @line.byteslice(after_start, @line.bytesize - after_start)
+ left_word_start, middle_start, right_word_start, after_start = Reline::Unicode.ed_transpose_words(current_line, @byte_pointer)
+ before = current_line.byteslice(0, left_word_start)
+ left_word = current_line.byteslice(left_word_start, middle_start - left_word_start)
+ middle = current_line.byteslice(middle_start, right_word_start - middle_start)
+ right_word = current_line.byteslice(right_word_start, after_start - right_word_start)
+ after = current_line.byteslice(after_start, current_line.bytesize - after_start)
return if left_word.empty? or right_word.empty?
- @line = before + right_word + middle + left_word + after
from_head_to_left_word = before + right_word + middle + left_word
- @byte_pointer = from_head_to_left_word.bytesize
- @cursor = calculate_width(from_head_to_left_word)
+ set_current_line(from_head_to_left_word + after, from_head_to_left_word.bytesize)
end
alias_method :transpose_words, :ed_transpose_words
private def em_capitol_case(key)
- if @line.bytesize > @byte_pointer
- byte_size, _, new_str = Reline::Unicode.em_forward_word_with_capitalization(@line, @byte_pointer)
- before = @line.byteslice(0, @byte_pointer)
- after = @line.byteslice((@byte_pointer + byte_size)..-1)
- @line = before + new_str + after
- @byte_pointer += new_str.bytesize
- @cursor += calculate_width(new_str)
+ if current_line.bytesize > @byte_pointer
+ byte_size, _, new_str = Reline::Unicode.em_forward_word_with_capitalization(current_line, @byte_pointer)
+ before = current_line.byteslice(0, @byte_pointer)
+ after = current_line.byteslice((@byte_pointer + byte_size)..-1)
+ set_current_line(before + new_str + after, @byte_pointer + new_str.bytesize)
end
end
alias_method :capitalize_word, :em_capitol_case
private def em_lower_case(key)
- if @line.bytesize > @byte_pointer
- byte_size, = Reline::Unicode.em_forward_word(@line, @byte_pointer)
- part = @line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
+ if current_line.bytesize > @byte_pointer
+ byte_size, = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
+ part = current_line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
mbchar =~ /[A-Z]/ ? mbchar.downcase : mbchar
}.join
- rest = @line.byteslice((@byte_pointer + byte_size)..-1)
- @line = @line.byteslice(0, @byte_pointer) + part
- @byte_pointer = @line.bytesize
- @cursor = calculate_width(@line)
- @cursor_max = @cursor + calculate_width(rest)
- @line += rest
+ rest = current_line.byteslice((@byte_pointer + byte_size)..-1)
+ line = current_line.byteslice(0, @byte_pointer) + part
+ set_current_line(line + rest, line.bytesize)
end
end
alias_method :downcase_word, :em_lower_case
private def em_upper_case(key)
- if @line.bytesize > @byte_pointer
- byte_size, = Reline::Unicode.em_forward_word(@line, @byte_pointer)
- part = @line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
+ if current_line.bytesize > @byte_pointer
+ byte_size, = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
+ part = current_line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
mbchar =~ /[a-z]/ ? mbchar.upcase : mbchar
}.join
- rest = @line.byteslice((@byte_pointer + byte_size)..-1)
- @line = @line.byteslice(0, @byte_pointer) + part
- @byte_pointer = @line.bytesize
- @cursor = calculate_width(@line)
- @cursor_max = @cursor + calculate_width(rest)
- @line += rest
+ rest = current_line.byteslice((@byte_pointer + byte_size)..-1)
+ line = current_line.byteslice(0, @byte_pointer) + part
+ set_current_line(line + rest, line.bytesize)
end
end
alias_method :upcase_word, :em_upper_case
private def em_kill_region(key)
if @byte_pointer > 0
- byte_size, width = Reline::Unicode.em_big_backward_word(@line, @byte_pointer)
- @line, deleted = byteslice!(@line, @byte_pointer - byte_size, byte_size)
- @byte_pointer -= byte_size
- @cursor -= width
- @cursor_max -= width
+ byte_size, _ = Reline::Unicode.em_big_backward_word(current_line, @byte_pointer)
+ line, deleted = byteslice!(current_line, @byte_pointer - byte_size, byte_size)
+ set_current_line(line, @byte_pointer - byte_size)
@kill_ring.append(deleted, true)
end
end
@@ -2820,10 +2122,9 @@ class Reline::LineEditor
alias_method :vi_movement_mode, :vi_command_mode
private def vi_next_word(key, arg: 1)
- if @line.bytesize > @byte_pointer
- byte_size, width = Reline::Unicode.vi_forward_word(@line, @byte_pointer, @drop_terminate_spaces)
+ if current_line.bytesize > @byte_pointer
+ byte_size, _ = Reline::Unicode.vi_forward_word(current_line, @byte_pointer, @drop_terminate_spaces)
@byte_pointer += byte_size
- @cursor += width
end
arg -= 1
vi_next_word(key, arg: arg) if arg > 0
@@ -2831,38 +2132,32 @@ class Reline::LineEditor
private def vi_prev_word(key, arg: 1)
if @byte_pointer > 0
- byte_size, width = Reline::Unicode.vi_backward_word(@line, @byte_pointer)
+ byte_size, _ = Reline::Unicode.vi_backward_word(current_line, @byte_pointer)
@byte_pointer -= byte_size
- @cursor -= width
end
arg -= 1
vi_prev_word(key, arg: arg) if arg > 0
end
private def vi_end_word(key, arg: 1, inclusive: false)
- if @line.bytesize > @byte_pointer
- byte_size, width = Reline::Unicode.vi_forward_end_word(@line, @byte_pointer)
+ if current_line.bytesize > @byte_pointer
+ byte_size, _ = Reline::Unicode.vi_forward_end_word(current_line, @byte_pointer)
@byte_pointer += byte_size
- @cursor += width
end
arg -= 1
if inclusive and arg.zero?
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
if byte_size > 0
- c = @line.byteslice(@byte_pointer, byte_size)
- width = Reline::Unicode.get_mbchar_width(c)
@byte_pointer += byte_size
- @cursor += width
end
end
vi_end_word(key, arg: arg) if arg > 0
end
private def vi_next_big_word(key, arg: 1)
- if @line.bytesize > @byte_pointer
- byte_size, width = Reline::Unicode.vi_big_forward_word(@line, @byte_pointer)
+ if current_line.bytesize > @byte_pointer
+ byte_size, _ = Reline::Unicode.vi_big_forward_word(current_line, @byte_pointer)
@byte_pointer += byte_size
- @cursor += width
end
arg -= 1
vi_next_big_word(key, arg: arg) if arg > 0
@@ -2870,50 +2165,39 @@ class Reline::LineEditor
private def vi_prev_big_word(key, arg: 1)
if @byte_pointer > 0
- byte_size, width = Reline::Unicode.vi_big_backward_word(@line, @byte_pointer)
+ byte_size, _ = Reline::Unicode.vi_big_backward_word(current_line, @byte_pointer)
@byte_pointer -= byte_size
- @cursor -= width
end
arg -= 1
vi_prev_big_word(key, arg: arg) if arg > 0
end
private def vi_end_big_word(key, arg: 1, inclusive: false)
- if @line.bytesize > @byte_pointer
- byte_size, width = Reline::Unicode.vi_big_forward_end_word(@line, @byte_pointer)
+ if current_line.bytesize > @byte_pointer
+ byte_size, _ = Reline::Unicode.vi_big_forward_end_word(current_line, @byte_pointer)
@byte_pointer += byte_size
- @cursor += width
end
arg -= 1
if inclusive and arg.zero?
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
if byte_size > 0
- c = @line.byteslice(@byte_pointer, byte_size)
- width = Reline::Unicode.get_mbchar_width(c)
@byte_pointer += byte_size
- @cursor += width
end
end
vi_end_big_word(key, arg: arg) if arg > 0
end
private def vi_delete_prev_char(key)
- if @is_multiline and @cursor == 0 and @line_index > 0
- @buffer_of_lines[@line_index] = @line
- @cursor = calculate_width(@buffer_of_lines[@line_index - 1])
+ if @byte_pointer == 0 and @line_index > 0
@byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
@buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
@line_index -= 1
- @line = @buffer_of_lines[@line_index]
- @cursor_max = calculate_width(@line)
- @rerender_all = true
- elsif @cursor > 0
- byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
+ process_auto_indent cursor_dependent: false
+ elsif @byte_pointer > 0
+ byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
@byte_pointer -= byte_size
- @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
- width = Reline::Unicode.get_mbchar_width(mbchar)
- @cursor -= width
- @cursor_max -= width
+ line, _ = byteslice!(current_line, @byte_pointer, byte_size)
+ set_current_line(line)
end
end
@@ -2928,78 +2212,81 @@ class Reline::LineEditor
end
private def ed_delete_prev_char(key, arg: 1)
- deleted = ''
+ deleted = +''
arg.times do
- if @cursor > 0
- byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
+ if @byte_pointer > 0
+ byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
@byte_pointer -= byte_size
- @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
+ line, mbchar = byteslice!(current_line, @byte_pointer, byte_size)
+ set_current_line(line)
deleted.prepend(mbchar)
- width = Reline::Unicode.get_mbchar_width(mbchar)
- @cursor -= width
- @cursor_max -= width
end
end
copy_for_vi(deleted)
end
- private def vi_zero(key)
- @byte_pointer = 0
- @cursor = 0
- end
-
- private def vi_change_meta(key, arg: 1)
- @drop_terminate_spaces = true
- @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
- @drop_terminate_spaces = false
- }
- @waiting_operator_vi_arg = arg
+ private def vi_change_meta(key, arg: nil)
+ if @vi_waiting_operator
+ set_current_line('', 0) if @vi_waiting_operator == :vi_change_meta_confirm && arg.nil?
+ @vi_waiting_operator = nil
+ @vi_waiting_operator_arg = nil
+ else
+ @drop_terminate_spaces = true
+ @vi_waiting_operator = :vi_change_meta_confirm
+ @vi_waiting_operator_arg = arg || 1
+ end
end
- private def vi_delete_meta(key, arg: 1)
- @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
- }
- @waiting_operator_vi_arg = arg
+ private def vi_change_meta_confirm(byte_pointer_diff)
+ vi_delete_meta_confirm(byte_pointer_diff)
+ @config.editing_mode = :vi_insert
+ @drop_terminate_spaces = false
end
- private def vi_yank(key, arg: 1)
- @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
- if byte_pointer_diff > 0
- cut = @line.byteslice(@byte_pointer, byte_pointer_diff)
- elsif byte_pointer_diff < 0
- cut = @line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff)
- end
- copy_for_vi(cut)
- }
- @waiting_operator_vi_arg = arg
+ private def vi_delete_meta(key, arg: nil)
+ if @vi_waiting_operator
+ set_current_line('', 0) if @vi_waiting_operator == :vi_delete_meta_confirm && arg.nil?
+ @vi_waiting_operator = nil
+ @vi_waiting_operator_arg = nil
+ else
+ @vi_waiting_operator = :vi_delete_meta_confirm
+ @vi_waiting_operator_arg = arg || 1
+ end
+ end
+
+ private def vi_delete_meta_confirm(byte_pointer_diff)
+ if byte_pointer_diff > 0
+ line, cut = byteslice!(current_line, @byte_pointer, byte_pointer_diff)
+ elsif byte_pointer_diff < 0
+ line, cut = byteslice!(current_line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
+ end
+ copy_for_vi(cut)
+ set_current_line(line || '', @byte_pointer + (byte_pointer_diff < 0 ? byte_pointer_diff : 0))
+ end
+
+ private def vi_yank(key, arg: nil)
+ if @vi_waiting_operator
+ copy_for_vi(current_line) if @vi_waiting_operator == :vi_yank_confirm && arg.nil?
+ @vi_waiting_operator = nil
+ @vi_waiting_operator_arg = nil
+ else
+ @vi_waiting_operator = :vi_yank_confirm
+ @vi_waiting_operator_arg = arg || 1
+ end
+ end
+
+ private def vi_yank_confirm(byte_pointer_diff)
+ if byte_pointer_diff > 0
+ cut = current_line.byteslice(@byte_pointer, byte_pointer_diff)
+ elsif byte_pointer_diff < 0
+ cut = current_line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff)
+ end
+ copy_for_vi(cut)
end
private def vi_list_or_eof(key)
- if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
- @line = nil
- if @buffer_of_lines.size > 1
- scroll_down(@highest_in_all - @first_line_started_from)
- end
- Reline::IOGate.move_cursor_column(0)
+ if current_line.empty? and @buffer_of_lines.size == 1
+ set_current_line('', 0)
@eof = true
finish
else
@@ -3010,18 +2297,15 @@ class Reline::LineEditor
alias_method :vi_eof_maybe, :vi_list_or_eof
private def ed_delete_next_char(key, arg: 1)
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
- unless @line.empty? || byte_size == 0
- @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
+ unless current_line.empty? || byte_size == 0
+ line, mbchar = byteslice!(current_line, @byte_pointer, byte_size)
copy_for_vi(mbchar)
- width = Reline::Unicode.get_mbchar_width(mbchar)
- @cursor_max -= width
- if @cursor > 0 and @cursor >= @cursor_max
- byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
- mbchar = @line.byteslice(@byte_pointer - byte_size, byte_size)
- width = Reline::Unicode.get_mbchar_width(mbchar)
- @byte_pointer -= byte_size
- @cursor -= width
+ if @byte_pointer > 0 && current_line.bytesize == @byte_pointer + byte_size
+ byte_size = Reline::Unicode.get_prev_mbchar_size(line, @byte_pointer)
+ set_current_line(line, @byte_pointer - byte_size)
+ else
+ set_current_line(line, @byte_pointer)
end
end
arg -= 1
@@ -3032,54 +2316,25 @@ class Reline::LineEditor
if Reline::HISTORY.empty?
return
end
- if @history_pointer.nil?
- @history_pointer = 0
- @line_backup_in_history = @line
- @line = Reline::HISTORY[@history_pointer]
- @cursor_max = calculate_width(@line)
- @cursor = 0
- @byte_pointer = 0
- elsif @history_pointer.zero?
- return
- else
- Reline::HISTORY[@history_pointer] = @line
- @history_pointer = 0
- @line = Reline::HISTORY[@history_pointer]
- @cursor_max = calculate_width(@line)
- @cursor = 0
- @byte_pointer = 0
- end
+ move_history(0, line: :start, cursor: :start)
end
private def vi_histedit(key)
path = Tempfile.open { |fp|
- if @is_multiline
- fp.write whole_lines.join("\n")
- else
- fp.write @line
- end
+ fp.write whole_lines.join("\n")
fp.path
}
system("#{ENV['EDITOR']} #{path}")
- if @is_multiline
- @buffer_of_lines = File.read(path).split("\n")
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
- @line_index = 0
- @line = @buffer_of_lines[@line_index]
- @rerender_all = true
- else
- @line = File.read(path)
- end
+ @buffer_of_lines = File.read(path).split("\n")
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
+ @line_index = 0
finish
end
private def vi_paste_prev(key, arg: 1)
if @vi_clipboard.size > 0
- @line = byteinsert(@line, @byte_pointer, @vi_clipboard)
- @cursor_max += calculate_width(@vi_clipboard)
cursor_point = @vi_clipboard.grapheme_clusters[0..-2].join
- @cursor += calculate_width(cursor_point)
- @byte_pointer += cursor_point.bytesize
+ set_current_line(byteinsert(current_line, @byte_pointer, @vi_clipboard), @byte_pointer + cursor_point.bytesize)
end
arg -= 1
vi_paste_prev(key, arg: arg) if arg > 0
@@ -3087,11 +2342,9 @@ class Reline::LineEditor
private def vi_paste_next(key, arg: 1)
if @vi_clipboard.size > 0
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
- @line = byteinsert(@line, @byte_pointer + byte_size, @vi_clipboard)
- @cursor_max += calculate_width(@vi_clipboard)
- @cursor += calculate_width(@vi_clipboard)
- @byte_pointer += @vi_clipboard.bytesize
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
+ line = byteinsert(current_line, @byte_pointer + byte_size, @vi_clipboard)
+ set_current_line(line, @byte_pointer + @vi_clipboard.bytesize)
end
arg -= 1
vi_paste_next(key, arg: arg) if arg > 0
@@ -3115,43 +2368,33 @@ class Reline::LineEditor
end
private def vi_to_column(key, arg: 0)
- @byte_pointer, @cursor = @line.grapheme_clusters.inject([0, 0]) { |total, gc|
- # total has [byte_size, cursor]
+ # Implementing behavior of vi, not Readline's vi-mode.
+ @byte_pointer, = current_line.grapheme_clusters.inject([0, 0]) { |(total_byte_size, total_width), gc|
mbchar_width = Reline::Unicode.get_mbchar_width(gc)
- if (total.last + mbchar_width) >= arg
- break total
- elsif (total.last + mbchar_width) >= @cursor_max
- break total
- else
- total = [total.first + gc.bytesize, total.last + mbchar_width]
- total
- end
+ break [total_byte_size, total_width] if (total_width + mbchar_width) >= arg
+ [total_byte_size + gc.bytesize, total_width + mbchar_width]
}
end
private def vi_replace_char(key, arg: 1)
@waiting_proc = ->(k) {
if arg == 1
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
- before = @line.byteslice(0, @byte_pointer)
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
+ before = current_line.byteslice(0, @byte_pointer)
remaining_point = @byte_pointer + byte_size
- after = @line.byteslice(remaining_point, @line.bytesize - remaining_point)
- @line = before + k.chr + after
- @cursor_max = calculate_width(@line)
+ after = current_line.byteslice(remaining_point, current_line.bytesize - remaining_point)
+ set_current_line(before + k.chr + after)
@waiting_proc = nil
elsif arg > 1
byte_size = 0
arg.times do
- byte_size += Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer + byte_size)
+ byte_size += Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer + byte_size)
end
- before = @line.byteslice(0, @byte_pointer)
+ before = current_line.byteslice(0, @byte_pointer)
remaining_point = @byte_pointer + byte_size
- after = @line.byteslice(remaining_point, @line.bytesize - remaining_point)
+ after = current_line.byteslice(remaining_point, current_line.bytesize - remaining_point)
replaced = k.chr * arg
- @line = before + replaced + after
- @byte_pointer += replaced.bytesize
- @cursor += calculate_width(replaced)
- @cursor_max = calculate_width(@line)
+ set_current_line(before + replaced + after, @byte_pointer + replaced.bytesize)
@waiting_proc = nil
end
}
@@ -3174,7 +2417,7 @@ class Reline::LineEditor
prev_total = nil
total = nil
found = false
- @line.byteslice(@byte_pointer..-1).grapheme_clusters.each do |mbchar|
+ current_line.byteslice(@byte_pointer..-1).grapheme_clusters.each do |mbchar|
# total has [byte_size, cursor]
unless total
# skip cursor point
@@ -3194,21 +2437,16 @@ class Reline::LineEditor
end
end
if not need_prev_char and found and total
- byte_size, width = total
+ byte_size, _ = total
@byte_pointer += byte_size
- @cursor += width
elsif need_prev_char and found and prev_total
- byte_size, width = prev_total
+ byte_size, _ = prev_total
@byte_pointer += byte_size
- @cursor += width
end
if inclusive
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
if byte_size > 0
- c = @line.byteslice(@byte_pointer, byte_size)
- width = Reline::Unicode.get_mbchar_width(c)
@byte_pointer += byte_size
- @cursor += width
end
end
@waiting_proc = nil
@@ -3231,7 +2469,7 @@ class Reline::LineEditor
prev_total = nil
total = nil
found = false
- @line.byteslice(0..@byte_pointer).grapheme_clusters.reverse_each do |mbchar|
+ current_line.byteslice(0..@byte_pointer).grapheme_clusters.reverse_each do |mbchar|
# total has [byte_size, cursor]
unless total
# skip cursor point
@@ -3251,26 +2489,19 @@ class Reline::LineEditor
end
end
if not need_next_char and found and total
- byte_size, width = total
+ byte_size, _ = total
@byte_pointer -= byte_size
- @cursor -= width
elsif need_next_char and found and prev_total
- byte_size, width = prev_total
+ byte_size, _ = prev_total
@byte_pointer -= byte_size
- @cursor -= width
end
@waiting_proc = nil
end
private def vi_join_lines(key, arg: 1)
- if @is_multiline and @buffer_of_lines.size > @line_index + 1
- @cursor = calculate_width(@line)
- @byte_pointer = @line.bytesize
- @line += ' ' + @buffer_of_lines.delete_at(@line_index + 1).lstrip
- @cursor_max = calculate_width(@line)
- @buffer_of_lines[@line_index] = @line
- @rerender_all = true
- @rest_height += 1
+ if @buffer_of_lines.size > @line_index + 1
+ next_line = @buffer_of_lines.delete_at(@line_index + 1).lstrip
+ set_current_line(current_line + ' ' + next_line, current_line.bytesize)
end
arg -= 1
vi_join_lines(key, arg: arg) if arg > 0
@@ -3284,14 +2515,27 @@ class Reline::LineEditor
private def em_exchange_mark(key)
return unless @mark_pointer
new_pointer = [@byte_pointer, @line_index]
- @previous_line_index = @line_index
@byte_pointer, @line_index = @mark_pointer
- @cursor = calculate_width(@line.byteslice(0, @byte_pointer))
- @cursor_max = calculate_width(@line)
@mark_pointer = new_pointer
end
alias_method :exchange_point_and_mark, :em_exchange_mark
- private def em_meta_next(key)
+ private def emacs_editing_mode(key)
+ @config.editing_mode = :emacs
+ end
+
+ private def vi_editing_mode(key)
+ @config.editing_mode = :vi_insert
+ end
+
+ private def undo(_key)
+ return if @past_lines.empty?
+
+ @undoing = true
+
+ target_lines, target_cursor_x, target_cursor_y = @past_lines.last
+ set_current_lines(target_lines, target_cursor_x, target_cursor_y)
+
+ @past_lines.pop
end
end
diff --git a/lib/reline/reline.gemspec b/lib/reline/reline.gemspec
index 7bf1f8758b..dfaf966728 100644
--- a/lib/reline/reline.gemspec
+++ b/lib/reline/reline.gemspec
@@ -18,6 +18,11 @@ Gem::Specification.new do |spec|
spec.files = Dir['BSDL', 'COPYING', 'README.md', 'license_of_rb-readline', 'lib/**/*']
spec.require_paths = ['lib']
+ spec.metadata = {
+ "bug_tracker_uri" => "https://github.com/ruby/reline/issues",
+ "changelog_uri" => "https://github.com/ruby/reline/releases",
+ "source_code_uri" => "https://github.com/ruby/reline"
+ }
spec.required_ruby_version = Gem::Requirement.new('>= 2.6')
diff --git a/lib/reline/terminfo.rb b/lib/reline/terminfo.rb
index 2cfa32b9f7..6885a0c6be 100644
--- a/lib/reline/terminfo.rb
+++ b/lib/reline/terminfo.rb
@@ -80,23 +80,11 @@ module Reline::Terminfo
def self.setupterm(term, fildes)
errret_int = Fiddle::Pointer.malloc(Fiddle::SIZEOF_INT)
ret = @setupterm.(term, fildes, errret_int)
- errret = errret_int[0, Fiddle::SIZEOF_INT].unpack1('i')
case ret
when 0 # OK
- 0
+ @term_supported = true
when -1 # ERR
- case errret
- when 1
- raise TerminfoError.new('The terminal is hardcopy, cannot be used for curses applications.')
- when 0
- raise TerminfoError.new('The terminal could not be found, or that it is a generic type, having too little information for curses applications to run.')
- when -1
- raise TerminfoError.new('The terminfo database could not be found.')
- else # unknown
- -1
- end
- else # unknown
- -2
+ @term_supported = false
end
end
@@ -148,9 +136,14 @@ module Reline::Terminfo
num
end
+ # NOTE: This means Fiddle and curses are enabled.
def self.enabled?
true
end
+
+ def self.term_supported?
+ @term_supported
+ end
end if Reline::Terminfo.curses_dl
module Reline::Terminfo
diff --git a/lib/reline/unicode.rb b/lib/reline/unicode.rb
index 26ef207ba6..d7460d6d4a 100644
--- a/lib/reline/unicode.rb
+++ b/lib/reline/unicode.rb
@@ -43,11 +43,13 @@ class Reline::Unicode
def self.escape_for_print(str)
str.chars.map! { |gr|
- escaped = EscapedPairs[gr.ord]
- if escaped && gr != -"\n" && gr != -"\t"
- escaped
- else
+ case gr
+ when -"\n"
gr
+ when -"\t"
+ -' '
+ else
+ EscapedPairs[gr.ord] || gr
end
}.join
end
@@ -128,10 +130,10 @@ class Reline::Unicode
end
end
- def self.split_by_width(str, max_width, encoding = str.encoding)
+ def self.split_by_width(str, max_width, encoding = str.encoding, offset: 0)
lines = [String.new(encoding: encoding)]
height = 1
- width = 0
+ width = offset
rest = str.encode(Encoding::UTF_8)
in_zero_width = false
seq = String.new(encoding: encoding)
@@ -145,7 +147,13 @@ class Reline::Unicode
lines.last << NON_PRINTING_END
when csi
lines.last << csi
- seq << csi
+ unless in_zero_width
+ if csi == -"\e[m" || csi == -"\e[0m"
+ seq.clear
+ else
+ seq << csi
+ end
+ end
when osc
lines.last << osc
seq << osc
@@ -173,32 +181,78 @@ class Reline::Unicode
# Take a chunk of a String cut by width with escape sequences.
def self.take_range(str, start_col, max_width)
+ take_mbchar_range(str, start_col, max_width).first
+ end
+
+ def self.take_mbchar_range(str, start_col, width, cover_begin: false, cover_end: false, padding: false)
chunk = String.new(encoding: str.encoding)
+
+ end_col = start_col + width
total_width = 0
rest = str.encode(Encoding::UTF_8)
in_zero_width = false
+ chunk_start_col = nil
+ chunk_end_col = nil
+ has_csi = false
rest.scan(WIDTH_SCANNER) do |non_printing_start, non_printing_end, csi, osc, gc|
case
when non_printing_start
in_zero_width = true
+ chunk << NON_PRINTING_START
when non_printing_end
in_zero_width = false
+ chunk << NON_PRINTING_END
when csi
+ has_csi = true
chunk << csi
when osc
chunk << osc
when gc
if in_zero_width
chunk << gc
+ next
+ end
+
+ mbchar_width = get_mbchar_width(gc)
+ prev_width = total_width
+ total_width += mbchar_width
+
+ if (cover_begin || padding ? total_width <= start_col : prev_width < start_col)
+ # Current character haven't reached start_col yet
+ next
+ elsif padding && !cover_begin && prev_width < start_col && start_col < total_width
+ # Add preceding padding. This padding might have background color.
+ chunk << ' '
+ chunk_start_col ||= start_col
+ chunk_end_col = total_width
+ next
+ elsif (cover_end ? prev_width < end_col : total_width <= end_col)
+ # Current character is in the range
+ chunk << gc
+ chunk_start_col ||= prev_width
+ chunk_end_col = total_width
+ break if total_width >= end_col
else
- mbchar_width = get_mbchar_width(gc)
- total_width += mbchar_width
- break if (start_col + max_width) < total_width
- chunk << gc if start_col < total_width
+ # Current character exceeds end_col
+ if padding && end_col < total_width
+ # Add succeeding padding. This padding might have background color.
+ chunk << ' '
+ chunk_start_col ||= prev_width
+ chunk_end_col = end_col
+ end
+ break
end
end
end
- chunk
+ chunk_start_col ||= start_col
+ chunk_end_col ||= start_col
+ if padding && chunk_end_col < end_col
+ # Append padding. This padding should not include background color.
+ chunk << "\e[0m" if has_csi
+ chunk << ' ' * (end_col - chunk_end_col)
+ chunk_end_col = end_col
+ end
+ [chunk, chunk_start_col, chunk_end_col - chunk_start_col]
end
def self.get_next_mbchar_size(line, byte_pointer)
diff --git a/lib/reline/version.rb b/lib/reline/version.rb
index 194d16e69a..46613a5952 100644
--- a/lib/reline/version.rb
+++ b/lib/reline/version.rb
@@ -1,3 +1,3 @@
module Reline
- VERSION = '0.4.1'
+ VERSION = '0.5.7'
end
diff --git a/lib/reline/windows.rb b/lib/reline/windows.rb
index 6f635f630f..ee3f73e383 100644
--- a/lib/reline/windows.rb
+++ b/lib/reline/windows.rb
@@ -1,6 +1,8 @@
require 'fiddle/import'
class Reline::Windows
+ RESET_COLOR = "\e[0m"
+
def self.encoding
Encoding::UTF_8
end
@@ -85,7 +87,7 @@ class Reline::Windows
def call(*args)
import = @proto.split("")
args.each_with_index do |x, i|
- args[i], = [x == 0 ? nil : x].pack("p").unpack(POINTER_TYPE) if import[i] == "S"
+ args[i], = [x == 0 ? nil : +x].pack("p").unpack(POINTER_TYPE) if import[i] == "S"
args[i], = [x].pack("I").unpack("i") if import[i] == "I"
end
ret, = @func.call(*args)
@@ -257,7 +259,7 @@ class Reline::Windows
def self.check_input_event
num_of_events = 0.chr * 8
while @@output_buf.empty?
- Reline.core.line_editor.resize
+ Reline.core.line_editor.handle_signal
if @@WaitForSingleObject.(@@hConsoleInputHandle, 100) != 0 # max 0.1 sec
# prevent for background consolemode change
@@legacy_console = (getconsolemode() & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0)
diff --git a/test/irb/command/test_custom_command.rb b/test/irb/command/test_custom_command.rb
new file mode 100644
index 0000000000..3a3ad11d5a
--- /dev/null
+++ b/test/irb/command/test_custom_command.rb
@@ -0,0 +1,149 @@
+# frozen_string_literal: true
+require "irb"
+
+require_relative "../helper"
+
+module TestIRB
+ class CustomCommandIntegrationTest < TestIRB::IntegrationTestCase
+ def test_command_registration_can_happen_after_irb_require
+ write_ruby <<~RUBY
+ require "irb"
+ require "irb/command"
+
+ class PrintCommand < IRB::Command::Base
+ category 'CommandTest'
+ description 'print_command'
+ def execute(*)
+ puts "Hello from PrintCommand"
+ end
+ end
+
+ IRB::Command.register(:print!, PrintCommand)
+
+ binding.irb
+ RUBY
+
+ output = run_ruby_file do
+ type "print!"
+ type "exit"
+ end
+
+ assert_include(output, "Hello from PrintCommand")
+ end
+
+ def test_command_registration_accepts_string_too
+ write_ruby <<~RUBY
+ require "irb/command"
+
+ class PrintCommand < IRB::Command::Base
+ category 'CommandTest'
+ description 'print_command'
+ def execute(*)
+ puts "Hello from PrintCommand"
+ end
+ end
+
+ IRB::Command.register("print!", PrintCommand)
+
+ binding.irb
+ RUBY
+
+ output = run_ruby_file do
+ type "print!"
+ type "exit"
+ end
+
+ assert_include(output, "Hello from PrintCommand")
+ end
+
+ def test_arguments_propagation
+ write_ruby <<~RUBY
+ require "irb/command"
+
+ class PrintArgCommand < IRB::Command::Base
+ category 'CommandTest'
+ description 'print_command_arg'
+ def execute(arg)
+ $nth_execution ||= 0
+ puts "\#{$nth_execution} arg=\#{arg.inspect}"
+ $nth_execution += 1
+ end
+ end
+
+ IRB::Command.register(:print_arg, PrintArgCommand)
+
+ binding.irb
+ RUBY
+
+ output = run_ruby_file do
+ type "print_arg"
+ type "print_arg \n"
+ type "print_arg a r g"
+ type "print_arg a r g \n"
+ type "exit"
+ end
+
+ assert_include(output, "0 arg=\"\"")
+ assert_include(output, "1 arg=\"\"")
+ assert_include(output, "2 arg=\"a r g\"")
+ assert_include(output, "3 arg=\"a r g\"")
+ end
+
+ def test_def_extend_command_still_works
+ write_ruby <<~RUBY
+ require "irb"
+
+ class FooBarCommand < IRB::Command::Base
+ category 'FooBarCategory'
+ description 'foobar_description'
+ def execute(*)
+ $nth_execution ||= 1
+ puts "\#{$nth_execution} FooBar executed"
+ $nth_execution += 1
+ end
+ end
+
+ IRB::ExtendCommandBundle.def_extend_command(:foobar, FooBarCommand, nil, [:fbalias, IRB::Command::OVERRIDE_ALL])
+
+ binding.irb
+ RUBY
+
+ output = run_ruby_file do
+ type "foobar"
+ type "fbalias"
+ type "help foobar"
+ type "exit"
+ end
+
+ assert_include(output, "1 FooBar executed")
+ assert_include(output, "2 FooBar executed")
+ assert_include(output, "foobar_description")
+ end
+
+ def test_no_meta_command_also_works
+ write_ruby <<~RUBY
+ require "irb/command"
+
+ class NoMetaCommand < IRB::Command::Base
+ def execute(*)
+ puts "This command does not override meta attributes"
+ end
+ end
+
+ IRB::Command.register(:no_meta, NoMetaCommand)
+
+ binding.irb
+ RUBY
+
+ output = run_ruby_file do
+ type "no_meta"
+ type "help no_meta"
+ type "exit"
+ end
+
+ assert_include(output, "This command does not override meta attributes")
+ assert_include(output, "No description provided.")
+ assert_not_include(output, "Maybe IRB bug")
+ end
+ end
+end
diff --git a/test/irb/command/test_force_exit.rb b/test/irb/command/test_force_exit.rb
new file mode 100644
index 0000000000..191a786872
--- /dev/null
+++ b/test/irb/command/test_force_exit.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: false
+require 'irb'
+
+require_relative "../helper"
+
+module TestIRB
+ class ForceExitTest < IntegrationTestCase
+ def test_forced_exit_finishes_process_immediately
+ write_ruby <<~'ruby'
+ puts "First line"
+ puts "Second line"
+ binding.irb
+ puts "Third line"
+ binding.irb
+ puts "Fourth line"
+ ruby
+
+ output = run_ruby_file do
+ type "123"
+ type "456"
+ type "exit!"
+ end
+
+ assert_match(/First line\r\n/, output)
+ assert_match(/Second line\r\n/, output)
+ assert_match(/irb\(main\):001> 123/, output)
+ assert_match(/irb\(main\):002> 456/, output)
+ refute_match(/Third line\r\n/, output)
+ refute_match(/Fourth line\r\n/, output)
+ end
+
+ def test_forced_exit_in_nested_sessions
+ write_ruby <<~'ruby'
+ def foo
+ binding.irb
+ end
+
+ binding.irb
+ binding.irb
+ ruby
+
+ output = run_ruby_file do
+ type "123"
+ type "foo"
+ type "exit!"
+ end
+
+ assert_match(/irb\(main\):001> 123/, output)
+ end
+ end
+end
diff --git a/test/irb/command/test_help.rb b/test/irb/command/test_help.rb
new file mode 100644
index 0000000000..b34832b022
--- /dev/null
+++ b/test/irb/command/test_help.rb
@@ -0,0 +1,75 @@
+require "tempfile"
+require_relative "../helper"
+
+module TestIRB
+ class HelpTest < IntegrationTestCase
+ def setup
+ super
+
+ write_rc <<~'RUBY'
+ IRB.conf[:USE_PAGER] = false
+ RUBY
+
+ write_ruby <<~'RUBY'
+ binding.irb
+ RUBY
+ end
+
+ def test_help
+ out = run_ruby_file do
+ type "help"
+ type "exit"
+ end
+
+ assert_match(/List all available commands/, out)
+ assert_match(/Start the debugger of debug\.gem/, out)
+ end
+
+ def test_command_help
+ out = run_ruby_file do
+ type "help ls"
+ type "exit"
+ end
+
+ assert_match(/Usage: ls \[obj\]/, out)
+ end
+
+ def test_command_help_not_found
+ out = run_ruby_file do
+ type "help foo"
+ type "exit"
+ end
+
+ assert_match(/Can't find command `foo`\. Please check the command name and try again\./, out)
+ end
+
+ def test_show_cmds
+ out = run_ruby_file do
+ type "help"
+ type "exit"
+ end
+
+ assert_match(/List all available commands/, out)
+ assert_match(/Start the debugger of debug\.gem/, out)
+ end
+
+ def test_help_lists_user_aliases
+ out = run_ruby_file do
+ type "help"
+ type "exit"
+ end
+
+ assert_match(/\$\s+Alias for `show_source`/, out)
+ assert_match(/@\s+Alias for `whereami`/, out)
+ end
+
+ def test_help_lists_helper_methods
+ out = run_ruby_file do
+ type "help"
+ type "exit"
+ end
+
+ assert_match(/Helper methods\s+conf\s+Returns the current IRB context/, out)
+ end
+ end
+end
diff --git a/test/irb/command/test_multi_irb_commands.rb b/test/irb/command/test_multi_irb_commands.rb
new file mode 100644
index 0000000000..e313c0c5d2
--- /dev/null
+++ b/test/irb/command/test_multi_irb_commands.rb
@@ -0,0 +1,50 @@
+require "tempfile"
+require_relative "../helper"
+
+module TestIRB
+ class MultiIRBTest < IntegrationTestCase
+ def setup
+ super
+
+ write_ruby <<~'RUBY'
+ binding.irb
+ RUBY
+ end
+
+ def test_jobs_command_with_print_deprecated_warning
+ out = run_ruby_file do
+ type "jobs"
+ type "exit"
+ end
+
+ assert_match(/Multi-irb commands are deprecated and will be removed in IRB 2\.0\.0\. Please use workspace commands instead\./, out)
+ assert_match(%r|If you have any use case for multi-irb, please leave a comment at https://github.com/ruby/irb/issues/653|, out)
+ assert_match(/#0->irb on main \(#<Thread:0x.+ run>: running\)/, out)
+ end
+
+ def test_irb_jobs_and_kill_commands
+ out = run_ruby_file do
+ type "irb"
+ type "jobs"
+ type "kill 1"
+ type "exit"
+ end
+
+ assert_match(/#0->irb on main \(#<Thread:0x.+ sleep_forever>: stop\)/, out)
+ assert_match(/#1->irb#1 on main \(#<Thread:0x.+ run>: running\)/, out)
+ end
+
+ def test_irb_fg_jobs_and_kill_commands
+ out = run_ruby_file do
+ type "irb"
+ type "fg 0"
+ type "jobs"
+ type "kill 1"
+ type "exit"
+ end
+
+ assert_match(/#0->irb on main \(#<Thread:0x.+ run>: running\)/, out)
+ assert_match(/#1->irb#1 on main \(#<Thread:0x.+ sleep_forever>: stop\)/, out)
+ end
+ end
+end
diff --git a/test/irb/cmd/test_show_source.rb b/test/irb/command/test_show_source.rb
index cedec8aa6f..d014c78fc4 100644
--- a/test/irb/cmd/test_show_source.rb
+++ b/test/irb/command/test_show_source.rb
@@ -52,6 +52,19 @@ module TestIRB
assert_match(%r[Couldn't locate a definition for foo], out)
end
+ def test_show_source_with_missing_constant
+ write_ruby <<~'RUBY'
+ binding.irb
+ RUBY
+
+ out = run_ruby_file do
+ type "show_source Foo"
+ type "exit"
+ end
+
+ assert_match(%r[Couldn't locate a definition for Foo], out)
+ end
+
def test_show_source_string
write_ruby <<~'RUBY'
binding.irb
@@ -272,5 +285,113 @@ module TestIRB
assert_match(%r[#{@ruby_file.to_path}:2\s+def foo\r\n end], out)
end
+
+ def test_show_source_with_double_colons
+ write_ruby <<~RUBY
+ class Foo
+ end
+
+ class Foo
+ class Bar
+ end
+ end
+
+ binding.irb
+ RUBY
+
+ out = run_ruby_file do
+ type "show_source ::Foo"
+ type "exit"
+ end
+
+ assert_match(%r[#{@ruby_file.to_path}:1\s+class Foo\r\nend], out)
+
+ out = run_ruby_file do
+ type "show_source ::Foo::Bar"
+ type "exit"
+ end
+
+ assert_match(%r[#{@ruby_file.to_path}:5\s+class Bar\r\n end], out)
+ end
+
+ def test_show_source_keep_script_lines
+ pend unless defined?(RubyVM.keep_script_lines)
+
+ write_ruby <<~RUBY
+ binding.irb
+ RUBY
+
+ out = run_ruby_file do
+ type "def foo; end"
+ type "show_source foo"
+ type "exit"
+ end
+
+ assert_match(%r[#{@ruby_file.to_path}\(irb\):1\s+def foo; end], out)
+ end
+
+ def test_show_source_unavailable_source
+ write_ruby <<~RUBY
+ binding.irb
+ RUBY
+
+ out = run_ruby_file do
+ type "RubyVM.keep_script_lines = false if defined?(RubyVM.keep_script_lines)"
+ type "def foo; end"
+ type "show_source foo"
+ type "exit"
+ end
+ assert_match(%r[#{@ruby_file.to_path}\(irb\):2\s+Source not available], out)
+ end
+
+ def test_show_source_shows_binary_source
+ write_ruby <<~RUBY
+ # io-console is an indirect dependency of irb
+ require "io/console"
+
+ binding.irb
+ RUBY
+
+ out = run_ruby_file do
+ # IO::ConsoleMode is defined in io-console gem's C extension
+ type "show_source IO::ConsoleMode"
+ type "exit"
+ end
+
+ # A safeguard to make sure the test subject is actually defined
+ refute_match(/NameError/, out)
+ assert_match(%r[Defined in binary file:.+io/console], out)
+ end
+
+ def test_show_source_with_constant_lookup
+ write_ruby <<~RUBY
+ X = 1
+ module M
+ Y = 1
+ Z = 2
+ end
+ class A
+ Z = 1
+ Array = 1
+ class B
+ include M
+ Object.new.instance_eval { binding.irb }
+ end
+ end
+ RUBY
+
+ out = run_ruby_file do
+ type "show_source X"
+ type "show_source Y"
+ type "show_source Z"
+ type "show_source Array"
+ type "exit"
+ end
+
+ assert_match(%r[#{@ruby_file.to_path}:1\s+X = 1], out)
+ assert_match(%r[#{@ruby_file.to_path}:3\s+Y = 1], out)
+ assert_match(%r[#{@ruby_file.to_path}:7\s+Z = 1], out)
+ assert_match(%r[#{@ruby_file.to_path}:8\s+Array = 1], out)
+ end
end
end
diff --git a/test/irb/helper.rb b/test/irb/helper.rb
index 38bdbb4c31..acaf6277f3 100644
--- a/test/irb/helper.rb
+++ b/test/irb/helper.rb
@@ -1,5 +1,6 @@
require "test/unit"
require "pathname"
+require "rubygems"
begin
require_relative "../lib/helper"
@@ -17,6 +18,7 @@ module IRB
end
module TestIRB
+ RUBY_3_4 = Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.4.0.dev")
class TestCase < Test::Unit::TestCase
class TestInputMethod < ::IRB::InputMethod
attr_reader :list, :line_no
@@ -119,7 +121,9 @@ module TestIRB
@envs["XDG_CONFIG_HOME"] ||= tmp_dir
@envs["IRBRC"] = nil unless @envs.key?("IRBRC")
- PTY.spawn(@envs.merge("TERM" => "dumb"), *cmd) do |read, write, pid|
+ envs_for_spawn = @envs.merge('TERM' => 'dumb', 'TEST_IRB_FORCE_INTERACTIVE' => 'true')
+
+ PTY.spawn(envs_for_spawn, *cmd) do |read, write, pid|
Timeout.timeout(TIMEOUT_SEC) do
while line = safe_gets(read)
lines << line
@@ -194,7 +198,7 @@ module TestIRB
end
def write_ruby(program)
- @ruby_file = Tempfile.create(%w{irb- .rb})
+ @ruby_file = Tempfile.create(%w{irbtest- .rb})
@tmpfiles << @ruby_file
@ruby_file.write(program)
@ruby_file.close
diff --git a/test/irb/test_color.rb b/test/irb/test_color.rb
index c9d2512dc5..9d78f5233e 100644
--- a/test/irb/test_color.rb
+++ b/test/irb/test_color.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: false
require 'irb/color'
-require 'rubygems'
require 'stringio'
require_relative "helper"
@@ -100,7 +99,7 @@ module TestIRB
"foo %i[bar]" => "foo #{YELLOW}%i[#{CLEAR}#{YELLOW}bar#{CLEAR}#{YELLOW}]#{CLEAR}",
"foo :@bar, baz, :@@qux, :$quux" => "foo #{YELLOW}:#{CLEAR}#{YELLOW}@bar#{CLEAR}, baz, #{YELLOW}:#{CLEAR}#{YELLOW}@@qux#{CLEAR}, #{YELLOW}:#{CLEAR}#{YELLOW}$quux#{CLEAR}",
"`echo`" => "#{RED}#{BOLD}`#{CLEAR}#{RED}echo#{CLEAR}#{RED}#{BOLD}`#{CLEAR}",
- "\t" => "\t", # not ^I
+ "\t" => Reline::Unicode.escape_for_print("\t") == ' ' ? ' ' : "\t", # not ^I
"foo(*%W(bar))" => "foo(*#{RED}#{BOLD}%W(#{CLEAR}#{RED}bar#{CLEAR}#{RED}#{BOLD})#{CLEAR})",
"$stdout" => "#{GREEN}#{BOLD}$stdout#{CLEAR}",
"__END__" => "#{GREEN}__END__#{CLEAR}",
diff --git a/test/irb/test_color_printer.rb b/test/irb/test_color_printer.rb
index f162b88548..c2c624d868 100644
--- a/test/irb/test_color_printer.rb
+++ b/test/irb/test_color_printer.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: false
require 'irb/color_printer'
-require 'rubygems'
require 'stringio'
require_relative "helper"
diff --git a/test/irb/test_cmd.rb b/test/irb/test_command.rb
index d99ac05c5d..8cb8928adb 100644
--- a/test/irb/test_cmd.rb
+++ b/test/irb/test_command.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: false
-require "rubygems"
require "irb"
-require "irb/extend-command"
require_relative "helper"
@@ -22,6 +20,7 @@ module TestIRB
@xdg_config_home_backup = ENV.delete("XDG_CONFIG_HOME")
save_encodings
IRB.instance_variable_get(:@CONF).clear
+ IRB.instance_variable_set(:@existing_rc_name_generators, nil)
@is_win = (RbConfig::CONFIG['host_os'] =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/)
end
@@ -34,17 +33,17 @@ module TestIRB
end
def execute_lines(*lines, conf: {}, main: self, irb_path: nil)
- IRB.init_config(nil)
- IRB.conf[:VERBOSE] = false
- IRB.conf[:PROMPT_MODE] = :SIMPLE
- IRB.conf[:USE_PAGER] = false
- IRB.conf.merge!(conf)
- input = TestInputMethod.new(lines)
- irb = IRB::Irb.new(IRB::WorkSpace.new(main), input)
- irb.context.return_format = "=> %s\n"
- irb.context.irb_path = irb_path if irb_path
- IRB.conf[:MAIN_CONTEXT] = irb.context
capture_output do
+ IRB.init_config(nil)
+ IRB.conf[:VERBOSE] = false
+ IRB.conf[:PROMPT_MODE] = :SIMPLE
+ IRB.conf[:USE_PAGER] = false
+ IRB.conf.merge!(conf)
+ input = TestInputMethod.new(lines)
+ irb = IRB::Irb.new(IRB::WorkSpace.new(main), input)
+ irb.context.return_format = "=> %s\n"
+ irb.context.irb_path = irb_path if irb_path
+ IRB.conf[:MAIN_CONTEXT] = irb.context
irb.eval_input
end
end
@@ -58,7 +57,7 @@ module TestIRB
"irb_info",
main: main
)
- assert_empty err
+ assert_empty(err)
assert_match(/RUBY_PLATFORM/, out)
end
end
@@ -77,6 +76,7 @@ module TestIRB
def test_irb_info_multiline
FileUtils.touch("#{@tmpdir}/.inputrc")
FileUtils.touch("#{@tmpdir}/.irbrc")
+ FileUtils.touch("#{@tmpdir}/_irbrc")
out, err = execute_lines(
"irb_info",
@@ -88,7 +88,7 @@ module TestIRB
IRB\sversion:\sirb\s.+\n
InputMethod:\sAbstract\sInputMethod\n
Completion: .+\n
- \.irbrc\spath:\s.+\n
+ \.irbrc\spaths:.*\.irbrc.*_irbrc\n
RUBY_PLATFORM:\s.+\n
East\sAsian\sAmbiguous\sWidth:\s\d\n
#{@is_win ? 'Code\spage:\s\d+\n' : ''}
@@ -112,7 +112,7 @@ module TestIRB
IRB\sversion:\sirb\s.+\n
InputMethod:\sAbstract\sInputMethod\n
Completion: .+\n
- \.irbrc\spath:\s.+\n
+ \.irbrc\spaths:\s.+\n
RUBY_PLATFORM:\s.+\n
East\sAsian\sAmbiguous\sWidth:\s\d\n
#{@is_win ? 'Code\spage:\s\d+\n' : ''}
@@ -198,7 +198,7 @@ module TestIRB
IRB\sversion:\sirb .+\n
InputMethod:\sAbstract\sInputMethod\n
Completion: .+\n
- \.irbrc\spath: .+\n
+ \.irbrc\spaths: .+\n
RUBY_PLATFORM: .+\n
LANG\senv:\sja_JP\.UTF-8\n
LC_ALL\senv:\sen_US\.UTF-8\n
@@ -373,17 +373,19 @@ module TestIRB
}
}
out, err = execute_lines(
- "measure :foo",
- "measure :on, :bar",
- "3\n",
+ "measure :foo\n",
+ "1\n",
+ "measure :on, :bar\n",
+ "2\n",
"measure :off, :foo\n",
- "measure :off, :bar\n",
"3\n",
+ "measure :off, :bar\n",
+ "4\n",
conf: conf
)
assert_empty err
- assert_match(/\AFOO is added\.\n=> nil\nfoo\nBAR is added\.\n=> nil\nbar\nfoo\n=> 3\nbar\nfoo\n=> nil\nbar\n=> nil\n=> 3\n/, out)
+ assert_match(/\AFOO is added\.\n=> nil\nfoo\n=> 1\nBAR is added\.\n=> nil\nbar\nfoo\n=> 2\n=> nil\nbar\n=> 3\n=> nil\n=> 4\n/, out)
end
def test_measure_with_proc_warning
@@ -402,7 +404,6 @@ module TestIRB
out, err = execute_lines(
"3\n",
"measure do\n",
- "end\n",
"3\n",
conf: conf,
main: c
@@ -484,62 +485,67 @@ module TestIRB
class CwwsTest < WorkspaceCommandTestCase
def test_cwws_returns_the_current_workspace_object
out, err = execute_lines(
- "cwws.class",
+ "cwws"
)
assert_empty err
- assert_include(out, self.class.name)
+ assert_include(out, "Current workspace: #{self}")
end
end
class PushwsTest < WorkspaceCommandTestCase
def test_pushws_switches_to_new_workspace_and_pushes_the_current_one_to_the_stack
out, err = execute_lines(
- "pushws #{self.class}::Foo.new\n",
- "cwws.class",
+ "pushws #{self.class}::Foo.new",
+ "self.class",
+ "popws",
+ "self.class"
)
assert_empty err
- assert_include(out, "#{self.class}::Foo")
+
+ assert_match(/=> #{self.class}::Foo\n/, out)
+ assert_match(/=> #{self.class}\n$/, out)
end
def test_pushws_extends_the_new_workspace_with_command_bundle
out, err = execute_lines(
- "pushws Object.new\n",
+ "pushws Object.new",
"self.singleton_class.ancestors"
)
assert_empty err
assert_include(out, "IRB::ExtendCommandBundle")
end
- def test_pushws_prints_help_message_when_no_arg_is_given
+ def test_pushws_prints_workspace_stack_when_no_arg_is_given
out, err = execute_lines(
- "pushws\n",
+ "pushws",
)
assert_empty err
- assert_match(/No other workspace/, out)
+ assert_include(out, "[#<TestIRB::PushwsTe...>]")
end
- end
- class WorkspacesTest < WorkspaceCommandTestCase
- def test_workspaces_returns_the_array_of_non_main_workspaces
+ def test_pushws_without_argument_swaps_the_top_two_workspaces
out, err = execute_lines(
- "pushws #{self.class}::Foo.new\n",
- "workspaces.map { |w| w.class.name }",
+ "pushws #{self.class}::Foo.new",
+ "self.class",
+ "pushws",
+ "self.class"
)
-
assert_empty err
- # self.class::Foo would be the current workspace
- # self.class would be the old workspace that's pushed to the stack
- assert_include(out, "=> [\"#{self.class}\"]")
+ assert_match(/=> #{self.class}::Foo\n/, out)
+ assert_match(/=> #{self.class}\n$/, out)
end
+ end
- def test_workspaces_returns_empty_array_when_no_workspaces_were_added
+ class WorkspacesTest < WorkspaceCommandTestCase
+ def test_workspaces_returns_the_stack_of_workspaces
out, err = execute_lines(
- "workspaces.map(&:to_s)",
+ "pushws #{self.class}::Foo.new\n",
+ "workspaces",
)
assert_empty err
- assert_include(out, "=> []")
+ assert_match(/\[#<TestIRB::Workspac...>, #<TestIRB::Workspac...>\]\n/, out)
end
end
@@ -548,7 +554,8 @@ module TestIRB
out, err = execute_lines(
"pushws Foo.new\n",
"popws\n",
- "cwws.class",
+ "cwws\n",
+ "self.class",
)
assert_empty err
assert_include(out, "=> #{self.class}")
@@ -559,7 +566,7 @@ module TestIRB
"popws\n",
)
assert_empty err
- assert_match(/workspace stack empty/, out)
+ assert_match(/\[#<TestIRB::PopwsTes...>\]\n/, out)
end
end
@@ -567,19 +574,20 @@ module TestIRB
def test_chws_replaces_the_current_workspace
out, err = execute_lines(
"chws #{self.class}::Foo.new\n",
- "cwws.class",
+ "cwws\n",
+ "self.class\n"
)
assert_empty err
+ assert_include(out, "Current workspace: #<#{self.class.name}::Foo")
assert_include(out, "=> #{self.class}::Foo")
end
def test_chws_does_nothing_when_receiving_no_argument
out, err = execute_lines(
"chws\n",
- "cwws.class",
)
assert_empty err
- assert_include(out, "=> #{self.class}")
+ assert_include(out, "Current workspace: #{self}")
end
end
@@ -601,29 +609,6 @@ module TestIRB
end
end
-
- class ShowCmdsTest < CommandTestCase
- def test_show_cmds
- out, err = execute_lines(
- "show_cmds\n"
- )
-
- assert_empty err
- assert_match(/List all available commands and their description/, out)
- assert_match(/Start the debugger of debug\.gem/, out)
- end
-
- def test_show_cmds_list_user_aliases
- out, err = execute_lines(
- "show_cmds\n"
- )
-
- assert_empty err
- assert_match(/\$\s+Alias for `show_source`/, out)
- assert_match(/@\s+Alias for `whereami`/, out)
- end
- end
-
class LsTest < CommandTestCase
def test_ls
out, err = execute_lines(
@@ -747,18 +732,18 @@ module TestIRB
def test_ls_grep_empty
out, err = execute_lines("ls\n")
assert_empty err
- assert_match(/whereami/, out)
- assert_match(/show_source/, out)
+ assert_match(/assert/, out)
+ assert_match(/refute/, out)
[
- "ls grep: /whereami/\n",
- "ls -g whereami\n",
- "ls -G whereami\n",
+ "ls grep: /assert/\n",
+ "ls -g assert\n",
+ "ls -G assert\n",
].each do |line|
out, err = execute_lines(line)
assert_empty err
- assert_match(/whereami/, out)
- assert_not_match(/show_source/, out)
+ assert_match(/assert/, out)
+ assert_not_match(/refute/, out)
end
end
@@ -774,22 +759,6 @@ module TestIRB
end
class ShowDocTest < CommandTestCase
- def test_help
- out, err = execute_lines(
- "help String#gsub\n",
- "\n",
- )
-
- # the former is what we'd get without document content installed, like on CI
- # the latter is what we may get locally
- possible_rdoc_output = [/Nothing known about String#gsub/, /gsub\(pattern\)/]
- assert_include err, "[Deprecation] The `help` command will be repurposed to display command help in the future.\n"
- assert(possible_rdoc_output.any? { |output| output.match?(out) }, "Expect the `help` command to match one of the possible outputs. Got:\n#{out}")
- ensure
- # this is the only way to reset the redefined method without coupling the test with its implementation
- EnvUtil.suppress_warning { load "irb/cmd/help.rb" }
- end
-
def test_show_doc
out, err = execute_lines(
"show_doc String#gsub\n",
@@ -803,7 +772,7 @@ module TestIRB
assert(possible_rdoc_output.any? { |output| output.match?(out) }, "Expect the `show_doc` command to match one of the possible outputs. Got:\n#{out}")
ensure
# this is the only way to reset the redefined method without coupling the test with its implementation
- EnvUtil.suppress_warning { load "irb/cmd/help.rb" }
+ EnvUtil.suppress_warning { load "irb/command/help.rb" }
end
def test_show_doc_without_rdoc
@@ -819,7 +788,7 @@ module TestIRB
assert_include(err, "Can't display document because `rdoc` is not installed.\n")
ensure
# this is the only way to reset the redefined method without coupling the test with its implementation
- EnvUtil.suppress_warning { load "irb/cmd/help.rb" }
+ EnvUtil.suppress_warning { load "irb/command/help.rb" }
end
end
@@ -848,6 +817,16 @@ module TestIRB
assert_match("command: ': code'", out)
end
+ def test_edit_without_arg_and_non_existing_irb_path
+ out, err = execute_lines(
+ "edit",
+ irb_path: '/path/to/file.rb(irb)'
+ )
+
+ assert_empty err
+ assert_match(/Can not find file: \/path\/to\/file\.rb\(irb\)/, out)
+ end
+
def test_edit_with_path
out, err = execute_lines(
"edit #{__FILE__}"
@@ -974,4 +953,19 @@ module TestIRB
end
+ class HelperMethodInsallTest < CommandTestCase
+ def test_helper_method_install
+ IRB::ExtendCommandBundle.module_eval do
+ def foobar
+ "test_helper_method_foobar"
+ end
+ end
+
+ out, err = execute_lines("foobar.upcase")
+ assert_empty err
+ assert_include(out, '=> "TEST_HELPER_METHOD_FOOBAR"')
+ ensure
+ IRB::ExtendCommandBundle.remove_method :foobar
+ end
+ end
end
diff --git a/test/irb/test_completion.rb b/test/irb/test_completion.rb
index 0625c96976..5fe7952b3d 100644
--- a/test/irb/test_completion.rb
+++ b/test/irb/test_completion.rb
@@ -14,6 +14,13 @@ module TestIRB
IRB::RegexpCompletor.new.doc_namespace('', target, '', bind: bind)
end
+ class CommandCompletionTest < CompletionTest
+ def test_command_completion
+ assert_include(IRB::RegexpCompletor.new.completion_candidates('', 'show_s', '', bind: binding), 'show_source')
+ assert_not_include(IRB::RegexpCompletor.new.completion_candidates(';', 'show_s', '', bind: binding), 'show_source')
+ end
+ end
+
class MethodCompletionTest < CompletionTest
def test_complete_string
assert_include(completion_candidates("'foo'.up", binding), "'foo'.upcase")
@@ -124,8 +131,9 @@ module TestIRB
end
def test_complete_require_library_name_first
- candidates = IRB::RegexpCompletor.new.completion_candidates("require ", "'csv", "", bind: binding)
- assert_equal "'csv", candidates.first
+ # Test that library name is completed first with subdirectories
+ candidates = IRB::RegexpCompletor.new.completion_candidates("require ", "'irb", "", bind: binding)
+ assert_equal "'irb", candidates.first
end
def test_complete_require_relative
@@ -204,6 +212,13 @@ module TestIRB
end
end
+ def test_not_completing_empty_string
+ assert_equal([], completion_candidates("", binding))
+ assert_equal([], completion_candidates(" ", binding))
+ assert_equal([], completion_candidates("\t", binding))
+ assert_equal(nil, doc_namespace("", binding))
+ end
+
def test_complete_symbol
symbols = %w"UTF-16LE UTF-7".map do |enc|
"K".force_encoding(enc).to_sym
diff --git a/test/irb/test_context.rb b/test/irb/test_context.rb
index 79186f13a1..cd3f2c8f62 100644
--- a/test/irb/test_context.rb
+++ b/test/irb/test_context.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: false
require 'tempfile'
require 'irb'
-require 'rubygems' if defined?(Gem)
require_relative "helper"
@@ -29,27 +28,6 @@ module TestIRB
restore_encodings
end
- def test_last_value
- assert_nil(@context.last_value)
- assert_nil(@context.evaluate('_', 1))
- obj = Object.new
- @context.set_last_value(obj)
- assert_same(obj, @context.last_value)
- assert_same(obj, @context.evaluate('_', 1))
- end
-
- def test_evaluate_with_encoding_error_without_lineno
- assert_raise_with_message(EncodingError, /invalid symbol/) {
- @context.evaluate(%q[:"\xAE"], 1)
- # The backtrace of this invalid encoding hash doesn't contain lineno.
- }
- end
-
- def test_evaluate_still_emits_warning
- assert_warning("(irb):1: warning: END in method; use at_exit\n") do
- @context.evaluate(%q[def foo; END {}; end], 1)
- end
- end
def test_eval_input
verbose, $VERBOSE = $VERBOSE, nil
@@ -64,11 +42,27 @@ module TestIRB
irb.eval_input
end
assert_empty err
- assert_pattern_list([:*, /\(irb\):1:in `<main>': Foo \(RuntimeError\)\n/,
- :*, /#<RuntimeError: Foo>\n/,
- :*, /0$/,
- :*, /0$/,
- /\s*/], out)
+
+ expected_output =
+ if RUBY_3_4
+ [
+ :*, /\(irb\):1:in '<main>': Foo \(RuntimeError\)\n/,
+ :*, /#<RuntimeError: Foo>\n/,
+ :*, /0$/,
+ :*, /0$/,
+ /\s*/
+ ]
+ else
+ [
+ :*, /\(irb\):1:in `<main>': Foo \(RuntimeError\)\n/,
+ :*, /#<RuntimeError: Foo>\n/,
+ :*, /0$/,
+ :*, /0$/,
+ /\s*/
+ ]
+ end
+
+ assert_pattern_list(expected_output, out)
ensure
$VERBOSE = verbose
end
@@ -84,11 +78,21 @@ module TestIRB
irb.eval_input
end
assert_empty err
- assert_pattern_list([
- :*, /\(irb\):1:in `<main>': Foo \(RuntimeError\)\n/,
- :*, /\(irb\):2:in `<main>': Bar \(RuntimeError\)\n/,
- :*, /#<RuntimeError: Bar>\n/,
- ], out)
+ expected_output =
+ if RUBY_3_4
+ [
+ :*, /\(irb\):1:in '<main>': Foo \(RuntimeError\)\n/,
+ :*, /\(irb\):2:in '<main>': Bar \(RuntimeError\)\n/,
+ :*, /#<RuntimeError: Bar>\n/,
+ ]
+ else
+ [
+ :*, /\(irb\):1:in `<main>': Foo \(RuntimeError\)\n/,
+ :*, /\(irb\):2:in `<main>': Bar \(RuntimeError\)\n/,
+ :*, /#<RuntimeError: Bar>\n/,
+ ]
+ end
+ assert_pattern_list(expected_output, out)
end
def test_prompt_n_deprecation
@@ -126,9 +130,9 @@ module TestIRB
[:marshal, "123", Marshal.dump(123)],
],
failed: [
- [false, "BasicObject.new", /#<NoMethodError: undefined method `to_s' for/],
- [:p, "class Foo; undef inspect ;end; Foo.new", /#<NoMethodError: undefined method `inspect' for/],
- [:yaml, "BasicObject.new", /#<NoMethodError: undefined method `inspect' for/],
+ [false, "BasicObject.new", /#<NoMethodError: undefined method (`|')to_s' for/],
+ [:p, "class Foo; undef inspect ;end; Foo.new", /#<NoMethodError: undefined method (`|')inspect' for/],
+ [:yaml, "BasicObject.new", /#<NoMethodError: undefined method (`|')inspect' for/],
[:marshal, "[Object.new, Class.new]", /#<TypeError: can't dump anonymous class #<Class:/]
]
}.each do |scenario, cases|
@@ -206,7 +210,7 @@ module TestIRB
end
assert_empty err
assert_match(/An error occurred when inspecting the object: #<RuntimeError: foo>/, out)
- assert_match(/An error occurred when running Kernel#inspect: #<NoMethodError: undefined method `inspect' for/, out)
+ assert_match(/An error occurred when running Kernel#inspect: #<NoMethodError: undefined method (`|')inspect' for/, out)
ensure
$VERBOSE = verbose
end
@@ -349,7 +353,7 @@ module TestIRB
end
assert_empty err
assert_equal("=> \n#{value}\n", out)
- irb.context.evaluate('A.remove_method(:inspect)', 0)
+ irb.context.evaluate_expression('A.remove_method(:inspect)', 0)
input.reset
irb.context.echo = true
@@ -359,7 +363,7 @@ module TestIRB
end
assert_empty err
assert_equal("=> #{value_first_line[0..(input.winsize.last - 9)]}...\n=> \n#{value}\n", out)
- irb.context.evaluate('A.remove_method(:inspect)', 0)
+ irb.context.evaluate_expression('A.remove_method(:inspect)', 0)
input.reset
irb.context.echo = true
@@ -369,7 +373,7 @@ module TestIRB
end
assert_empty err
assert_equal("=> \n#{value}\n=> \n#{value}\n", out)
- irb.context.evaluate('A.remove_method(:inspect)', 0)
+ irb.context.evaluate_expression('A.remove_method(:inspect)', 0)
input.reset
irb.context.echo = false
@@ -379,7 +383,7 @@ module TestIRB
end
assert_empty err
assert_equal("", out)
- irb.context.evaluate('A.remove_method(:inspect)', 0)
+ irb.context.evaluate_expression('A.remove_method(:inspect)', 0)
input.reset
irb.context.echo = false
@@ -389,7 +393,7 @@ module TestIRB
end
assert_empty err
assert_equal("", out)
- irb.context.evaluate('A.remove_method(:inspect)', 0)
+ irb.context.evaluate_expression('A.remove_method(:inspect)', 0)
input.reset
irb.context.echo = false
@@ -399,7 +403,7 @@ module TestIRB
end
assert_empty err
assert_equal("", out)
- irb.context.evaluate('A.remove_method(:inspect)', 0)
+ irb.context.evaluate_expression('A.remove_method(:inspect)', 0)
end
end
@@ -496,22 +500,30 @@ module TestIRB
irb.eval_input
end
assert_empty err
- if RUBY_VERSION < '3.0.0' && STDOUT.tty?
- expected = [
- :*, /Traceback \(most recent call last\):\n/,
- :*, /\t 2: from \(irb\):1:in `<main>'\n/,
- :*, /\t 1: from \(irb\):1:in `hoge'\n/,
- :*, /\(irb\):1:in `fuga': unhandled exception\n/,
- ]
- else
- expected = [
- :*, /\(irb\):1:in `fuga': unhandled exception\n/,
- :*, /\tfrom \(irb\):1:in `hoge'\n/,
- :*, /\tfrom \(irb\):1:in `<main>'\n/,
- :*
- ]
- end
- assert_pattern_list(expected, out)
+ expected_output =
+ if RUBY_3_4
+ [
+ :*, /\(irb\):1:in 'fuga': unhandled exception\n/,
+ :*, /\tfrom \(irb\):1:in 'hoge'\n/,
+ :*, /\tfrom \(irb\):1:in '<main>'\n/,
+ :*
+ ]
+ elsif RUBY_VERSION < '3.0.0' && STDOUT.tty?
+ [
+ :*, /Traceback \(most recent call last\):\n/,
+ :*, /\t 2: from \(irb\):1:in `<main>'\n/,
+ :*, /\t 1: from \(irb\):1:in `hoge'\n/,
+ :*, /\(irb\):1:in `fuga': unhandled exception\n/,
+ ]
+ else
+ [
+ :*, /\(irb\):1:in `fuga': unhandled exception\n/,
+ :*, /\tfrom \(irb\):1:in `hoge'\n/,
+ :*, /\tfrom \(irb\):1:in `<main>'\n/,
+ :*
+ ]
+ end
+ assert_pattern_list(expected_output, out)
ensure
$VERBOSE = verbose
end
@@ -526,22 +538,31 @@ module TestIRB
irb.eval_input
end
assert_empty err
- if RUBY_VERSION < '3.0.0' && STDOUT.tty?
- expected = [
- :*, /Traceback \(most recent call last\):\n/,
- :*, /\t 2: from \(irb\):1:in `<main>'\n/,
- :*, /\t 1: from \(irb\):1:in `hoge'\n/,
- :*, /\(irb\):1:in `fuga': A\\xF3B \(RuntimeError\)\n/,
- ]
- else
- expected = [
- :*, /\(irb\):1:in `fuga': A\\xF3B \(RuntimeError\)\n/,
- :*, /\tfrom \(irb\):1:in `hoge'\n/,
- :*, /\tfrom \(irb\):1:in `<main>'\n/,
- :*
- ]
- end
- assert_pattern_list(expected, out)
+ expected_output =
+ if RUBY_3_4
+ [
+ :*, /\(irb\):1:in 'fuga': A\\xF3B \(RuntimeError\)\n/,
+ :*, /\tfrom \(irb\):1:in 'hoge'\n/,
+ :*, /\tfrom \(irb\):1:in '<main>'\n/,
+ :*
+ ]
+ elsif RUBY_VERSION < '3.0.0' && STDOUT.tty?
+ [
+ :*, /Traceback \(most recent call last\):\n/,
+ :*, /\t 2: from \(irb\):1:in `<main>'\n/,
+ :*, /\t 1: from \(irb\):1:in `hoge'\n/,
+ :*, /\(irb\):1:in `fuga': A\\xF3B \(RuntimeError\)\n/,
+ ]
+ else
+ [
+ :*, /\(irb\):1:in `fuga': A\\xF3B \(RuntimeError\)\n/,
+ :*, /\tfrom \(irb\):1:in `hoge'\n/,
+ :*, /\tfrom \(irb\):1:in `<main>'\n/,
+ :*
+ ]
+ end
+
+ assert_pattern_list(expected_output, out)
ensure
$VERBOSE = verbose
end
@@ -567,43 +588,43 @@ module TestIRB
expected = [
:*, /Traceback \(most recent call last\):\n/,
:*, /\t... \d+ levels...\n/,
- :*, /\t16: from \(irb\):1:in `a4'\n/,
- :*, /\t15: from \(irb\):1:in `a5'\n/,
- :*, /\t14: from \(irb\):1:in `a6'\n/,
- :*, /\t13: from \(irb\):1:in `a7'\n/,
- :*, /\t12: from \(irb\):1:in `a8'\n/,
- :*, /\t11: from \(irb\):1:in `a9'\n/,
- :*, /\t10: from \(irb\):1:in `a10'\n/,
- :*, /\t 9: from \(irb\):1:in `a11'\n/,
- :*, /\t 8: from \(irb\):1:in `a12'\n/,
- :*, /\t 7: from \(irb\):1:in `a13'\n/,
- :*, /\t 6: from \(irb\):1:in `a14'\n/,
- :*, /\t 5: from \(irb\):1:in `a15'\n/,
- :*, /\t 4: from \(irb\):1:in `a16'\n/,
- :*, /\t 3: from \(irb\):1:in `a17'\n/,
- :*, /\t 2: from \(irb\):1:in `a18'\n/,
- :*, /\t 1: from \(irb\):1:in `a19'\n/,
- :*, /\(irb\):1:in `a20': unhandled exception\n/,
+ :*, /\t16: from \(irb\):1:in (`|')a4'\n/,
+ :*, /\t15: from \(irb\):1:in (`|')a5'\n/,
+ :*, /\t14: from \(irb\):1:in (`|')a6'\n/,
+ :*, /\t13: from \(irb\):1:in (`|')a7'\n/,
+ :*, /\t12: from \(irb\):1:in (`|')a8'\n/,
+ :*, /\t11: from \(irb\):1:in (`|')a9'\n/,
+ :*, /\t10: from \(irb\):1:in (`|')a10'\n/,
+ :*, /\t 9: from \(irb\):1:in (`|')a11'\n/,
+ :*, /\t 8: from \(irb\):1:in (`|')a12'\n/,
+ :*, /\t 7: from \(irb\):1:in (`|')a13'\n/,
+ :*, /\t 6: from \(irb\):1:in (`|')a14'\n/,
+ :*, /\t 5: from \(irb\):1:in (`|')a15'\n/,
+ :*, /\t 4: from \(irb\):1:in (`|')a16'\n/,
+ :*, /\t 3: from \(irb\):1:in (`|')a17'\n/,
+ :*, /\t 2: from \(irb\):1:in (`|')a18'\n/,
+ :*, /\t 1: from \(irb\):1:in (`|')a19'\n/,
+ :*, /\(irb\):1:in (`|')a20': unhandled exception\n/,
]
else
expected = [
- :*, /\(irb\):1:in `a20': unhandled exception\n/,
- :*, /\tfrom \(irb\):1:in `a19'\n/,
- :*, /\tfrom \(irb\):1:in `a18'\n/,
- :*, /\tfrom \(irb\):1:in `a17'\n/,
- :*, /\tfrom \(irb\):1:in `a16'\n/,
- :*, /\tfrom \(irb\):1:in `a15'\n/,
- :*, /\tfrom \(irb\):1:in `a14'\n/,
- :*, /\tfrom \(irb\):1:in `a13'\n/,
- :*, /\tfrom \(irb\):1:in `a12'\n/,
- :*, /\tfrom \(irb\):1:in `a11'\n/,
- :*, /\tfrom \(irb\):1:in `a10'\n/,
- :*, /\tfrom \(irb\):1:in `a9'\n/,
- :*, /\tfrom \(irb\):1:in `a8'\n/,
- :*, /\tfrom \(irb\):1:in `a7'\n/,
- :*, /\tfrom \(irb\):1:in `a6'\n/,
- :*, /\tfrom \(irb\):1:in `a5'\n/,
- :*, /\tfrom \(irb\):1:in `a4'\n/,
+ :*, /\(irb\):1:in (`|')a20': unhandled exception\n/,
+ :*, /\tfrom \(irb\):1:in (`|')a19'\n/,
+ :*, /\tfrom \(irb\):1:in (`|')a18'\n/,
+ :*, /\tfrom \(irb\):1:in (`|')a17'\n/,
+ :*, /\tfrom \(irb\):1:in (`|')a16'\n/,
+ :*, /\tfrom \(irb\):1:in (`|')a15'\n/,
+ :*, /\tfrom \(irb\):1:in (`|')a14'\n/,
+ :*, /\tfrom \(irb\):1:in (`|')a13'\n/,
+ :*, /\tfrom \(irb\):1:in (`|')a12'\n/,
+ :*, /\tfrom \(irb\):1:in (`|')a11'\n/,
+ :*, /\tfrom \(irb\):1:in (`|')a10'\n/,
+ :*, /\tfrom \(irb\):1:in (`|')a9'\n/,
+ :*, /\tfrom \(irb\):1:in (`|')a8'\n/,
+ :*, /\tfrom \(irb\):1:in (`|')a7'\n/,
+ :*, /\tfrom \(irb\):1:in (`|')a6'\n/,
+ :*, /\tfrom \(irb\):1:in (`|')a5'\n/,
+ :*, /\tfrom \(irb\):1:in (`|')a4'\n/,
:*, /\t... \d+ levels...\n/,
]
end
@@ -641,6 +662,14 @@ module TestIRB
assert_equal("irb(!ArgumentError)>", irb.send(:format_prompt, 'irb(%M)>', nil, 1, 1))
end
+ def test_prompt_format
+ main = 'main'
+ irb = IRB::Irb.new(IRB::WorkSpace.new(main), TestInputMethod.new)
+ assert_equal('%% main %m %main %%m >', irb.send(:format_prompt, '%%%% %m %%m %%%m %%%%m %l', '>', 1, 1))
+ assert_equal('42,%i, 42,%3i,042,%03i', irb.send(:format_prompt, '%i,%%i,%3i,%%3i,%03i,%%03i', nil, 42, 1))
+ assert_equal('42,%n, 42,%3n,042,%03n', irb.send(:format_prompt, '%n,%%n,%3n,%%3n,%03n,%%03n', nil, 1, 42))
+ end
+
def test_lineno
input = TestInputMethod.new([
"\n",
@@ -662,6 +691,18 @@ module TestIRB
], out)
end
+ def test_irb_path_setter
+ @context.irb_path = __FILE__
+ assert_equal(__FILE__, @context.irb_path)
+ assert_equal("#{__FILE__}(irb)", @context.instance_variable_get(:@eval_path))
+ @context.irb_path = 'file/does/not/exist'
+ assert_equal('file/does/not/exist', @context.irb_path)
+ assert_equal('file/does/not/exist', @context.instance_variable_get(:@eval_path))
+ @context.irb_path = "#{__FILE__}(irb)"
+ assert_equal("#{__FILE__}(irb)", @context.irb_path)
+ assert_equal("#{__FILE__}(irb)", @context.instance_variable_get(:@eval_path))
+ end
+
def test_build_completor
verbose, $VERBOSE = $VERBOSE, nil
original_completor = IRB.conf[:COMPLETOR]
diff --git a/test/irb/test_debug_cmd.rb b/test/irb/test_debugger_integration.rb
index 0fb45af478..8b1bddea17 100644
--- a/test/irb/test_debug_cmd.rb
+++ b/test/irb/test_debugger_integration.rb
@@ -6,7 +6,7 @@ require "tmpdir"
require_relative "helper"
module TestIRB
- class DebugCommandTest < IntegrationTestCase
+ class DebuggerIntegrationTest < IntegrationTestCase
def setup
super
@@ -67,6 +67,22 @@ module TestIRB
assert_match(/IRB is already running with a debug session/, output)
end
+ def test_debug_command_can_only_be_called_from_binding_irb
+ write_ruby <<~'ruby'
+ require "irb"
+ # trick test framework
+ puts "binding.irb"
+ IRB.start
+ ruby
+
+ output = run_ruby_file do
+ type "debug"
+ type "exit"
+ end
+
+ assert_include(output, "Debugging commands are only available when IRB is started with binding.irb")
+ end
+
def test_next
write_ruby <<~'ruby'
binding.irb
@@ -244,28 +260,46 @@ module TestIRB
def test_exit
write_ruby <<~'RUBY'
binding.irb
- puts "hello"
+ puts "he" + "llo"
RUBY
output = run_ruby_file do
- type "next"
+ type "debug"
type "exit"
end
- assert_match(/irb\(main\):001> next/, output)
+ assert_match(/irb:rdbg\(main\):002>/, output)
+ assert_match(/hello/, output)
+ end
+
+ def test_force_exit
+ write_ruby <<~'RUBY'
+ binding.irb
+ puts "he" + "llo"
+ RUBY
+
+ output = run_ruby_file do
+ type "debug"
+ type "exit!"
+ end
+
+ assert_match(/irb:rdbg\(main\):002>/, output)
+ assert_not_match(/hello/, output)
end
def test_quit
write_ruby <<~'RUBY'
binding.irb
+ puts "he" + "llo"
RUBY
output = run_ruby_file do
- type "next"
+ type "debug"
type "quit!"
end
- assert_match(/irb\(main\):001> next/, output)
+ assert_match(/irb:rdbg\(main\):002>/, output)
+ assert_not_match(/hello/, output)
end
def test_prompt_line_number_continues
@@ -345,19 +379,19 @@ module TestIRB
assert_include(output, "### Frame control")
end
- def test_show_cmds_display_different_content_when_debugger_is_enabled
+ def test_help_display_different_content_when_debugger_is_enabled
write_ruby <<~'ruby'
binding.irb
ruby
output = run_ruby_file do
type "debug"
- type "show_cmds"
+ type "help"
type "continue"
end
# IRB's commands should still be listed
- assert_match(/show_cmds\s+List all available commands and their description\./, output)
+ assert_match(/help\s+List all available commands/, output)
# debug gem's commands should be appended at the end
assert_match(/Debugging \(from debug\.gem\)\s+### Control flow/, output)
end
@@ -434,5 +468,29 @@ module TestIRB
assert_match(/irb\(main\):001> next/, output)
assert_include(output, "Multi-IRB commands are not available when the debugger is enabled.")
end
+
+ def test_irb_passes_empty_input_to_debugger_to_repeat_the_last_command
+ write_ruby <<~'ruby'
+ binding.irb
+ puts "foo"
+ puts "bar"
+ puts "baz"
+ ruby
+
+ output = run_ruby_file do
+ type "next"
+ type ""
+ # Test that empty input doesn't repeat expressions
+ type "123"
+ type ""
+ type "next"
+ type ""
+ type ""
+ end
+
+ assert_include(output, "=> 2\| puts \"foo\"")
+ assert_include(output, "=> 3\| puts \"bar\"")
+ assert_include(output, "=> 4\| puts \"baz\"")
+ end
end
end
diff --git a/test/irb/test_eval_history.rb b/test/irb/test_eval_history.rb
index 0f9ec4811c..54913ceff5 100644
--- a/test/irb/test_eval_history.rb
+++ b/test/irb/test_eval_history.rb
@@ -30,14 +30,14 @@ module TestIRB
end
end
- def test_eval_history_is_diabled_by_default
+ def test_eval_history_is_disabled_by_default
out, err = execute_lines(
"a = 1",
"__"
)
assert_empty(err)
- assert_match(/undefined local variable or method `__'/, out)
+ assert_match(/undefined local variable or method (`|')__'/, out)
end
def test_eval_history_can_be_retrieved_with_double_underscore
diff --git a/test/irb/test_helper_method.rb b/test/irb/test_helper_method.rb
new file mode 100644
index 0000000000..291278c16a
--- /dev/null
+++ b/test/irb/test_helper_method.rb
@@ -0,0 +1,134 @@
+# frozen_string_literal: true
+require "irb"
+
+require_relative "helper"
+
+module TestIRB
+ class HelperMethodTestCase < TestCase
+ def setup
+ $VERBOSE = nil
+ @verbosity = $VERBOSE
+ save_encodings
+ IRB.instance_variable_get(:@CONF).clear
+ end
+
+ def teardown
+ $VERBOSE = @verbosity
+ restore_encodings
+ end
+
+ def execute_lines(*lines, conf: {}, main: self, irb_path: nil)
+ IRB.init_config(nil)
+ IRB.conf[:VERBOSE] = false
+ IRB.conf[:PROMPT_MODE] = :SIMPLE
+ IRB.conf.merge!(conf)
+ input = TestInputMethod.new(lines)
+ irb = IRB::Irb.new(IRB::WorkSpace.new(main), input)
+ irb.context.return_format = "=> %s\n"
+ irb.context.irb_path = irb_path if irb_path
+ IRB.conf[:MAIN_CONTEXT] = irb.context
+ IRB.conf[:USE_PAGER] = false
+ capture_output do
+ irb.eval_input
+ end
+ end
+ end
+
+ module TestHelperMethod
+ class ConfTest < HelperMethodTestCase
+ def test_conf_returns_the_context_object
+ out, err = execute_lines("conf.ap_name")
+
+ assert_empty err
+ assert_include out, "=> \"irb\""
+ end
+ end
+ end
+
+ class HelperMethodIntegrationTest < IntegrationTestCase
+ def test_arguments_propogation
+ write_ruby <<~RUBY
+ require "irb/helper_method"
+
+ class MyHelper < IRB::HelperMethod::Base
+ description "This is a test helper"
+
+ def execute(
+ required_arg, optional_arg = nil, *splat_arg, required_keyword_arg:,
+ optional_keyword_arg: nil, **double_splat_arg, &block_arg
+ )
+ puts [required_arg, optional_arg, splat_arg, required_keyword_arg, optional_keyword_arg, double_splat_arg, block_arg.call].to_s
+ end
+ end
+
+ IRB::HelperMethod.register(:my_helper, MyHelper)
+
+ binding.irb
+ RUBY
+
+ output = run_ruby_file do
+ type <<~INPUT
+ my_helper(
+ "required", "optional", "splat", required_keyword_arg: "required",
+ optional_keyword_arg: "optional", a: 1, b: 2
+ ) { "block" }
+ INPUT
+ type "exit"
+ end
+
+ assert_include(output, '["required", "optional", ["splat"], "required", "optional", {:a=>1, :b=>2}, "block"]')
+ end
+
+ def test_helper_method_injection_can_happen_after_irb_require
+ write_ruby <<~RUBY
+ require "irb"
+
+ class MyHelper < IRB::HelperMethod::Base
+ description "This is a test helper"
+
+ def execute
+ puts "Hello from MyHelper"
+ end
+ end
+
+ IRB::HelperMethod.register(:my_helper, MyHelper)
+
+ binding.irb
+ RUBY
+
+ output = run_ruby_file do
+ type "my_helper"
+ type "exit"
+ end
+
+ assert_include(output, 'Hello from MyHelper')
+ end
+
+ def test_helper_method_instances_are_memoized
+ write_ruby <<~RUBY
+ require "irb/helper_method"
+
+ class MyHelper < IRB::HelperMethod::Base
+ description "This is a test helper"
+
+ def execute(val)
+ @val ||= val
+ end
+ end
+
+ IRB::HelperMethod.register(:my_helper, MyHelper)
+
+ binding.irb
+ RUBY
+
+ output = run_ruby_file do
+ type "my_helper(100)"
+ type "my_helper(200)"
+ type "exit"
+ end
+
+ assert_include(output, '=> 100')
+ assert_not_include(output, '=> 200')
+ end
+ end
+end
diff --git a/test/irb/test_history.rb b/test/irb/test_history.rb
index f7ba2b9d33..63be35fdaa 100644
--- a/test/irb/test_history.rb
+++ b/test/irb/test_history.rb
@@ -10,11 +10,24 @@ return if RUBY_PLATFORM.match?(/solaris|mswin|mingw/i)
module TestIRB
class HistoryTest < TestCase
def setup
- IRB.conf[:RC_NAME_GENERATOR] = nil
+ @original_verbose, $VERBOSE = $VERBOSE, nil
+ @tmpdir = Dir.mktmpdir("test_irb_history_")
+ @backup_home = ENV["HOME"]
+ @backup_xdg_config_home = ENV.delete("XDG_CONFIG_HOME")
+ @backup_irbrc = ENV.delete("IRBRC")
+ @backup_default_external = Encoding.default_external
+ ENV["HOME"] = @tmpdir
+ IRB.instance_variable_set(:@existing_rc_name_generators, nil)
end
def teardown
- IRB.conf[:RC_NAME_GENERATOR] = nil
+ IRB.instance_variable_set(:@existing_rc_name_generators, nil)
+ ENV["HOME"] = @backup_home
+ ENV["XDG_CONFIG_HOME"] = @backup_xdg_config_home
+ ENV["IRBRC"] = @backup_irbrc
+ Encoding.default_external = @backup_default_external
+ $VERBOSE = @original_verbose
+ FileUtils.rm_rf(@tmpdir)
end
class TestInputMethodWithRelineHistory < TestInputMethod
@@ -123,35 +136,23 @@ module TestIRB
end
def test_history_concurrent_use_not_present
- backup_home = ENV["HOME"]
- backup_xdg_config_home = ENV.delete("XDG_CONFIG_HOME")
- backup_irbrc = ENV.delete("IRBRC")
IRB.conf[:LC_MESSAGES] = IRB::Locale.new
IRB.conf[:SAVE_HISTORY] = 1
- Dir.mktmpdir("test_irb_history_") do |tmpdir|
- ENV["HOME"] = tmpdir
- io = TestInputMethodWithRelineHistory.new
- io.class::HISTORY.clear
- io.load_history
- io.class::HISTORY << 'line1'
- io.class::HISTORY << 'line2'
-
- history_file = IRB.rc_file("_history")
- assert_not_send [File, :file?, history_file]
- File.write(history_file, "line0\n")
- io.save_history
- assert_equal(%w"line0 line1 line2", File.read(history_file).split)
- end
- ensure
- ENV["HOME"] = backup_home
- ENV["XDG_CONFIG_HOME"] = backup_xdg_config_home
- ENV["IRBRC"] = backup_irbrc
+ io = TestInputMethodWithRelineHistory.new
+ io.class::HISTORY.clear
+ io.load_history
+ io.class::HISTORY << 'line1'
+ io.class::HISTORY << 'line2'
+
+ history_file = IRB.rc_file("_history")
+ assert_not_send [File, :file?, history_file]
+ File.write(history_file, "line0\n")
+ io.save_history
+ assert_equal(%w"line0 line1 line2", File.read(history_file).split)
end
def test_history_different_encodings
- backup_default_external = Encoding.default_external
IRB.conf[:SAVE_HISTORY] = 2
- verbose_bak, $VERBOSE = $VERBOSE, nil
Encoding.default_external = Encoding::US_ASCII
locale = IRB::Locale.new("C")
assert_history(<<~EXPECTED_HISTORY.encode(Encoding::US_ASCII), <<~INITIAL_HISTORY.encode(Encoding::UTF_8), <<~INPUT, locale: locale)
@@ -162,9 +163,34 @@ module TestIRB
INITIAL_HISTORY
exit
INPUT
+ end
+
+ def test_history_does_not_raise_when_history_file_directory_does_not_exist
+ backup_history_file = IRB.conf[:HISTORY_FILE]
+ IRB.conf[:SAVE_HISTORY] = 1
+ IRB.conf[:HISTORY_FILE] = "fake/fake/fake/history_file"
+ io = TestInputMethodWithRelineHistory.new
+
+ assert_warn(/history file does not exist/) do
+ io.save_history
+ end
+
+ # assert_warn reverts $VERBOSE to EnvUtil.original_verbose, which is true in some cases
+ # We want to keep $VERBOSE as nil until teardown is called
+ # TODO: check if this is an assert_warn issue
+ $VERBOSE = nil
ensure
- Encoding.default_external = backup_default_external
- $VERBOSE = verbose_bak
+ IRB.conf[:HISTORY_FILE] = backup_history_file
+ end
+
+ def test_no_home_no_history_file_does_not_raise_history_save
+ ENV['HOME'] = nil
+ io = TestInputMethodWithRelineHistory.new
+ assert_nil(IRB.rc_file('_history'))
+ assert_nothing_raised do
+ io.load_history
+ io.save_history
+ end
end
private
@@ -199,34 +225,30 @@ module TestIRB
end
def assert_history(expected_history, initial_irb_history, input, input_method = TestInputMethodWithRelineHistory, locale: IRB::Locale.new)
- backup_verbose, $VERBOSE = $VERBOSE, nil
- backup_home = ENV["HOME"]
- backup_xdg_config_home = ENV.delete("XDG_CONFIG_HOME")
IRB.conf[:LC_MESSAGES] = locale
actual_history = nil
- Dir.mktmpdir("test_irb_history_") do |tmpdir|
- ENV["HOME"] = tmpdir
- File.open(IRB.rc_file("_history"), "w") do |f|
- f.write(initial_irb_history)
- end
+ history_file = IRB.rc_file("_history")
+ ENV["HOME"] = @tmpdir
+ File.open(history_file, "w") do |f|
+ f.write(initial_irb_history)
+ end
- io = input_method.new
+ io = input_method.new
+ io.class::HISTORY.clear
+ io.load_history
+ if block_given?
+ previous_history = []
+ io.class::HISTORY.each { |line| previous_history << line }
+ yield history_file
io.class::HISTORY.clear
- io.load_history
- if block_given?
- previous_history = []
- io.class::HISTORY.each { |line| previous_history << line }
- yield IRB.rc_file("_history")
- io.class::HISTORY.clear
- previous_history.each { |line| io.class::HISTORY << line }
- end
- input.split.each { |line| io.class::HISTORY << line }
- io.save_history
+ previous_history.each { |line| io.class::HISTORY << line }
+ end
+ input.split.each { |line| io.class::HISTORY << line }
+ io.save_history
- io.load_history
- File.open(IRB.rc_file("_history"), "r") do |f|
- actual_history = f.read
- end
+ io.load_history
+ File.open(history_file, "r") do |f|
+ actual_history = f.read
end
assert_equal(expected_history, actual_history, <<~MESSAGE)
expected:
@@ -234,10 +256,6 @@ module TestIRB
but actual:
#{actual_history}
MESSAGE
- ensure
- $VERBOSE = backup_verbose
- ENV["HOME"] = backup_home
- ENV["XDG_CONFIG_HOME"] = backup_xdg_config_home
end
def with_temp_stdio
@@ -251,10 +269,6 @@ module TestIRB
class IRBHistoryIntegrationTest < IntegrationTestCase
def test_history_saving_with_debug
- if ruby_core?
- omit "This test works only under ruby/irb"
- end
-
write_history ""
write_ruby <<~'RUBY'
@@ -293,6 +307,53 @@ module TestIRB
HISTORY
end
+ def test_history_saving_with_debug_without_prior_history
+ tmpdir = Dir.mktmpdir("test_irb_history_")
+ # Intentionally not creating the file so we test the reset counter logic
+ history_file = File.join(tmpdir, "irb_history")
+
+ write_rc <<~RUBY
+ IRB.conf[:HISTORY_FILE] = "#{history_file}"
+ RUBY
+
+ write_ruby <<~'RUBY'
+ def foo
+ end
+
+ binding.irb
+
+ foo
+ RUBY
+
+ output = run_ruby_file do
+ type "'irb session'"
+ type "next"
+ type "'irb:debug session'"
+ type "step"
+ type "irb_info"
+ type "puts Reline::HISTORY.to_a.to_s"
+ type "q!"
+ end
+
+ assert_include(output, "InputMethod: RelineInputMethod")
+ # check that in-memory history is preserved across sessions
+ assert_include output, %q(
+ ["'irb session'", "next", "'irb:debug session'", "step", "irb_info", "puts Reline::HISTORY.to_a.to_s"]
+ ).strip
+
+ assert_equal <<~HISTORY, File.read(history_file)
+ 'irb session'
+ next
+ 'irb:debug session'
+ step
+ irb_info
+ puts Reline::HISTORY.to_a.to_s
+ q!
+ HISTORY
+ ensure
+ FileUtils.rm_rf(tmpdir)
+ end
+
def test_history_saving_with_nested_sessions
write_history ""
@@ -323,6 +384,62 @@ module TestIRB
HISTORY
end
+ def test_nested_history_saving_from_inner_session_with_exit!
+ write_history ""
+
+ write_ruby <<~'RUBY'
+ def foo
+ binding.irb
+ end
+
+ binding.irb
+ RUBY
+
+ run_ruby_file do
+ type "'outer session'"
+ type "foo"
+ type "'inner session'"
+ type "exit!"
+ end
+
+ assert_equal <<~HISTORY, @history_file.open.read
+ 'outer session'
+ foo
+ 'inner session'
+ exit!
+ HISTORY
+ end
+
+ def test_nested_history_saving_from_outer_session_with_exit!
+ write_history ""
+
+ write_ruby <<~'RUBY'
+ def foo
+ binding.irb
+ end
+
+ binding.irb
+ RUBY
+
+ run_ruby_file do
+ type "'outer session'"
+ type "foo"
+ type "'inner session'"
+ type "exit"
+ type "'outer session again'"
+ type "exit!"
+ end
+
+ assert_equal <<~HISTORY, @history_file.open.read
+ 'outer session'
+ foo
+ 'inner session'
+ exit
+ 'outer session again'
+ exit!
+ HISTORY
+ end
+
def test_history_saving_with_nested_sessions_and_prior_history
write_history <<~HISTORY
old_history_1
diff --git a/test/irb/test_init.rb b/test/irb/test_init.rb
index 7d11b5b507..f11d7398c8 100644
--- a/test/irb/test_init.rb
+++ b/test/irb/test_init.rb
@@ -11,13 +11,18 @@ module TestIRB
@backup_env = %w[HOME XDG_CONFIG_HOME IRBRC].each_with_object({}) do |env, hash|
hash[env] = ENV.delete(env)
end
- ENV["HOME"] = @tmpdir = Dir.mktmpdir("test_irb_init_#{$$}")
+ ENV["HOME"] = @tmpdir = File.realpath(Dir.mktmpdir("test_irb_init_#{$$}"))
+ end
+
+ def reset_rc_name_generators
+ IRB.instance_variable_set(:@existing_rc_name_generators, nil)
end
def teardown
ENV.update(@backup_env)
FileUtils.rm_rf(@tmpdir)
IRB.conf.delete(:SCRIPT)
+ reset_rc_name_generators
end
def test_setup_with_argv_preserves_global_argv
@@ -34,42 +39,80 @@ module TestIRB
assert_equal orig, $0
end
- def test_rc_file
+ def test_rc_files
tmpdir = @tmpdir
Dir.chdir(tmpdir) do
- ENV["XDG_CONFIG_HOME"] = "#{tmpdir}/xdg"
- IRB.conf[:RC_NAME_GENERATOR] = nil
- assert_equal(tmpdir+"/.irb#{IRB::IRBRC_EXT}", IRB.rc_file)
- assert_equal(tmpdir+"/.irb_history", IRB.rc_file("_history"))
- assert_file.not_exist?(tmpdir+"/xdg")
- IRB.conf[:RC_NAME_GENERATOR] = nil
- FileUtils.touch(tmpdir+"/.irb#{IRB::IRBRC_EXT}")
- assert_equal(tmpdir+"/.irb#{IRB::IRBRC_EXT}", IRB.rc_file)
- assert_equal(tmpdir+"/.irb_history", IRB.rc_file("_history"))
- assert_file.not_exist?(tmpdir+"/xdg")
+ home = ENV['HOME'] = "#{tmpdir}/home"
+ xdg_config_home = ENV['XDG_CONFIG_HOME'] = "#{tmpdir}/xdg"
+ reset_rc_name_generators
+ assert_empty(IRB.irbrc_files)
+ assert_equal("#{home}/.irb_history", IRB.rc_file('_history'))
+ FileUtils.mkdir_p(home)
+ FileUtils.mkdir_p("#{xdg_config_home}/irb")
+ FileUtils.mkdir_p("#{home}/.config/irb")
+ reset_rc_name_generators
+ assert_empty(IRB.irbrc_files)
+ assert_equal("#{xdg_config_home}/irb/irb_history", IRB.rc_file('_history'))
+ home_irbrc = "#{home}/.irbrc"
+ config_irbrc = "#{home}/.config/irb/irbrc"
+ xdg_config_irbrc = "#{xdg_config_home}/irb/irbrc"
+ [home_irbrc, config_irbrc, xdg_config_irbrc].each do |file|
+ FileUtils.touch(file)
+ end
+ current_dir_irbrcs = %w[.irbrc irbrc _irbrc $irbrc].map { |file| "#{tmpdir}/#{file}" }
+ current_dir_irbrcs.each { |file| FileUtils.touch(file) }
+ reset_rc_name_generators
+ assert_equal([xdg_config_irbrc, home_irbrc, *current_dir_irbrcs], IRB.irbrc_files)
+ assert_equal(xdg_config_irbrc.sub(/rc$/, '_history'), IRB.rc_file('_history'))
+ ENV['XDG_CONFIG_HOME'] = nil
+ reset_rc_name_generators
+ assert_equal([home_irbrc, config_irbrc, *current_dir_irbrcs], IRB.irbrc_files)
+ assert_equal(home_irbrc.sub(/rc$/, '_history'), IRB.rc_file('_history'))
+ ENV['XDG_CONFIG_HOME'] = ''
+ reset_rc_name_generators
+ assert_equal([home_irbrc, config_irbrc] + current_dir_irbrcs, IRB.irbrc_files)
+ assert_equal(home_irbrc.sub(/rc$/, '_history'), IRB.rc_file('_history'))
+ ENV['XDG_CONFIG_HOME'] = xdg_config_home
+ ENV['IRBRC'] = "#{tmpdir}/.irbrc"
+ reset_rc_name_generators
+ assert_equal([ENV['IRBRC'], xdg_config_irbrc, home_irbrc] + (current_dir_irbrcs - [ENV['IRBRC']]), IRB.irbrc_files)
+ assert_equal(ENV['IRBRC'] + '_history', IRB.rc_file('_history'))
+ ENV['IRBRC'] = ENV['HOME'] = ENV['XDG_CONFIG_HOME'] = nil
+ reset_rc_name_generators
+ assert_equal(current_dir_irbrcs, IRB.irbrc_files)
+ assert_nil(IRB.rc_file('_history'))
end
end
- def test_rc_file_in_subdir
+ def test_duplicated_rc_files
tmpdir = @tmpdir
Dir.chdir(tmpdir) do
- FileUtils.mkdir_p("#{tmpdir}/mydir")
- Dir.chdir("#{tmpdir}/mydir") do
- IRB.conf[:RC_NAME_GENERATOR] = nil
- assert_equal(tmpdir+"/.irb#{IRB::IRBRC_EXT}", IRB.rc_file)
- assert_equal(tmpdir+"/.irb_history", IRB.rc_file("_history"))
- IRB.conf[:RC_NAME_GENERATOR] = nil
- FileUtils.touch(tmpdir+"/.irb#{IRB::IRBRC_EXT}")
- assert_equal(tmpdir+"/.irb#{IRB::IRBRC_EXT}", IRB.rc_file)
- assert_equal(tmpdir+"/.irb_history", IRB.rc_file("_history"))
+ ENV['XDG_CONFIG_HOME'] = "#{ENV['HOME']}/.config"
+ FileUtils.mkdir_p("#{ENV['XDG_CONFIG_HOME']}/irb")
+ env_irbrc = ENV['IRBRC'] = "#{tmpdir}/_irbrc"
+ xdg_config_irbrc = "#{ENV['XDG_CONFIG_HOME']}/irb/irbrc"
+ home_irbrc = "#{ENV['HOME']}/.irbrc"
+ current_dir_irbrc = "#{tmpdir}/irbrc"
+ [env_irbrc, xdg_config_irbrc, home_irbrc, current_dir_irbrc].each do |file|
+ FileUtils.touch(file)
end
+ reset_rc_name_generators
+ assert_equal([env_irbrc, xdg_config_irbrc, home_irbrc, current_dir_irbrc], IRB.irbrc_files)
end
end
- def test_recovery_sigint
+ def test_sigint_restore_default
pend "This test gets stuck on Solaris for unknown reason; contribution is welcome" if RUBY_PLATFORM =~ /solaris/
bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : []
- status = assert_in_out_err(bundle_exec + %w[-W0 -rirb -e binding.irb;loop{Process.kill("SIGINT",$$)} -- -f --], "exit\n", //, //)
+ # IRB should restore SIGINT handler
+ status = assert_in_out_err(bundle_exec + %w[-W0 -rirb -e Signal.trap("SIGINT","DEFAULT");binding.irb;loop{Process.kill("SIGINT",$$)} -- -f --], "exit\n", //, //)
+ Process.kill("SIGKILL", status.pid) if !status.exited? && !status.stopped? && !status.signaled?
+ end
+
+ def test_sigint_restore_block
+ bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : []
+ # IRB should restore SIGINT handler
+ status = assert_in_out_err(bundle_exec + %w[-W0 -rirb -e x=false;Signal.trap("SIGINT"){x=true};binding.irb;loop{Process.kill("SIGINT",$$);if(x);break;end} -- -f --], "exit\n", //, //)
Process.kill("SIGKILL", status.pid) if !status.exited? && !status.stopped? && !status.signaled?
end
@@ -208,6 +251,12 @@ module TestIRB
assert_equal(['-f'], argv)
end
+ def test_option_tracer
+ argv = %w[--tracer]
+ IRB.setup(eval("__FILE__"), argv: argv)
+ assert_equal(true, IRB.conf[:USE_TRACER])
+ end
+
private
def with_argv(argv)
diff --git a/test/irb/test_input_method.rb b/test/irb/test_input_method.rb
index e6a1b06e82..ce317b4b32 100644
--- a/test/irb/test_input_method.rb
+++ b/test/irb/test_input_method.rb
@@ -15,7 +15,7 @@ module TestIRB
def teardown
IRB.conf.replace(@conf_backup)
restore_encodings
- # Reset Reline configuration overrided by RelineInputMethod.
+ # Reset Reline configuration overridden by RelineInputMethod.
Reline.instance_variable_set(:@core, nil)
end
end
@@ -88,17 +88,18 @@ module TestIRB
@driver = RDoc::RI::Driver.new(use_stdout: true)
end
- def display_document(target, bind)
+ def display_document(target, bind, driver = nil)
input_method = IRB::RelineInputMethod.new(IRB::RegexpCompletor.new)
- input_method.instance_variable_set(:@completion_params, [target, '', '', bind])
- input_method.display_document(target, driver: @driver)
+ input_method.instance_variable_set(:@rdoc_ri_driver, driver) if driver
+ input_method.instance_variable_set(:@completion_params, ['', target, '', bind])
+ input_method.display_document(target)
end
def test_perfectly_matched_namespace_triggers_document_display
omit unless has_rdoc_content?
out, err = capture_output do
- display_document("String", binding)
+ display_document("String", binding, @driver)
end
assert_empty(err)
@@ -109,7 +110,7 @@ module TestIRB
def test_perfectly_matched_multiple_namespaces_triggers_document_display
result = nil
out, err = capture_output do
- result = display_document("{}.nil?", binding)
+ result = display_document("{}.nil?", binding, @driver)
end
assert_empty(err)
@@ -131,7 +132,7 @@ module TestIRB
def test_not_matched_namespace_triggers_nothing
result = nil
out, err = capture_output do
- result = display_document("Stri", binding)
+ result = display_document("Stri", binding, @driver)
end
assert_empty(err)
@@ -156,7 +157,7 @@ module TestIRB
def test_perfect_matching_handles_nil_namespace
out, err = capture_output do
# symbol literal has `nil` doc namespace so it's a good test subject
- assert_nil(display_document(":aiueo", binding))
+ assert_nil(display_document(":aiueo", binding, @driver))
end
assert_empty(err)
@@ -170,4 +171,3 @@ module TestIRB
end
end
end
-
diff --git a/test/irb/test_irb.rb b/test/irb/test_irb.rb
index fb8b5c2bfa..28be744088 100644
--- a/test/irb/test_irb.rb
+++ b/test/irb/test_irb.rb
@@ -42,6 +42,56 @@ module TestIRB
assert_include output, "From: #{@ruby_file.path}:1"
end
+ def test_underscore_stores_last_result
+ write_ruby <<~'RUBY'
+ binding.irb
+ RUBY
+
+ output = run_ruby_file do
+ type "1 + 1"
+ type "_ + 10"
+ type "exit!"
+ end
+
+ assert_include output, "=> 12"
+ end
+
+ def test_evaluate_with_encoding_error_without_lineno
+ if RUBY_ENGINE == 'truffleruby'
+ omit "Remove me after https://github.com/ruby/prism/issues/2129 is addressed and adopted in TruffleRuby"
+ end
+
+ if RUBY_VERSION >= "3.4."
+ omit "Now raises SyntaxError"
+ end
+
+ write_ruby <<~'RUBY'
+ binding.irb
+ RUBY
+
+ output = run_ruby_file do
+ type %q[:"\xAE"]
+ type "exit!"
+ end
+
+ assert_include output, 'invalid symbol in encoding UTF-8 :"\xAE"'
+ # EncodingError would be wrapped with ANSI escape sequences, so we assert it separately
+ assert_include output, "EncodingError"
+ end
+
+ def test_evaluate_still_emits_warning
+ write_ruby <<~'RUBY'
+ binding.irb
+ RUBY
+
+ output = run_ruby_file do
+ type %q[def foo; END {}; end]
+ type "exit!"
+ end
+
+ assert_include output, '(irb):1: warning: END in method; use at_exit'
+ end
+
def test_symbol_aliases_dont_affect_ruby_syntax
write_ruby <<~'RUBY'
$foo = "It's a foo"
@@ -58,6 +108,41 @@ module TestIRB
assert_include output, "=> \"It's a foo\""
assert_include output, "=> \"It's a bar\""
end
+
+ def test_empty_input_echoing_behaviour
+ write_ruby <<~'RUBY'
+ binding.irb
+ RUBY
+
+ output = run_ruby_file do
+ type ""
+ type " "
+ type "exit"
+ end
+
+ assert_not_match(/irb\(main\):001> (\r*\n)?=> nil/, output)
+ assert_match(/irb\(main\):002> (\r*\n)?=> nil/, output)
+ end
+ end
+
+ class NestedBindingIrbTest < IntegrationTestCase
+ def test_current_context_restore
+ write_ruby <<~'RUBY'
+ binding.irb
+ RUBY
+
+ output = run_ruby_file do
+ type '$ctx = IRB.CurrentContext'
+ type 'binding.irb'
+ type 'p context_changed: IRB.CurrentContext != $ctx'
+ type 'exit'
+ type 'p context_restored: IRB.CurrentContext == $ctx'
+ type 'exit'
+ end
+
+ assert_include output, '{:context_changed=>true}'
+ assert_include output, '{:context_restored=>true}'
+ end
end
class IrbIOConfigurationTest < TestCase
@@ -738,4 +823,95 @@ module TestIRB
IRB::Irb.new(workspace, TestInputMethod.new)
end
end
+
+ class BacktraceFilteringTest < TestIRB::IntegrationTestCase
+ def test_backtrace_filtering
+ write_ruby <<~'RUBY'
+ def foo
+ raise "error"
+ end
+
+ def bar
+ foo
+ end
+
+ binding.irb
+ RUBY
+
+ output = run_ruby_file do
+ type "bar"
+ type "exit"
+ end
+
+ assert_match(/irbtest-.*\.rb:2:in (`|'Object#)foo': error \(RuntimeError\)/, output)
+ frame_traces = output.split("\n").select { |line| line.strip.match?(/from /) }.map(&:strip)
+
+ expected_traces = if RUBY_VERSION >= "3.3.0"
+ [
+ /from .*\/irbtest-.*.rb:6:in (`|'Object#)bar'/,
+ /from .*\/irbtest-.*.rb\(irb\):1:in [`']<main>'/,
+ /from <internal:kernel>:\d+:in (`|'Kernel#)loop'/,
+ /from <internal:prelude>:\d+:in (`|'Binding#)irb'/,
+ /from .*\/irbtest-.*.rb:9:in [`']<main>'/
+ ]
+ else
+ [
+ /from .*\/irbtest-.*.rb:6:in (`|'Object#)bar'/,
+ /from .*\/irbtest-.*.rb\(irb\):1:in [`']<main>'/,
+ /from <internal:prelude>:\d+:in (`|'Binding#)irb'/,
+ /from .*\/irbtest-.*.rb:9:in [`']<main>'/
+ ]
+ end
+
+ expected_traces.reverse! if RUBY_VERSION < "3.0.0"
+
+ expected_traces.each_with_index do |expected_trace, index|
+ assert_match(expected_trace, frame_traces[index])
+ end
+ end
+
+ def test_backtrace_filtering_with_backtrace_filter
+ write_rc <<~'RUBY'
+ class TestBacktraceFilter
+ def self.call(backtrace)
+ backtrace.reject { |line| line.include?("internal") }
+ end
+ end
+
+ IRB.conf[:BACKTRACE_FILTER] = TestBacktraceFilter
+ RUBY
+
+ write_ruby <<~'RUBY'
+ def foo
+ raise "error"
+ end
+
+ def bar
+ foo
+ end
+
+ binding.irb
+ RUBY
+
+ output = run_ruby_file do
+ type "bar"
+ type "exit"
+ end
+
+ assert_match(/irbtest-.*\.rb:2:in (`|'Object#)foo': error \(RuntimeError\)/, output)
+ frame_traces = output.split("\n").select { |line| line.strip.match?(/from /) }.map(&:strip)
+
+ expected_traces = [
+ /from .*\/irbtest-.*.rb:6:in (`|'Object#)bar'/,
+ /from .*\/irbtest-.*.rb\(irb\):1:in [`']<main>'/,
+ /from .*\/irbtest-.*.rb:9:in [`']<main>'/
+ ]
+
+ expected_traces.reverse! if RUBY_VERSION < "3.0.0"
+
+ expected_traces.each_with_index do |expected_trace, index|
+ assert_match(expected_trace, frame_traces[index])
+ end
+ end
+ end
end
diff --git a/test/irb/test_nesting_parser.rb b/test/irb/test_nesting_parser.rb
index ea3a23aaf5..2482d40081 100644
--- a/test/irb/test_nesting_parser.rb
+++ b/test/irb/test_nesting_parser.rb
@@ -280,6 +280,44 @@ module TestIRB
end
end
+ def test_undef_alias
+ codes = [
+ 'undef foo',
+ 'alias foo bar',
+ 'undef !',
+ 'alias + -',
+ 'alias $a $b',
+ 'undef do',
+ 'alias do do',
+ 'undef :do',
+ 'alias :do :do',
+ 'undef :"#{alias do do}"',
+ 'alias :"#{undef do}" do',
+ 'alias do :"#{undef do}"'
+ ]
+ code_with_comment = <<~EOS
+ undef #
+ #
+ do #
+ alias #
+ #
+ do #
+ #
+ do #
+ EOS
+ code_with_heredoc = <<~EOS
+ <<~A; alias
+ A
+ :"#{<<~A}"
+ A
+ do
+ EOS
+ [*codes, code_with_comment, code_with_heredoc].each do |code|
+ opens = IRB::NestingParser.open_tokens(IRB::RubyLex.ripper_lex_without_warning('(' + code + "\nif"))
+ assert_equal(%w[( if], opens.map(&:tok))
+ end
+ end
+
def test_case_in
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.7.0')
pend 'This test requires ruby version that supports case-in syntax'
diff --git a/test/irb/test_raise_exception.rb b/test/irb/test_raise_exception.rb
index c373dd7335..44a5ae87e1 100644
--- a/test/irb/test_raise_exception.rb
+++ b/test/irb/test_raise_exception.rb
@@ -61,7 +61,7 @@ IRB
# TruffleRuby warns when the locale does not exist
env['TRUFFLERUBYOPT'] = "#{ENV['TRUFFLERUBYOPT']} --log.level=SEVERE" if RUBY_ENGINE == 'truffleruby'
args = [env] + bundle_exec + %W[-rirb -C #{tmpdir} -W0 -e IRB.start(__FILE__) -- -f --]
- error = /`raise_euc_with_invalid_byte_sequence': あ\\xFF \(RuntimeError\)/
+ error = /raise_euc_with_invalid_byte_sequence': あ\\xFF \(RuntimeError\)/
assert_in_out_err(args, <<~IRB, error, [], encoding: "UTF-8")
require_relative 'euc'
raise_euc_with_invalid_byte_sequence
diff --git a/test/irb/test_ruby_lex.rb b/test/irb/test_ruby_lex.rb
index 5cfd81dbe8..4e406a8ce0 100644
--- a/test/irb/test_ruby_lex.rb
+++ b/test/irb/test_ruby_lex.rb
@@ -221,6 +221,10 @@ module TestIRB
end
def assert_code_block_open(lines, expected, local_variables: [])
+ if RUBY_ENGINE == 'truffleruby'
+ omit "Remove me after https://github.com/ruby/prism/issues/2129 is addressed and adopted in TruffleRuby"
+ end
+
_indent_level, _continue, code_block_open = check_state(lines, local_variables: local_variables)
error_message = "Wrong result of code_block_open for:\n #{lines.join("\n")}"
assert_equal(expected, code_block_open, error_message)
diff --git a/test/irb/test_tracer.rb b/test/irb/test_tracer.rb
new file mode 100644
index 0000000000..540f8be131
--- /dev/null
+++ b/test/irb/test_tracer.rb
@@ -0,0 +1,90 @@
+# frozen_string_literal: false
+require 'tempfile'
+require 'irb'
+
+require_relative "helper"
+
+module TestIRB
+ class ContextWithTracerIntegrationTest < IntegrationTestCase
+ def setup
+ super
+
+ omit "Tracer gem is not available when running on TruffleRuby" if RUBY_ENGINE == "truffleruby"
+
+ @envs.merge!("NO_COLOR" => "true")
+ end
+
+ def example_ruby_file
+ <<~'RUBY'
+ class Foo
+ def self.foo
+ 100
+ end
+ end
+
+ def bar(obj)
+ obj.foo
+ end
+
+ binding.irb
+ RUBY
+ end
+
+ def test_use_tracer_enabled_when_gem_is_unavailable
+ write_rc <<~RUBY
+ # Simulate the absence of the tracer gem
+ ::Kernel.send(:alias_method, :irb_original_require, :require)
+
+ ::Kernel.define_method(:require) do |name|
+ raise LoadError, "cannot load such file -- tracer (test)" if name.match?("tracer")
+ ::Kernel.send(:irb_original_require, name)
+ end
+
+ IRB.conf[:USE_TRACER] = true
+ RUBY
+
+ write_ruby example_ruby_file
+
+ output = run_ruby_file do
+ type "bar(Foo)"
+ type "exit"
+ end
+
+ assert_include(output, "Tracer extension of IRB is enabled but tracer gem wasn't found.")
+ end
+
+ def test_use_tracer_enabled_when_gem_is_available
+ write_rc <<~RUBY
+ IRB.conf[:USE_TRACER] = true
+ RUBY
+
+ write_ruby example_ruby_file
+
+ output = run_ruby_file do
+ type "bar(Foo)"
+ type "exit"
+ end
+
+ assert_include(output, "Object#bar at")
+ assert_include(output, "Foo.foo at")
+ assert_include(output, "Foo.foo #=> 100")
+ assert_include(output, "Object#bar #=> 100")
+
+ # Test that the tracer output does not include IRB's own files
+ assert_not_include(output, "irb/workspace.rb")
+ end
+
+ def test_use_tracer_is_disabled_by_default
+ write_ruby example_ruby_file
+
+ output = run_ruby_file do
+ type "bar(Foo)"
+ type "exit"
+ end
+
+ assert_not_include(output, "#depth:")
+ assert_not_include(output, "Foo.foo")
+ end
+
+ end
+end
diff --git a/test/irb/test_type_completor.rb b/test/irb/test_type_completor.rb
index cf4fc12c9f..5ed8988b34 100644
--- a/test/irb/test_type_completor.rb
+++ b/test/irb/test_type_completor.rb
@@ -9,7 +9,7 @@ rescue LoadError
return
end
-require 'irb/completion'
+require 'irb'
require 'tempfile'
require_relative './helper'
@@ -54,6 +54,11 @@ module TestIRB
assert_equal [], candidates
assert_doc_namespace('(', ')', nil)
end
+
+ def test_command_completion
+ assert_include(@completor.completion_candidates('', 'show_s', '', bind: binding), 'show_source')
+ assert_not_include(@completor.completion_candidates(';', 'show_s', '', bind: binding), 'show_source')
+ end
end
class TypeCompletorIntegrationTest < IntegrationTestCase
diff --git a/test/irb/test_workspace.rb b/test/irb/test_workspace.rb
index ff17b1a22c..199ce95a37 100644
--- a/test/irb/test_workspace.rb
+++ b/test/irb/test_workspace.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: false
require 'tempfile'
-require 'rubygems'
require 'irb'
require 'irb/workspace'
require 'irb/color'
@@ -91,7 +90,7 @@ module TestIRB
irb_path = "#{top_srcdir}/#{dir}/irb"
File.exist?(irb_path)
end or omit 'irb command not found'
- assert_in_out_err(bundle_exec + ['-W0', "-C#{top_srcdir}", '-e', <<~RUBY , '--', '-f', '--'], 'binding.local_variables', /\[:_\]/, [], bug17623)
+ assert_in_out_err(bundle_exec + ['-W0', "-C#{top_srcdir}", '-e', <<~RUBY, '--', '-f', '--'], 'binding.local_variables', /\[:_\]/, [], bug17623)
version = 'xyz' # typical rubygems loading file
load('#{irb_path}')
RUBY
diff --git a/test/irb/yamatanooroti/test_rendering.rb b/test/irb/yamatanooroti/test_rendering.rb
index b18b95bbbd..44e07a3a12 100644
--- a/test/irb/yamatanooroti/test_rendering.rb
+++ b/test/irb/yamatanooroti/test_rendering.rb
@@ -11,6 +11,8 @@ end
class IRB::RenderingTest < Yamatanooroti::TestCase
def setup
@original_term = ENV['TERM']
+ @home_backup = ENV['HOME']
+ @xdg_config_home_backup = ENV['XDG_CONFIG_HOME']
ENV['TERM'] = "xterm-256color"
@pwd = Dir.pwd
suffix = '%010d' % Random.rand(0..65535)
@@ -24,13 +26,16 @@ class IRB::RenderingTest < Yamatanooroti::TestCase
@irbrc_backup = ENV['IRBRC']
@irbrc_file = ENV['IRBRC'] = File.join(@tmpdir, 'temporaty_irbrc')
File.unlink(@irbrc_file) if File.exist?(@irbrc_file)
+ ENV['HOME'] = File.join(@tmpdir, 'home')
+ ENV['XDG_CONFIG_HOME'] = File.join(@tmpdir, 'xdg_config_home')
end
def teardown
FileUtils.rm_rf(@tmpdir)
ENV['IRBRC'] = @irbrc_backup
ENV['TERM'] = @original_term
- ENV.delete('RELINE_TEST_PROMPT') if ENV['RELINE_TEST_PROMPT']
+ ENV['HOME'] = @home_backup
+ ENV['XDG_CONFIG_HOME'] = @xdg_config_home_backup
end
def test_launch
@@ -50,6 +55,42 @@ class IRB::RenderingTest < Yamatanooroti::TestCase
EOC
end
+ def test_configuration_file_is_skipped_with_dash_f
+ write_irbrc <<~'LINES'
+ puts '.irbrc file should be ignored when -f is used'
+ LINES
+ start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb -f}, startup_message: '')
+ write(<<~EOC)
+ 'Hello, World!'
+ EOC
+ close
+ assert_screen(<<~EOC)
+ irb(main):001> 'Hello, World!'
+ => "Hello, World!"
+ irb(main):002>
+ EOC
+ end
+
+ def test_configuration_file_is_skipped_with_dash_f_for_nested_sessions
+ write_irbrc <<~'LINES'
+ puts '.irbrc file should be ignored when -f is used'
+ LINES
+ start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb -f}, startup_message: '')
+ write(<<~EOC)
+ 'Hello, World!'
+ binding.irb
+ exit!
+ EOC
+ close
+ assert_screen(<<~EOC)
+ irb(main):001> 'Hello, World!'
+ => "Hello, World!"
+ irb(main):002> binding.irb
+ irb(main):003> exit!
+ irb(main):001>
+ EOC
+ end
+
def test_nomultiline
write_irbrc <<~'LINES'
puts 'start IRB'
@@ -96,6 +137,7 @@ class IRB::RenderingTest < Yamatanooroti::TestCase
a
.a
.b
+ .itself
EOC
close
assert_screen(<<~EOC)
@@ -112,9 +154,10 @@ class IRB::RenderingTest < Yamatanooroti::TestCase
irb(main):008>
irb(main):009> a
irb(main):010> .a
- irb(main):011> .b
+ irb(main):011> .b
+ irb(main):012> .itself
=> true
- irb(main):012>
+ irb(main):013>
EOC
end
@@ -140,7 +183,6 @@ class IRB::RenderingTest < Yamatanooroti::TestCase
(a)
&.b()
-
class A def b; self; end; def c; true; end; end;
a = A.new
a
@@ -149,6 +191,7 @@ class IRB::RenderingTest < Yamatanooroti::TestCase
.c
(a)
&.b()
+ .itself
EOC
close
assert_screen(<<~EOC)
@@ -173,17 +216,17 @@ class IRB::RenderingTest < Yamatanooroti::TestCase
irb(main):015> &.b()
=> #<A>
irb(main):016>
- irb(main):017>
- irb(main):018> class A def b; self; end; def c; true; end; end;
- irb(main):019> a = A.new
+ irb(main):017> class A def b; self; end; def c; true; end; end;
+ irb(main):018> a = A.new
=> #<A>
- irb(main):020> a
- irb(main):021> .b
- irb(main):022> # aaa
- irb(main):023> .c
+ irb(main):019> a
+ irb(main):020> .b
+ irb(main):021> # aaa
+ irb(main):022> .c
=> true
- irb(main):024> (a)
- irb(main):025> &.b()
+ irb(main):023> (a)
+ irb(main):024> &.b()
+ irb(main):025> .itself
=> #<A>
irb(main):026>
EOC
@@ -213,9 +256,9 @@ class IRB::RenderingTest < Yamatanooroti::TestCase
start_terminal(3, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB')
write("{}.__id_")
write("\C-i")
+ sleep 0.2
close
screen = result.join("\n").sub(/\n*\z/, "\n")
- # This assertion passes whether showdoc dialog completed or not.
assert_match(/start\ IRB\nirb\(main\):001> {}\.__id__\n }\.__id__(?:Press )?/, screen)
end
@@ -233,7 +276,9 @@ class IRB::RenderingTest < Yamatanooroti::TestCase
puts 'start IRB'
LINES
start_terminal(4, 19, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB')
- write("IR\C-i")
+ write("IR")
+ write("\C-i")
+ sleep 0.2
close
# This is because on macOS we display different shortcut for displaying the full doc
@@ -269,7 +314,9 @@ class IRB::RenderingTest < Yamatanooroti::TestCase
puts 'start IRB'
LINES
start_terminal(4, 12, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB')
- write("IR\C-i")
+ write("IR")
+ write("\C-i")
+ sleep 0.2
close
assert_screen(<<~EOC)
start IRB
@@ -319,7 +366,7 @@ class IRB::RenderingTest < Yamatanooroti::TestCase
puts 'start IRB'
LINES
start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB')
- write("show_cmds\n")
+ write("help\n")
write("G") # move to the end of the screen
write("\C-c") # quit pager
write("'foo' + 'bar'\n") # eval something to make sure IRB resumes
diff --git a/test/reline/helper.rb b/test/reline/helper.rb
index fb2262e7f5..26fe834482 100644
--- a/test/reline/helper.rb
+++ b/test/reline/helper.rb
@@ -29,12 +29,19 @@ module Reline
else
encoding = Encoding::UTF_8
end
+ @original_get_screen_size = IOGate.method(:get_screen_size)
+ IOGate.singleton_class.remove_method(:get_screen_size)
+ def IOGate.get_screen_size
+ [24, 80]
+ end
Reline::GeneralIO.reset(encoding: encoding) unless ansi
core.config.instance_variable_set(:@test_mode, true)
core.config.reset
end
def test_reset
+ IOGate.singleton_class.remove_method(:get_screen_size)
+ IOGate.define_singleton_method(:get_screen_size, @original_get_screen_size)
remove_const('IOGate')
const_set('IOGate', @original_iogate)
Reline::GeneralIO.reset
@@ -71,14 +78,6 @@ module Reline
end
end
-def start_pasting
- Reline::GeneralIO.start_pasting
-end
-
-def finish_pasting
- Reline::GeneralIO.finish_pasting
-end
-
class Reline::TestCase < Test::Unit::TestCase
private def convert_str(input, options = {}, normalized = nil)
return nil if input.nil?
@@ -129,9 +128,14 @@ class Reline::TestCase < Test::Unit::TestCase
end
end
- def assert_line(expected)
- expected = convert_str(expected)
- assert_equal(expected, @line_editor.line)
+ def assert_line_around_cursor(before, after)
+ before = convert_str(before)
+ after = convert_str(after)
+ line = @line_editor.current_line
+ byte_pointer = @line_editor.instance_variable_get(:@byte_pointer)
+ actual_before = line.byteslice(0, byte_pointer)
+ actual_after = line.byteslice(byte_pointer..)
+ assert_equal([before, after], [actual_before, actual_after])
end
def assert_byte_pointer_size(expected)
@@ -146,14 +150,6 @@ class Reline::TestCase < Test::Unit::TestCase
EOM
end
- def assert_cursor(expected)
- assert_equal(expected, @line_editor.instance_variable_get(:@cursor))
- end
-
- def assert_cursor_max(expected)
- assert_equal(expected, @line_editor.instance_variable_get(:@cursor_max))
- end
-
def assert_line_index(expected)
assert_equal(expected, @line_editor.instance_variable_get(:@line_index))
end
diff --git a/test/reline/test_ansi_with_terminfo.rb b/test/reline/test_ansi_with_terminfo.rb
index 1387460659..e1c56b9ee1 100644
--- a/test/reline/test_ansi_with_terminfo.rb
+++ b/test/reline/test_ansi_with_terminfo.rb
@@ -109,4 +109,4 @@ class Reline::ANSI::TestWithTerminfo < Reline::TestCase
assert_key_binding("\e ", :em_set_mark, [:emacs])
assert_key_binding("\C-x\C-x", :em_exchange_mark, [:emacs])
end
-end if Reline::Terminfo.enabled?
+end if Reline::Terminfo.enabled? && Reline::Terminfo.term_supported?
diff --git a/test/reline/test_config.rb b/test/reline/test_config.rb
index 9ead047ce4..6068292847 100644
--- a/test/reline/test_config.rb
+++ b/test/reline/test_config.rb
@@ -216,6 +216,38 @@ class Reline::Config::Test < Reline::TestCase
end
end
+ def test_nested_if_else
+ @config.read_lines(<<~LINES.lines)
+ $if Ruby
+ "\x1": "O"
+ $if NotRuby
+ "\x2": "X"
+ $else
+ "\x3": "O"
+ $if Ruby
+ "\x4": "O"
+ $else
+ "\x5": "X"
+ $endif
+ "\x6": "O"
+ $endif
+ "\x7": "O"
+ $else
+ "\x8": "X"
+ $if NotRuby
+ "\x9": "X"
+ $else
+ "\xA": "X"
+ $endif
+ "\xB": "X"
+ $endif
+ "\xC": "O"
+ LINES
+ keys = [0x1, 0x3, 0x4, 0x6, 0x7, 0xC]
+ key_bindings = keys.to_h { |k| [[k.ord], ['O'.ord]] }
+ assert_equal(key_bindings, @config.instance_variable_get(:@additional_key_bindings)[:emacs])
+ end
+
def test_unclosed_if
e = assert_raise(Reline::Config::InvalidInputrc) do
@config.read_lines(<<~LINES.lines, "INPUTRC")
@@ -243,6 +275,78 @@ class Reline::Config::Test < Reline::TestCase
assert_equal "INPUTRC:1: unmatched endif", e.message
end
+ def test_if_with_mode
+ @config.read_lines(<<~LINES.lines)
+ $if mode=emacs
+ "\C-e": history-search-backward # comment
+ $else
+ "\C-f": history-search-forward
+ $endif
+ LINES
+
+ assert_equal({[5] => :history_search_backward}, @config.instance_variable_get(:@additional_key_bindings)[:emacs])
+ assert_equal({}, @config.instance_variable_get(:@additional_key_bindings)[:vi_insert])
+ assert_equal({}, @config.instance_variable_get(:@additional_key_bindings)[:vi_command])
+ end
+
+ def test_else
+ @config.read_lines(<<~LINES.lines)
+ $if mode=vi
+ "\C-e": history-search-backward # comment
+ $else
+ "\C-f": history-search-forward
+ $endif
+ LINES
+
+ assert_equal({[6] => :history_search_forward}, @config.instance_variable_get(:@additional_key_bindings)[:emacs])
+ assert_equal({}, @config.instance_variable_get(:@additional_key_bindings)[:vi_insert])
+ assert_equal({}, @config.instance_variable_get(:@additional_key_bindings)[:vi_command])
+ end
+
+ def test_if_with_invalid_mode
+ @config.read_lines(<<~LINES.lines)
+ $if mode=vim
+ "\C-e": history-search-backward
+ $else
+ "\C-f": history-search-forward # comment
+ $endif
+ LINES
+
+ assert_equal({[6] => :history_search_forward}, @config.instance_variable_get(:@additional_key_bindings)[:emacs])
+ assert_equal({}, @config.instance_variable_get(:@additional_key_bindings)[:vi_insert])
+ assert_equal({}, @config.instance_variable_get(:@additional_key_bindings)[:vi_command])
+ end
+
+ def test_mode_label_differs_from_keymap_label
+ @config.read_lines(<<~LINES.lines)
+ # Sets mode_label and keymap_label to vi
+ set editing-mode vi
+ # Change keymap_label to emacs. mode_label is still vi.
+ set keymap emacs
+ # condition=true because current mode_label is vi
+ $if mode=vi
+ # sets keybinding to current keymap_label=emacs
+ "\C-e": history-search-backward
+ $endif
+ LINES
+ assert_equal({[5] => :history_search_backward}, @config.instance_variable_get(:@additional_key_bindings)[:emacs])
+ assert_equal({}, @config.instance_variable_get(:@additional_key_bindings)[:vi_insert])
+ assert_equal({}, @config.instance_variable_get(:@additional_key_bindings)[:vi_command])
+ end
+
+ def test_if_without_else_condition
+ @config.read_lines(<<~LINES.lines)
+ set editing-mode vi
+ $if mode=vi
+ "\C-e": history-search-backward
+ $endif
+ LINES
+
+ assert_equal({}, @config.instance_variable_get(:@additional_key_bindings)[:emacs])
+ assert_equal({[5] => :history_search_backward}, @config.instance_variable_get(:@additional_key_bindings)[:vi_insert])
+ assert_equal({}, @config.instance_variable_get(:@additional_key_bindings)[:vi_command])
+ end
+
def test_default_key_bindings
@config.add_default_key_binding('abcd'.bytes, 'EFGH'.bytes)
@config.read_lines(<<~'LINES'.lines)
@@ -313,6 +417,18 @@ class Reline::Config::Test < Reline::TestCase
assert_equal expected, @config.key_bindings
end
+ def test_key_bindings_with_reset
+ # @config.reset is called after each readline.
+ # inputrc file is read once, so key binding shouldn't be cleared by @config.reset
+ @config.add_default_key_binding('default'.bytes, 'DEFAULT'.bytes)
+ @config.read_lines(<<~'LINES'.lines)
+ "additional": "ADDITIONAL"
+ LINES
+ @config.reset
+ expected = { 'default'.bytes => 'DEFAULT'.bytes, 'additional'.bytes => 'ADDITIONAL'.bytes }
+ assert_equal expected, @config.key_bindings
+ end
+
def test_history_size
@config.read_lines(<<~LINES.lines)
set history-size 5000
diff --git a/test/reline/test_key_actor_emacs.rb b/test/reline/test_key_actor_emacs.rb
index 4575e51eb2..013ca2f7b3 100644
--- a/test/reline/test_key_actor_emacs.rb
+++ b/test/reline/test_key_actor_emacs.rb
@@ -19,1259 +19,699 @@ class Reline::KeyActor::Emacs::Test < Reline::TestCase
def test_ed_insert_one
input_keys('a')
- assert_line('a')
- assert_byte_pointer_size('a')
- assert_cursor(1)
- assert_cursor_max(1)
+ assert_line_around_cursor('a', '')
end
def test_ed_insert_two
input_keys('ab')
- assert_line('ab')
- assert_byte_pointer_size('ab')
- assert_cursor(2)
- assert_cursor_max(2)
+ assert_line_around_cursor('ab', '')
end
def test_ed_insert_mbchar_one
input_keys('か')
- assert_line('か')
- assert_byte_pointer_size('か')
- assert_cursor(2)
- assert_cursor_max(2)
+ assert_line_around_cursor('か', '')
end
def test_ed_insert_mbchar_two
input_keys('かき')
- assert_line('かき')
- assert_byte_pointer_size('かき')
- assert_cursor(4)
- assert_cursor_max(4)
+ assert_line_around_cursor('かき', '')
end
def test_ed_insert_for_mbchar_by_plural_code_points
input_keys("か\u3099")
- assert_line("か\u3099")
- assert_byte_pointer_size("か\u3099")
- assert_cursor(2)
- assert_cursor_max(2)
+ assert_line_around_cursor("か\u3099", '')
end
def test_ed_insert_for_plural_mbchar_by_plural_code_points
input_keys("か\u3099き\u3099")
- assert_line("か\u3099き\u3099")
- assert_byte_pointer_size("か\u3099き\u3099")
- assert_cursor(4)
- assert_cursor_max(4)
+ assert_line_around_cursor("か\u3099き\u3099", '')
end
def test_move_next_and_prev
input_keys('abd')
- assert_byte_pointer_size('abd')
- assert_cursor(3)
- assert_cursor_max(3)
+ assert_line_around_cursor('abd', '')
input_keys("\C-b", false)
- assert_byte_pointer_size('ab')
- assert_cursor(2)
- assert_cursor_max(3)
+ assert_line_around_cursor('ab', 'd')
input_keys("\C-b", false)
- assert_byte_pointer_size('a')
- assert_cursor(1)
- assert_cursor_max(3)
+ assert_line_around_cursor('a', 'bd')
input_keys("\C-f", false)
- assert_byte_pointer_size('ab')
- assert_cursor(2)
- assert_cursor_max(3)
+ assert_line_around_cursor('ab', 'd')
input_keys('c')
- assert_byte_pointer_size('abc')
- assert_cursor(3)
- assert_cursor_max(4)
- assert_line('abcd')
+ assert_line_around_cursor('abc', 'd')
end
def test_move_next_and_prev_for_mbchar
input_keys('かきけ')
- assert_byte_pointer_size('かきけ')
- assert_cursor(6)
- assert_cursor_max(6)
+ assert_line_around_cursor('かきけ', '')
input_keys("\C-b", false)
- assert_byte_pointer_size('かき')
- assert_cursor(4)
- assert_cursor_max(6)
+ assert_line_around_cursor('かき', 'け')
input_keys("\C-b", false)
- assert_byte_pointer_size('か')
- assert_cursor(2)
- assert_cursor_max(6)
+ assert_line_around_cursor('か', 'きけ')
input_keys("\C-f", false)
- assert_byte_pointer_size('かき')
- assert_cursor(4)
- assert_cursor_max(6)
+ assert_line_around_cursor('かき', 'け')
input_keys('く')
- assert_byte_pointer_size('かきく')
- assert_cursor(6)
- assert_cursor_max(8)
- assert_line('かきくけ')
+ assert_line_around_cursor('かきく', 'け')
end
def test_move_next_and_prev_for_mbchar_by_plural_code_points
input_keys("か\u3099き\u3099け\u3099")
- assert_byte_pointer_size("か\u3099き\u3099け\u3099")
- assert_cursor(6)
- assert_cursor_max(6)
+ assert_line_around_cursor("か\u3099き\u3099け\u3099", '')
input_keys("\C-b", false)
- assert_byte_pointer_size("か\u3099き\u3099")
- assert_cursor(4)
- assert_cursor_max(6)
+ assert_line_around_cursor("か\u3099き\u3099", "け\u3099")
input_keys("\C-b", false)
- assert_byte_pointer_size("か\u3099")
- assert_cursor(2)
- assert_cursor_max(6)
+ assert_line_around_cursor("か\u3099", "き\u3099け\u3099")
input_keys("\C-f", false)
- assert_byte_pointer_size("か\u3099き\u3099")
- assert_cursor(4)
- assert_cursor_max(6)
+ assert_line_around_cursor("か\u3099き\u3099", "け\u3099")
input_keys("く\u3099")
- assert_byte_pointer_size("か\u3099き\u3099く\u3099")
- assert_cursor(6)
- assert_cursor_max(8)
- assert_line("か\u3099き\u3099く\u3099け\u3099")
+ assert_line_around_cursor("か\u3099き\u3099く\u3099", "け\u3099")
end
def test_move_to_beg_end
input_keys('bcd')
- assert_byte_pointer_size('bcd')
- assert_cursor(3)
- assert_cursor_max(3)
+ assert_line_around_cursor('bcd', '')
input_keys("\C-a", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(3)
+ assert_line_around_cursor('', 'bcd')
input_keys('a')
- assert_byte_pointer_size('a')
- assert_cursor(1)
- assert_cursor_max(4)
+ assert_line_around_cursor('a', 'bcd')
input_keys("\C-e", false)
- assert_byte_pointer_size('abcd')
- assert_cursor(4)
- assert_cursor_max(4)
+ assert_line_around_cursor('abcd', '')
input_keys('e')
- assert_byte_pointer_size('abcde')
- assert_cursor(5)
- assert_cursor_max(5)
- assert_line('abcde')
+ assert_line_around_cursor('abcde', '')
end
def test_ed_newline_with_cr
input_keys('ab')
- assert_byte_pointer_size('ab')
- assert_cursor(2)
- assert_cursor_max(2)
+ assert_line_around_cursor('ab', '')
refute(@line_editor.finished?)
input_keys("\C-m", false)
- assert_line('ab')
+ assert_line_around_cursor('ab', '')
assert(@line_editor.finished?)
end
def test_ed_newline_with_lf
input_keys('ab')
- assert_byte_pointer_size('ab')
- assert_cursor(2)
- assert_cursor_max(2)
+ assert_line_around_cursor('ab', '')
refute(@line_editor.finished?)
input_keys("\C-j", false)
- assert_line('ab')
+ assert_line_around_cursor('ab', '')
assert(@line_editor.finished?)
end
def test_em_delete_prev_char
input_keys('ab')
- assert_byte_pointer_size('ab')
- assert_cursor(2)
- assert_cursor_max(2)
+ assert_line_around_cursor('ab', '')
input_keys("\C-h", false)
- assert_byte_pointer_size('a')
- assert_cursor(1)
- assert_cursor_max(1)
- assert_line('a')
+ assert_line_around_cursor('a', '')
end
def test_em_delete_prev_char_for_mbchar
input_keys('かき')
- assert_byte_pointer_size('かき')
- assert_cursor(4)
- assert_cursor_max(4)
+ assert_line_around_cursor('かき', '')
input_keys("\C-h", false)
- assert_byte_pointer_size('か')
- assert_cursor(2)
- assert_cursor_max(2)
- assert_line('か')
+ assert_line_around_cursor('か', '')
end
def test_em_delete_prev_char_for_mbchar_by_plural_code_points
input_keys("か\u3099き\u3099")
- assert_byte_pointer_size("か\u3099き\u3099")
- assert_cursor(4)
- assert_cursor_max(4)
+ assert_line_around_cursor("か\u3099き\u3099", '')
input_keys("\C-h", false)
- assert_byte_pointer_size("か\u3099")
- assert_cursor(2)
- assert_cursor_max(2)
- assert_line("か\u3099")
+ assert_line_around_cursor("か\u3099", '')
end
def test_ed_quoted_insert
input_keys("ab\C-v\C-acd")
- assert_line("ab\C-acd")
- assert_byte_pointer_size("ab\C-acd")
- assert_cursor(6)
- assert_cursor_max(6)
+ assert_line_around_cursor("ab\C-acd", '')
input_keys("\C-q\C-b")
- assert_line("ab\C-acd\C-b")
- assert_byte_pointer_size("ab\C-acd\C-b")
- assert_cursor(8)
- assert_cursor_max(8)
+ assert_line_around_cursor("ab\C-acd\C-b", '')
end
def test_ed_kill_line
input_keys("\C-k", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
- assert_line('')
+ assert_line_around_cursor('', '')
input_keys('abc')
- assert_byte_pointer_size('abc')
- assert_cursor(3)
- assert_cursor_max(3)
+ assert_line_around_cursor('abc', '')
input_keys("\C-k", false)
- assert_byte_pointer_size('abc')
- assert_cursor(3)
- assert_cursor_max(3)
- assert_line('abc')
+ assert_line_around_cursor('abc', '')
input_keys("\C-b\C-k", false)
- assert_byte_pointer_size('ab')
- assert_cursor(2)
- assert_cursor_max(2)
- assert_line('ab')
+ assert_line_around_cursor('ab', '')
end
def test_em_kill_line
@line_editor.input_key(Reline::Key.new(:em_kill_line, :em_kill_line, false))
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
- assert_line('')
+ assert_line_around_cursor('', '')
input_keys('abc')
@line_editor.input_key(Reline::Key.new(:em_kill_line, :em_kill_line, false))
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
- assert_line('')
+ assert_line_around_cursor('', '')
input_keys('abc')
input_keys("\C-b", false)
@line_editor.input_key(Reline::Key.new(:em_kill_line, :em_kill_line, false))
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
- assert_line('')
+ assert_line_around_cursor('', '')
input_keys('abc')
input_keys("\C-a", false)
@line_editor.input_key(Reline::Key.new(:em_kill_line, :em_kill_line, false))
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
- assert_line('')
+ assert_line_around_cursor('', '')
end
def test_ed_move_to_beg
input_keys('abd')
- assert_byte_pointer_size('abd')
- assert_cursor(3)
- assert_cursor_max(3)
+ assert_line_around_cursor('abd', '')
input_keys("\C-b", false)
- assert_byte_pointer_size('ab')
- assert_cursor(2)
- assert_cursor_max(3)
+ assert_line_around_cursor('ab', 'd')
input_keys('c')
- assert_byte_pointer_size('abc')
- assert_cursor(3)
- assert_cursor_max(4)
+ assert_line_around_cursor('abc', 'd')
input_keys("\C-a", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(4)
+ assert_line_around_cursor('', 'abcd')
input_keys('012')
- assert_byte_pointer_size('012')
- assert_cursor(3)
- assert_cursor_max(7)
- assert_line('012abcd')
+ assert_line_around_cursor('012', 'abcd')
input_keys("\C-a", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(7)
+ assert_line_around_cursor('', '012abcd')
input_keys('ABC')
- assert_byte_pointer_size('ABC')
- assert_cursor(3)
- assert_cursor_max(10)
- assert_line('ABC012abcd')
+ assert_line_around_cursor('ABC', '012abcd')
input_keys("\C-f" * 10 + "\C-a", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(10)
+ assert_line_around_cursor('', 'ABC012abcd')
input_keys('a')
- assert_byte_pointer_size('a')
- assert_cursor(1)
- assert_cursor_max(11)
- assert_line('aABC012abcd')
+ assert_line_around_cursor('a', 'ABC012abcd')
end
def test_ed_move_to_beg_with_blank
input_keys(' abc')
- assert_byte_pointer_size(' abc')
- assert_cursor(5)
- assert_cursor_max(5)
+ assert_line_around_cursor(' abc', '')
input_keys("\C-a", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(5)
+ assert_line_around_cursor('', ' abc')
end
def test_ed_move_to_end
input_keys('abd')
- assert_byte_pointer_size('abd')
- assert_cursor(3)
- assert_cursor_max(3)
+ assert_line_around_cursor('abd', '')
input_keys("\C-b", false)
- assert_byte_pointer_size('ab')
- assert_cursor(2)
- assert_cursor_max(3)
+ assert_line_around_cursor('ab', 'd')
input_keys('c')
- assert_byte_pointer_size('abc')
- assert_cursor(3)
- assert_cursor_max(4)
+ assert_line_around_cursor('abc', 'd')
input_keys("\C-e", false)
- assert_byte_pointer_size('abcd')
- assert_cursor(4)
- assert_cursor_max(4)
+ assert_line_around_cursor('abcd', '')
input_keys('012')
- assert_byte_pointer_size('abcd012')
- assert_cursor(7)
- assert_cursor_max(7)
- assert_line('abcd012')
+ assert_line_around_cursor('abcd012', '')
input_keys("\C-e", false)
- assert_byte_pointer_size('abcd012')
- assert_cursor(7)
- assert_cursor_max(7)
+ assert_line_around_cursor('abcd012', '')
input_keys('ABC')
- assert_byte_pointer_size('abcd012ABC')
- assert_cursor(10)
- assert_cursor_max(10)
- assert_line('abcd012ABC')
+ assert_line_around_cursor('abcd012ABC', '')
input_keys("\C-b" * 10 + "\C-e", false)
- assert_byte_pointer_size('abcd012ABC')
- assert_cursor(10)
- assert_cursor_max(10)
+ assert_line_around_cursor('abcd012ABC', '')
input_keys('a')
- assert_byte_pointer_size('abcd012ABCa')
- assert_cursor(11)
- assert_cursor_max(11)
- assert_line('abcd012ABCa')
+ assert_line_around_cursor('abcd012ABCa', '')
end
def test_em_delete
input_keys('ab')
- assert_byte_pointer_size('ab')
- assert_cursor(2)
- assert_cursor_max(2)
+ assert_line_around_cursor('ab', '')
input_keys("\C-a", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(2)
+ assert_line_around_cursor('', 'ab')
input_keys("\C-d", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(1)
- assert_line('b')
+ assert_line_around_cursor('', 'b')
end
def test_em_delete_for_mbchar
input_keys('かき')
- assert_byte_pointer_size('かき')
- assert_cursor(4)
- assert_cursor_max(4)
+ assert_line_around_cursor('かき', '')
input_keys("\C-a", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(4)
+ assert_line_around_cursor('', 'かき')
input_keys("\C-d", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(2)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(2)
- assert_line('き')
+ assert_line_around_cursor('', 'き')
end
def test_em_delete_for_mbchar_by_plural_code_points
input_keys("か\u3099き\u3099")
- assert_byte_pointer_size("か\u3099き\u3099")
- assert_cursor(4)
- assert_cursor_max(4)
+ assert_line_around_cursor("か\u3099き\u3099", '')
input_keys("\C-a", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(4)
+ assert_line_around_cursor('', "か\u3099き\u3099")
input_keys("\C-d", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(2)
- assert_line("き\u3099")
+ assert_line_around_cursor('', "き\u3099")
end
def test_em_delete_ends_editing
input_keys("\C-d") # quit from inputing
- assert_line(nil)
+ assert_nil(@line_editor.line)
assert(@line_editor.finished?)
end
def test_ed_clear_screen
- refute(@line_editor.instance_variable_get(:@cleared))
+ @line_editor.instance_variable_get(:@rendered_screen).lines = [[]]
input_keys("\C-l", false)
- assert(@line_editor.instance_variable_get(:@cleared))
+ assert_empty(@line_editor.instance_variable_get(:@rendered_screen).lines)
end
def test_ed_clear_screen_with_inputed
input_keys('abc')
input_keys("\C-b", false)
- refute(@line_editor.instance_variable_get(:@cleared))
- assert_byte_pointer_size('ab')
- assert_cursor(2)
- assert_cursor_max(3)
+ @line_editor.instance_variable_get(:@rendered_screen).lines = [[]]
+ assert_line_around_cursor('ab', 'c')
input_keys("\C-l", false)
- assert(@line_editor.instance_variable_get(:@cleared))
- assert_byte_pointer_size('ab')
- assert_cursor(2)
- assert_cursor_max(3)
- assert_line('abc')
+ assert_empty(@line_editor.instance_variable_get(:@rendered_screen).lines)
+ assert_line_around_cursor('ab', 'c')
end
def test_key_delete
input_keys('abc')
- assert_cursor(3)
- assert_cursor_max(3)
+ assert_line_around_cursor('abc', '')
@line_editor.input_key(Reline::Key.new(:key_delete, :key_delete, false))
- assert_cursor(3)
- assert_cursor_max(3)
- assert_line('abc')
+ assert_line_around_cursor('abc', '')
end
def test_key_delete_does_not_end_editing
@line_editor.input_key(Reline::Key.new(:key_delete, :key_delete, false))
- assert_cursor(0)
- assert_cursor_max(0)
- assert_line('')
+ assert_line_around_cursor('', '')
refute(@line_editor.finished?)
end
def test_key_delete_preserves_cursor
input_keys('abc')
input_keys("\C-b", false)
- assert_cursor(2)
- assert_cursor_max(3)
+ assert_line_around_cursor('ab', 'c')
@line_editor.input_key(Reline::Key.new(:key_delete, :key_delete, false))
- assert_cursor(2)
- assert_cursor_max(2)
- assert_line('ab')
+ assert_line_around_cursor('ab', '')
end
def test_em_next_word
- assert_byte_pointer_size('')
- assert_cursor(0)
+ assert_line_around_cursor('', '')
input_keys('abc def{bbb}ccc')
input_keys("\C-a\M-F", false)
- assert_byte_pointer_size('abc')
- assert_cursor(3)
+ assert_line_around_cursor('abc', ' def{bbb}ccc')
input_keys("\M-F", false)
- assert_byte_pointer_size('abc def')
- assert_cursor(7)
+ assert_line_around_cursor('abc def', '{bbb}ccc')
input_keys("\M-F", false)
- assert_byte_pointer_size('abc def{bbb')
- assert_cursor(11)
+ assert_line_around_cursor('abc def{bbb', '}ccc')
input_keys("\M-F", false)
- assert_byte_pointer_size('abc def{bbb}ccc')
- assert_cursor(15)
+ assert_line_around_cursor('abc def{bbb}ccc', '')
input_keys("\M-F", false)
- assert_byte_pointer_size('abc def{bbb}ccc')
- assert_cursor(15)
+ assert_line_around_cursor('abc def{bbb}ccc', '')
end
def test_em_next_word_for_mbchar
- assert_cursor(0)
+ assert_line_around_cursor('', '')
input_keys('あいう かきく{さしす}たちつ')
input_keys("\C-a\M-F", false)
- assert_byte_pointer_size('あいう')
- assert_cursor(6)
+ assert_line_around_cursor('あいう', ' かきく{さしす}たちつ')
input_keys("\M-F", false)
- assert_byte_pointer_size('あいう かきく')
- assert_cursor(13)
+ assert_line_around_cursor('あいう かきく', '{さしす}たちつ')
input_keys("\M-F", false)
- assert_byte_pointer_size('あいう かきく{さしす')
- assert_cursor(20)
+ assert_line_around_cursor('あいう かきく{さしす', '}たちつ')
input_keys("\M-F", false)
- assert_byte_pointer_size('あいう かきく{さしす}たちつ')
- assert_cursor(27)
+ assert_line_around_cursor('あいう かきく{さしす}たちつ', '')
input_keys("\M-F", false)
- assert_byte_pointer_size('あいう かきく{さしす}たちつ')
- assert_cursor(27)
+ assert_line_around_cursor('あいう かきく{さしす}たちつ', '')
end
def test_em_next_word_for_mbchar_by_plural_code_points
- assert_cursor(0)
+ assert_line_around_cursor("", "")
input_keys("あいう か\u3099き\u3099く\u3099{さしす}たちつ")
input_keys("\C-a\M-F", false)
- assert_byte_pointer_size("あいう")
- assert_cursor(6)
+ assert_line_around_cursor("あいう", " か\u3099き\u3099く\u3099{さしす}たちつ")
input_keys("\M-F", false)
- assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099")
- assert_cursor(13)
+ assert_line_around_cursor("あいう か\u3099き\u3099く\u3099", "{さしす}たちつ")
input_keys("\M-F", false)
- assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099{さしす")
- assert_cursor(20)
+ assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす", "}たちつ")
input_keys("\M-F", false)
- assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099{さしす}たちつ")
- assert_cursor(27)
+ assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}たちつ", "")
input_keys("\M-F", false)
- assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099{さしす}たちつ")
- assert_cursor(27)
+ assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}たちつ", "")
end
def test_em_prev_word
input_keys('abc def{bbb}ccc')
- assert_byte_pointer_size('abc def{bbb}ccc')
- assert_cursor(15)
+ assert_line_around_cursor('abc def{bbb}ccc', '')
input_keys("\M-B", false)
- assert_byte_pointer_size('abc def{bbb}')
- assert_cursor(12)
+ assert_line_around_cursor('abc def{bbb}', 'ccc')
input_keys("\M-B", false)
- assert_byte_pointer_size('abc def{')
- assert_cursor(8)
+ assert_line_around_cursor('abc def{', 'bbb}ccc')
input_keys("\M-B", false)
- assert_byte_pointer_size('abc ')
- assert_cursor(4)
+ assert_line_around_cursor('abc ', 'def{bbb}ccc')
input_keys("\M-B", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
+ assert_line_around_cursor('', 'abc def{bbb}ccc')
input_keys("\M-B", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
+ assert_line_around_cursor('', 'abc def{bbb}ccc')
end
def test_em_prev_word_for_mbchar
input_keys('あいう かきく{さしす}たちつ')
- assert_byte_pointer_size('あいう かきく{さしす}たちつ')
- assert_cursor(27)
+ assert_line_around_cursor('あいう かきく{さしす}たちつ', '')
input_keys("\M-B", false)
- assert_byte_pointer_size('あいう かきく{さしす}')
- assert_cursor(21)
+ assert_line_around_cursor('あいう かきく{さしす}', 'たちつ')
input_keys("\M-B", false)
- assert_byte_pointer_size('あいう かきく{')
- assert_cursor(14)
+ assert_line_around_cursor('あいう かきく{', 'さしす}たちつ')
input_keys("\M-B", false)
- assert_byte_pointer_size('あいう ')
- assert_cursor(7)
+ assert_line_around_cursor('あいう ', 'かきく{さしす}たちつ')
input_keys("\M-B", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
+ assert_line_around_cursor('', 'あいう かきく{さしす}たちつ')
input_keys("\M-B", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
+ assert_line_around_cursor('', 'あいう かきく{さしす}たちつ')
end
def test_em_prev_word_for_mbchar_by_plural_code_points
input_keys("あいう か\u3099き\u3099く\u3099{さしす}たちつ")
- assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099{さしす}たちつ")
- assert_cursor(27)
+ assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}たちつ", "")
input_keys("\M-B", false)
- assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099{さしす}")
- assert_cursor(21)
+ assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}", "たちつ")
input_keys("\M-B", false)
- assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099{")
- assert_cursor(14)
+ assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{", "さしす}たちつ")
input_keys("\M-B", false)
- assert_byte_pointer_size('あいう ')
- assert_cursor(7)
+ assert_line_around_cursor("あいう ", "か\u3099き\u3099く\u3099{さしす}たちつ")
input_keys("\M-B", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
+ assert_line_around_cursor("", "あいう か\u3099き\u3099く\u3099{さしす}たちつ")
input_keys("\M-B", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
+ assert_line_around_cursor("", "あいう か\u3099き\u3099く\u3099{さしす}たちつ")
end
def test_em_delete_next_word
input_keys('abc def{bbb}ccc')
input_keys("\C-a", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(15)
+ assert_line_around_cursor('', 'abc def{bbb}ccc')
input_keys("\M-d", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(12)
- assert_line(' def{bbb}ccc')
+ assert_line_around_cursor('', ' def{bbb}ccc')
input_keys("\M-d", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(8)
- assert_line('{bbb}ccc')
+ assert_line_around_cursor('', '{bbb}ccc')
input_keys("\M-d", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(4)
- assert_line('}ccc')
+ assert_line_around_cursor('', '}ccc')
input_keys("\M-d", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
- assert_line('')
+ assert_line_around_cursor('', '')
end
def test_em_delete_next_word_for_mbchar
input_keys('あいう かきく{さしす}たちつ')
input_keys("\C-a", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(27)
+ assert_line_around_cursor('', 'あいう かきく{さしす}たちつ')
input_keys("\M-d", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(21)
- assert_line(' かきく{さしす}たちつ')
+ assert_line_around_cursor('', ' かきく{さしす}たちつ')
input_keys("\M-d", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(14)
- assert_line('{さしす}たちつ')
+ assert_line_around_cursor('', '{さしす}たちつ')
input_keys("\M-d", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(7)
- assert_line('}たちつ')
+ assert_line_around_cursor('', '}たちつ')
input_keys("\M-d", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
- assert_line('')
+ assert_line_around_cursor('', '')
end
def test_em_delete_next_word_for_mbchar_by_plural_code_points
input_keys("あいう か\u3099き\u3099く\u3099{さしす}たちつ")
input_keys("\C-a", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(27)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(27)
+ assert_line_around_cursor('', "あいう か\u3099き\u3099く\u3099{さしす}たちつ")
input_keys("\M-d", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(21)
- assert_line(" か\u3099き\u3099く\u3099{さしす}たちつ")
+ assert_line_around_cursor('', " か\u3099き\u3099く\u3099{さしす}たちつ")
input_keys("\M-d", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(14)
- assert_line('{さしす}たちつ')
+ assert_line_around_cursor('', '{さしす}たちつ')
input_keys("\M-d", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(7)
- assert_line('}たちつ')
+ assert_line_around_cursor('', '}たちつ')
input_keys("\M-d", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
- assert_line('')
+ assert_line_around_cursor('', '')
end
def test_ed_delete_prev_word
input_keys('abc def{bbb}ccc')
- assert_byte_pointer_size('abc def{bbb}ccc')
- assert_cursor(15)
- assert_cursor_max(15)
+ assert_line_around_cursor('abc def{bbb}ccc', '')
input_keys("\M-\C-H", false)
- assert_byte_pointer_size('abc def{bbb}')
- assert_cursor(12)
- assert_cursor_max(12)
- assert_line('abc def{bbb}')
+ assert_line_around_cursor('abc def{bbb}', '')
input_keys("\M-\C-H", false)
- assert_byte_pointer_size('abc def{')
- assert_cursor(8)
- assert_cursor_max(8)
- assert_line('abc def{')
+ assert_line_around_cursor('abc def{', '')
input_keys("\M-\C-H", false)
- assert_byte_pointer_size('abc ')
- assert_cursor(4)
- assert_cursor_max(4)
- assert_line('abc ')
+ assert_line_around_cursor('abc ', '')
input_keys("\M-\C-H", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
- assert_line('')
+ assert_line_around_cursor('', '')
end
def test_ed_delete_prev_word_for_mbchar
input_keys('あいう かきく{さしす}たちつ')
- assert_byte_pointer_size('あいう かきく{さしす}たちつ')
- assert_cursor(27)
- assert_cursor_max(27)
+ assert_line_around_cursor('あいう かきく{さしす}たちつ', '')
input_keys("\M-\C-H", false)
- assert_byte_pointer_size('あいう かきく{さしす}')
- assert_cursor(21)
- assert_cursor_max(21)
- assert_line('あいう かきく{さしす}')
+ assert_line_around_cursor('あいう かきく{さしす}', '')
input_keys("\M-\C-H", false)
- assert_byte_pointer_size('あいう かきく{')
- assert_cursor(14)
- assert_cursor_max(14)
- assert_line('あいう かきく{')
+ assert_line_around_cursor('あいう かきく{', '')
input_keys("\M-\C-H", false)
- assert_byte_pointer_size('あいう ')
- assert_cursor(7)
- assert_cursor_max(7)
- assert_line('あいう ')
+ assert_line_around_cursor('あいう ', '')
input_keys("\M-\C-H", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
- assert_line('')
+ assert_line_around_cursor('', '')
end
def test_ed_delete_prev_word_for_mbchar_by_plural_code_points
input_keys("あいう か\u3099き\u3099く\u3099{さしす}たちつ")
- assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099{さしす}たちつ")
- assert_cursor(27)
- assert_cursor_max(27)
+ assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}たちつ", '')
input_keys("\M-\C-H", false)
- assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099{さしす}")
- assert_cursor(21)
- assert_cursor_max(21)
- assert_line("あいう か\u3099き\u3099く\u3099{さしす}")
+ assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}", '')
input_keys("\M-\C-H", false)
- assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099{")
- assert_cursor(14)
- assert_cursor_max(14)
- assert_line("あいう か\u3099き\u3099く\u3099{")
+ assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{", '')
input_keys("\M-\C-H", false)
- assert_byte_pointer_size("あいう ")
- assert_cursor(7)
- assert_cursor_max(7)
- assert_line('あいう ')
+ assert_line_around_cursor('あいう ', '')
input_keys("\M-\C-H", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
- assert_line('')
+ assert_line_around_cursor('', '')
end
def test_ed_transpose_chars
input_keys('abc')
input_keys("\C-a", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(3)
+ assert_line_around_cursor('', 'abc')
input_keys("\C-t", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(3)
- assert_line('abc')
+ assert_line_around_cursor('', 'abc')
input_keys("\C-f\C-t", false)
- assert_byte_pointer_size('ba')
- assert_cursor(2)
- assert_cursor_max(3)
- assert_line('bac')
+ assert_line_around_cursor('ba', 'c')
input_keys("\C-t", false)
- assert_byte_pointer_size('bca')
- assert_cursor(3)
- assert_cursor_max(3)
- assert_line('bca')
+ assert_line_around_cursor('bca', '')
input_keys("\C-t", false)
- assert_byte_pointer_size('bac')
- assert_cursor(3)
- assert_cursor_max(3)
- assert_line('bac')
+ assert_line_around_cursor('bac', '')
end
def test_ed_transpose_chars_for_mbchar
input_keys('あかさ')
input_keys("\C-a", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(6)
+ assert_line_around_cursor('', 'あかさ')
input_keys("\C-t", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(6)
- assert_line('あかさ')
+ assert_line_around_cursor('', 'あかさ')
input_keys("\C-f\C-t", false)
- assert_byte_pointer_size('かあ')
- assert_cursor(4)
- assert_cursor_max(6)
- assert_line('かあさ')
+ assert_line_around_cursor('かあ', 'さ')
input_keys("\C-t", false)
- assert_byte_pointer_size('かさあ')
- assert_cursor(6)
- assert_cursor_max(6)
- assert_line('かさあ')
+ assert_line_around_cursor('かさあ', '')
input_keys("\C-t", false)
- assert_byte_pointer_size('かあさ')
- assert_cursor(6)
- assert_cursor_max(6)
- assert_line('かあさ')
+ assert_line_around_cursor('かあさ', '')
end
def test_ed_transpose_chars_for_mbchar_by_plural_code_points
input_keys("あか\u3099さ")
input_keys("\C-a", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(6)
+ assert_line_around_cursor('', "あか\u3099さ")
input_keys("\C-t", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(6)
- assert_line("あか\u3099さ")
+ assert_line_around_cursor('', "あか\u3099さ")
input_keys("\C-f\C-t", false)
- assert_byte_pointer_size("か\u3099あ")
- assert_cursor(4)
- assert_cursor_max(6)
- assert_line("か\u3099あさ")
+ assert_line_around_cursor("か\u3099あ", 'さ')
input_keys("\C-t", false)
- assert_byte_pointer_size("か\u3099さあ")
- assert_cursor(6)
- assert_cursor_max(6)
- assert_line("か\u3099さあ")
+ assert_line_around_cursor("か\u3099さあ", '')
input_keys("\C-t", false)
- assert_byte_pointer_size("か\u3099あさ")
- assert_cursor(6)
- assert_cursor_max(6)
- assert_line("か\u3099あさ")
+ assert_line_around_cursor("か\u3099あさ", '')
end
def test_ed_transpose_words
input_keys('abc def')
- assert_line('abc def')
- assert_byte_pointer_size('abc def')
- assert_cursor(7)
- assert_cursor_max(7)
+ assert_line_around_cursor('abc def', '')
input_keys("\M-t", false)
- assert_line('def abc')
- assert_byte_pointer_size('def abc')
- assert_cursor(7)
- assert_cursor_max(7)
+ assert_line_around_cursor('def abc', '')
input_keys("\C-a\C-k", false)
input_keys(' abc def ')
input_keys("\C-b" * 4, false)
- assert_line(' abc def ')
- assert_byte_pointer_size(' abc de')
- assert_cursor(8)
- assert_cursor_max(12)
+ assert_line_around_cursor(' abc de', 'f ')
input_keys("\M-t", false)
- assert_line(' def abc ')
- assert_byte_pointer_size(' def abc')
- assert_cursor(9)
- assert_cursor_max(12)
+ assert_line_around_cursor(' def abc', ' ')
input_keys("\C-a\C-k", false)
input_keys(' abc def ')
input_keys("\C-b" * 6, false)
- assert_line(' abc def ')
- assert_byte_pointer_size(' abc ')
- assert_cursor(6)
- assert_cursor_max(12)
+ assert_line_around_cursor(' abc ', 'def ')
input_keys("\M-t", false)
- assert_line(' def abc ')
- assert_byte_pointer_size(' def abc')
- assert_cursor(9)
- assert_cursor_max(12)
+ assert_line_around_cursor(' def abc', ' ')
input_keys("\M-t", false)
- assert_line(' abc def')
- assert_byte_pointer_size(' abc def')
- assert_cursor(12)
- assert_cursor_max(12)
+ assert_line_around_cursor(' abc def', '')
end
def test_ed_transpose_words_for_mbchar
input_keys('あいう かきく')
- assert_line('あいう かきく')
- assert_byte_pointer_size('あいう かきく')
- assert_cursor(13)
- assert_cursor_max(13)
+ assert_line_around_cursor('あいう かきく', '')
input_keys("\M-t", false)
- assert_line('かきく あいう')
- assert_byte_pointer_size('かきく あいう')
- assert_cursor(13)
- assert_cursor_max(13)
+ assert_line_around_cursor('かきく あいう', '')
input_keys("\C-a\C-k", false)
input_keys(' あいう かきく ')
input_keys("\C-b" * 4, false)
- assert_line(' あいう かきく ')
- assert_byte_pointer_size(' あいう かき')
- assert_cursor(13)
- assert_cursor_max(18)
+ assert_line_around_cursor(' あいう かき', 'く ')
input_keys("\M-t", false)
- assert_line(' かきく あいう ')
- assert_byte_pointer_size(' かきく あいう')
- assert_cursor(15)
- assert_cursor_max(18)
+ assert_line_around_cursor(' かきく あいう', ' ')
input_keys("\C-a\C-k", false)
input_keys(' あいう かきく ')
input_keys("\C-b" * 6, false)
- assert_line(' あいう かきく ')
- assert_byte_pointer_size(' あいう ')
- assert_cursor(9)
- assert_cursor_max(18)
+ assert_line_around_cursor(' あいう ', 'かきく ')
input_keys("\M-t", false)
- assert_line(' かきく あいう ')
- assert_byte_pointer_size(' かきく あいう')
- assert_cursor(15)
- assert_cursor_max(18)
+ assert_line_around_cursor(' かきく あいう', ' ')
input_keys("\M-t", false)
- assert_line(' あいう かきく')
- assert_byte_pointer_size(' あいう かきく')
- assert_cursor(18)
- assert_cursor_max(18)
+ assert_line_around_cursor(' あいう かきく', '')
end
def test_ed_transpose_words_with_one_word
input_keys('abc ')
- assert_line('abc ')
- assert_byte_pointer_size('abc ')
- assert_cursor(5)
- assert_cursor_max(5)
+ assert_line_around_cursor('abc ', '')
input_keys("\M-t", false)
- assert_line('abc ')
- assert_byte_pointer_size('abc ')
- assert_cursor(5)
- assert_cursor_max(5)
+ assert_line_around_cursor('abc ', '')
input_keys("\C-b", false)
- assert_line('abc ')
- assert_byte_pointer_size('abc ')
- assert_cursor(4)
- assert_cursor_max(5)
+ assert_line_around_cursor('abc ', ' ')
input_keys("\M-t", false)
- assert_line('abc ')
- assert_byte_pointer_size('abc ')
- assert_cursor(4)
- assert_cursor_max(5)
+ assert_line_around_cursor('abc ', ' ')
input_keys("\C-b" * 2, false)
- assert_line('abc ')
- assert_byte_pointer_size('ab')
- assert_cursor(2)
- assert_cursor_max(5)
+ assert_line_around_cursor('ab', 'c ')
input_keys("\M-t", false)
- assert_line('abc ')
- assert_byte_pointer_size('ab')
- assert_cursor(2)
- assert_cursor_max(5)
+ assert_line_around_cursor('ab', 'c ')
input_keys("\M-t", false)
- assert_line('abc ')
- assert_byte_pointer_size('ab')
- assert_cursor(2)
- assert_cursor_max(5)
+ assert_line_around_cursor('ab', 'c ')
end
def test_ed_transpose_words_with_one_word_for_mbchar
input_keys('あいう ')
- assert_line('あいう ')
- assert_byte_pointer_size('あいう ')
- assert_cursor(8)
- assert_cursor_max(8)
+ assert_line_around_cursor('あいう ', '')
input_keys("\M-t", false)
- assert_line('あいう ')
- assert_byte_pointer_size('あいう ')
- assert_cursor(8)
- assert_cursor_max(8)
+ assert_line_around_cursor('あいう ', '')
input_keys("\C-b", false)
- assert_line('あいう ')
- assert_byte_pointer_size('あいう ')
- assert_cursor(7)
- assert_cursor_max(8)
+ assert_line_around_cursor('あいう ', ' ')
input_keys("\M-t", false)
- assert_line('あいう ')
- assert_byte_pointer_size('あいう ')
- assert_cursor(7)
- assert_cursor_max(8)
+ assert_line_around_cursor('あいう ', ' ')
input_keys("\C-b" * 2, false)
- assert_line('あいう ')
- assert_byte_pointer_size('あい')
- assert_cursor(4)
- assert_cursor_max(8)
+ assert_line_around_cursor('あい', 'う ')
input_keys("\M-t", false)
- assert_line('あいう ')
- assert_byte_pointer_size('あい')
- assert_cursor(4)
- assert_cursor_max(8)
+ assert_line_around_cursor('あい', 'う ')
input_keys("\M-t", false)
- assert_line('あいう ')
- assert_byte_pointer_size('あい')
- assert_cursor(4)
- assert_cursor_max(8)
+ assert_line_around_cursor('あい', 'う ')
end
def test_ed_digit
input_keys('0123')
- assert_byte_pointer_size('0123')
- assert_cursor(4)
- assert_cursor_max(4)
- assert_line('0123')
+ assert_line_around_cursor('0123', '')
end
def test_ed_next_and_prev_char
input_keys('abc')
- assert_byte_pointer_size('abc')
- assert_cursor(3)
- assert_cursor_max(3)
+ assert_line_around_cursor('abc', '')
input_keys("\C-b", false)
- assert_byte_pointer_size('ab')
- assert_cursor(2)
- assert_cursor_max(3)
+ assert_line_around_cursor('ab', 'c')
input_keys("\C-b", false)
- assert_byte_pointer_size('a')
- assert_cursor(1)
- assert_cursor_max(3)
+ assert_line_around_cursor('a', 'bc')
input_keys("\C-b", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(3)
+ assert_line_around_cursor('', 'abc')
input_keys("\C-b", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(3)
+ assert_line_around_cursor('', 'abc')
input_keys("\C-f", false)
- assert_byte_pointer_size('a')
- assert_cursor(1)
- assert_cursor_max(3)
+ assert_line_around_cursor('a', 'bc')
input_keys("\C-f", false)
- assert_byte_pointer_size('ab')
- assert_cursor(2)
- assert_cursor_max(3)
+ assert_line_around_cursor('ab', 'c')
input_keys("\C-f", false)
- assert_byte_pointer_size('abc')
- assert_cursor(3)
- assert_cursor_max(3)
+ assert_line_around_cursor('abc', '')
input_keys("\C-f", false)
- assert_byte_pointer_size('abc')
- assert_cursor(3)
- assert_cursor_max(3)
+ assert_line_around_cursor('abc', '')
end
def test_ed_next_and_prev_char_for_mbchar
input_keys('あいう')
- assert_byte_pointer_size('あいう')
- assert_cursor(6)
- assert_cursor_max(6)
+ assert_line_around_cursor('あいう', '')
input_keys("\C-b", false)
- assert_byte_pointer_size('あい')
- assert_cursor(4)
- assert_cursor_max(6)
+ assert_line_around_cursor('あい', 'う')
input_keys("\C-b", false)
- assert_byte_pointer_size('あ')
- assert_cursor(2)
- assert_cursor_max(6)
+ assert_line_around_cursor('あ', 'いう')
input_keys("\C-b", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(6)
+ assert_line_around_cursor('', 'あいう')
input_keys("\C-b", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(6)
+ assert_line_around_cursor('', 'あいう')
input_keys("\C-f", false)
- assert_byte_pointer_size('あ')
- assert_cursor(2)
- assert_cursor_max(6)
+ assert_line_around_cursor('あ', 'いう')
input_keys("\C-f", false)
- assert_byte_pointer_size('あい')
- assert_cursor(4)
- assert_cursor_max(6)
+ assert_line_around_cursor('あい', 'う')
input_keys("\C-f", false)
- assert_byte_pointer_size('あいう')
- assert_cursor(6)
- assert_cursor_max(6)
+ assert_line_around_cursor('あいう', '')
input_keys("\C-f", false)
- assert_byte_pointer_size('あいう')
- assert_cursor(6)
- assert_cursor_max(6)
+ assert_line_around_cursor('あいう', '')
end
def test_ed_next_and_prev_char_for_mbchar_by_plural_code_points
input_keys("か\u3099き\u3099く\u3099")
- assert_byte_pointer_size("か\u3099き\u3099く\u3099")
- assert_cursor(6)
- assert_cursor_max(6)
+ assert_line_around_cursor("か\u3099き\u3099く\u3099", '')
input_keys("\C-b", false)
- assert_byte_pointer_size("か\u3099き\u3099")
- assert_cursor(4)
- assert_cursor_max(6)
+ assert_line_around_cursor("か\u3099き\u3099", "く\u3099")
input_keys("\C-b", false)
- assert_byte_pointer_size("か\u3099")
- assert_cursor(2)
- assert_cursor_max(6)
+ assert_line_around_cursor("か\u3099", "き\u3099く\u3099")
input_keys("\C-b", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(6)
+ assert_line_around_cursor('', "か\u3099き\u3099く\u3099")
input_keys("\C-b", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(6)
+ assert_line_around_cursor('', "か\u3099き\u3099く\u3099")
input_keys("\C-f", false)
- assert_byte_pointer_size("か\u3099")
- assert_cursor(2)
- assert_cursor_max(6)
+ assert_line_around_cursor("か\u3099", "き\u3099く\u3099")
input_keys("\C-f", false)
- assert_byte_pointer_size("か\u3099き\u3099")
- assert_cursor(4)
- assert_cursor_max(6)
+ assert_line_around_cursor("か\u3099き\u3099", "く\u3099")
input_keys("\C-f", false)
- assert_byte_pointer_size("か\u3099き\u3099く\u3099")
- assert_cursor(6)
- assert_cursor_max(6)
+ assert_line_around_cursor("か\u3099き\u3099く\u3099", '')
input_keys("\C-f", false)
- assert_byte_pointer_size("か\u3099き\u3099く\u3099")
- assert_cursor(6)
- assert_cursor_max(6)
+ assert_line_around_cursor("か\u3099き\u3099く\u3099", '')
end
def test_em_capitol_case
input_keys('abc def{bbb}ccc')
input_keys("\C-a\M-c", false)
- assert_byte_pointer_size('Abc')
- assert_cursor(3)
- assert_cursor_max(15)
- assert_line('Abc def{bbb}ccc')
+ assert_line_around_cursor('Abc', ' def{bbb}ccc')
input_keys("\M-c", false)
- assert_byte_pointer_size('Abc Def')
- assert_cursor(7)
- assert_cursor_max(15)
- assert_line('Abc Def{bbb}ccc')
+ assert_line_around_cursor('Abc Def', '{bbb}ccc')
input_keys("\M-c", false)
- assert_byte_pointer_size('Abc Def{Bbb')
- assert_cursor(11)
- assert_cursor_max(15)
- assert_line('Abc Def{Bbb}ccc')
+ assert_line_around_cursor('Abc Def{Bbb', '}ccc')
input_keys("\M-c", false)
- assert_byte_pointer_size('Abc Def{Bbb}Ccc')
- assert_cursor(15)
- assert_cursor_max(15)
- assert_line('Abc Def{Bbb}Ccc')
+ assert_line_around_cursor('Abc Def{Bbb}Ccc', '')
end
def test_em_capitol_case_with_complex_example
input_keys('{}#* AaA!!!cCc ')
input_keys("\C-a\M-c", false)
- assert_byte_pointer_size('{}#* Aaa')
- assert_cursor(11)
- assert_cursor_max(20)
- assert_line('{}#* Aaa!!!cCc ')
+ assert_line_around_cursor('{}#* Aaa', '!!!cCc ')
input_keys("\M-c", false)
- assert_byte_pointer_size('{}#* Aaa!!!Ccc')
- assert_cursor(17)
- assert_cursor_max(20)
- assert_line('{}#* Aaa!!!Ccc ')
+ assert_line_around_cursor('{}#* Aaa!!!Ccc', ' ')
input_keys("\M-c", false)
- assert_byte_pointer_size('{}#* Aaa!!!Ccc ')
- assert_cursor(20)
- assert_cursor_max(20)
- assert_line('{}#* Aaa!!!Ccc ')
+ assert_line_around_cursor('{}#* Aaa!!!Ccc ', '')
end
def test_em_lower_case
input_keys('AbC def{bBb}CCC')
input_keys("\C-a\M-l", false)
- assert_byte_pointer_size('abc')
- assert_cursor(3)
- assert_cursor_max(15)
- assert_line('abc def{bBb}CCC')
+ assert_line_around_cursor('abc', ' def{bBb}CCC')
input_keys("\M-l", false)
- assert_byte_pointer_size('abc def')
- assert_cursor(7)
- assert_cursor_max(15)
- assert_line('abc def{bBb}CCC')
+ assert_line_around_cursor('abc def', '{bBb}CCC')
input_keys("\M-l", false)
- assert_byte_pointer_size('abc def{bbb')
- assert_cursor(11)
- assert_cursor_max(15)
- assert_line('abc def{bbb}CCC')
+ assert_line_around_cursor('abc def{bbb', '}CCC')
input_keys("\M-l", false)
- assert_byte_pointer_size('abc def{bbb}ccc')
- assert_cursor(15)
- assert_cursor_max(15)
- assert_line('abc def{bbb}ccc')
+ assert_line_around_cursor('abc def{bbb}ccc', '')
end
def test_em_lower_case_with_complex_example
input_keys('{}#* AaA!!!cCc ')
input_keys("\C-a\M-l", false)
- assert_byte_pointer_size('{}#* aaa')
- assert_cursor(11)
- assert_cursor_max(20)
- assert_line('{}#* aaa!!!cCc ')
+ assert_line_around_cursor('{}#* aaa', '!!!cCc ')
input_keys("\M-l", false)
- assert_byte_pointer_size('{}#* aaa!!!ccc')
- assert_cursor(17)
- assert_cursor_max(20)
- assert_line('{}#* aaa!!!ccc ')
+ assert_line_around_cursor('{}#* aaa!!!ccc', ' ')
input_keys("\M-l", false)
- assert_byte_pointer_size('{}#* aaa!!!ccc ')
- assert_cursor(20)
- assert_cursor_max(20)
- assert_line('{}#* aaa!!!ccc ')
+ assert_line_around_cursor('{}#* aaa!!!ccc ', '')
end
def test_em_upper_case
input_keys('AbC def{bBb}CCC')
input_keys("\C-a\M-u", false)
- assert_byte_pointer_size('ABC')
- assert_cursor(3)
- assert_cursor_max(15)
- assert_line('ABC def{bBb}CCC')
+ assert_line_around_cursor('ABC', ' def{bBb}CCC')
input_keys("\M-u", false)
- assert_byte_pointer_size('ABC DEF')
- assert_cursor(7)
- assert_cursor_max(15)
- assert_line('ABC DEF{bBb}CCC')
+ assert_line_around_cursor('ABC DEF', '{bBb}CCC')
input_keys("\M-u", false)
- assert_byte_pointer_size('ABC DEF{BBB')
- assert_cursor(11)
- assert_cursor_max(15)
- assert_line('ABC DEF{BBB}CCC')
+ assert_line_around_cursor('ABC DEF{BBB', '}CCC')
input_keys("\M-u", false)
- assert_byte_pointer_size('ABC DEF{BBB}CCC')
- assert_cursor(15)
- assert_cursor_max(15)
- assert_line('ABC DEF{BBB}CCC')
+ assert_line_around_cursor('ABC DEF{BBB}CCC', '')
end
def test_em_upper_case_with_complex_example
input_keys('{}#* AaA!!!cCc ')
input_keys("\C-a\M-u", false)
- assert_byte_pointer_size('{}#* AAA')
- assert_cursor(11)
- assert_cursor_max(20)
- assert_line('{}#* AAA!!!cCc ')
+ assert_line_around_cursor('{}#* AAA', '!!!cCc ')
input_keys("\M-u", false)
- assert_byte_pointer_size('{}#* AAA!!!CCC')
- assert_cursor(17)
- assert_cursor_max(20)
- assert_line('{}#* AAA!!!CCC ')
+ assert_line_around_cursor('{}#* AAA!!!CCC', ' ')
input_keys("\M-u", false)
- assert_byte_pointer_size('{}#* AAA!!!CCC ')
- assert_cursor(20)
- assert_cursor_max(20)
- assert_line('{}#* AAA!!!CCC ')
+ assert_line_around_cursor('{}#* AAA!!!CCC ', '')
end
def test_em_delete_or_list
@@ -1286,28 +726,16 @@ class Reline::KeyActor::Emacs::Test < Reline::TestCase
}
}
input_keys('fooo')
- assert_byte_pointer_size('fooo')
- assert_cursor(4)
- assert_cursor_max(4)
- assert_line('fooo')
+ assert_line_around_cursor('fooo', '')
assert_equal(nil, @line_editor.instance_variable_get(:@menu_info))
input_keys("\C-b", false)
- assert_byte_pointer_size('foo')
- assert_cursor(3)
- assert_cursor_max(4)
- assert_line('fooo')
+ assert_line_around_cursor('foo', 'o')
assert_equal(nil, @line_editor.instance_variable_get(:@menu_info))
@line_editor.input_key(Reline::Key.new(:em_delete_or_list, :em_delete_or_list, false))
- assert_byte_pointer_size('foo')
- assert_cursor(3)
- assert_cursor_max(3)
- assert_line('foo')
+ assert_line_around_cursor('foo', '')
assert_equal(nil, @line_editor.instance_variable_get(:@menu_info))
@line_editor.input_key(Reline::Key.new(:em_delete_or_list, :em_delete_or_list, false))
- assert_byte_pointer_size('foo')
- assert_cursor(3)
- assert_cursor_max(3)
- assert_line('foo')
+ assert_line_around_cursor('foo', '')
assert_equal(%w{foo_foo foo_bar foo_baz}, @line_editor.instance_variable_get(:@menu_info).list)
end
@@ -1322,22 +750,13 @@ class Reline::KeyActor::Emacs::Test < Reline::TestCase
}
}
input_keys('foo_')
- assert_byte_pointer_size('foo_')
- assert_cursor(4)
- assert_cursor_max(4)
- assert_line('foo_')
+ assert_line_around_cursor('foo_', '')
assert_equal(nil, @line_editor.instance_variable_get(:@menu_info))
input_keys("\C-i", false)
- assert_byte_pointer_size('foo_')
- assert_cursor(4)
- assert_cursor_max(4)
- assert_line('foo_')
+ assert_line_around_cursor('foo_', '')
assert_equal(nil, @line_editor.instance_variable_get(:@menu_info))
input_keys("\C-i", false)
- assert_byte_pointer_size('foo_')
- assert_cursor(4)
- assert_cursor_max(4)
- assert_line('foo_')
+ assert_line_around_cursor('foo_', '')
assert_equal(%w{foo_foo foo_bar}, @line_editor.instance_variable_get(:@menu_info).list)
end
@@ -1353,36 +772,63 @@ class Reline::KeyActor::Emacs::Test < Reline::TestCase
}
}
input_keys('fo')
- assert_byte_pointer_size('fo')
- assert_cursor(2)
- assert_cursor_max(2)
- assert_line('fo')
+ assert_line_around_cursor('fo', '')
assert_equal(nil, @line_editor.instance_variable_get(:@menu_info))
input_keys("\C-i", false)
- assert_byte_pointer_size('foo_')
- assert_cursor(4)
- assert_cursor_max(4)
- assert_line('foo_')
+ assert_line_around_cursor('foo_', '')
assert_equal(nil, @line_editor.instance_variable_get(:@menu_info))
input_keys("\C-i", false)
- assert_byte_pointer_size('foo_')
- assert_cursor(4)
- assert_cursor_max(4)
- assert_line('foo_')
+ assert_line_around_cursor('foo_', '')
assert_equal(%w{foo_foo foo_bar foo_baz}, @line_editor.instance_variable_get(:@menu_info).list)
input_keys('a')
input_keys("\C-i", false)
- assert_byte_pointer_size('foo_a')
- assert_cursor(5)
- assert_cursor_max(5)
- assert_line('foo_a')
+ assert_line_around_cursor('foo_a', '')
input_keys("\C-h", false)
input_keys('b')
input_keys("\C-i", false)
- assert_byte_pointer_size('foo_ba')
- assert_cursor(6)
- assert_cursor_max(6)
- assert_line('foo_ba')
+ assert_line_around_cursor('foo_ba', '')
+ input_keys("\C-h")
+ input_key_by_symbol(:complete)
+ assert_line_around_cursor('foo_ba', '')
+ input_keys("\C-h", false)
+ input_key_by_symbol(:menu_complete)
+ assert_line_around_cursor('foo_bar', '')
+ input_key_by_symbol(:menu_complete)
+ assert_line_around_cursor('foo_baz', '')
+ input_keys("\C-h", false)
+ input_key_by_symbol(:menu_complete_backward)
+ assert_line_around_cursor('foo_baz', '')
+ input_key_by_symbol(:menu_complete_backward)
+ assert_line_around_cursor('foo_bar', '')
+ end
+
+ def test_autocompletion
+ @config.autocompletion = true
+ @line_editor.completion_proc = proc { |word|
+ %w{
+ Readline
+ Regexp
+ RegexpError
+ }.map { |i|
+ i.encode(@encoding)
+ }
+ }
+ input_keys('Re')
+ assert_line_around_cursor('Re', '')
+ input_keys("\C-i", false)
+ assert_line_around_cursor('Readline', '')
+ input_keys("\C-i", false)
+ assert_line_around_cursor('Regexp', '')
+ input_key_by_symbol(:completion_journey_up)
+ assert_line_around_cursor('Readline', '')
+ input_key_by_symbol(:complete)
+ assert_line_around_cursor('Regexp', '')
+ input_key_by_symbol(:menu_complete_backward)
+ assert_line_around_cursor('Readline', '')
+ input_key_by_symbol(:menu_complete)
+ assert_line_around_cursor('Regexp', '')
+ ensure
+ @config.autocompletion = false
end
def test_completion_with_indent
@@ -1397,22 +843,13 @@ class Reline::KeyActor::Emacs::Test < Reline::TestCase
}
}
input_keys(' fo')
- assert_byte_pointer_size(' fo')
- assert_cursor(4)
- assert_cursor_max(4)
- assert_line(' fo')
+ assert_line_around_cursor(' 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_line_around_cursor(' 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_line_around_cursor(' foo_', '')
assert_equal(%w{foo_foo foo_bar foo_baz}, @line_editor.instance_variable_get(:@menu_info).list)
end
@@ -1428,22 +865,13 @@ class Reline::KeyActor::Emacs::Test < Reline::TestCase
}
}
input_keys(' "".fo')
- assert_byte_pointer_size(' "".fo')
- assert_cursor(7)
- assert_cursor_max(7)
- assert_line(' "".fo')
+ assert_line_around_cursor(' "".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_line_around_cursor(' "".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_line_around_cursor(' "".foo_', '')
assert_equal(%w{"".foo_foo "".foo_bar "".foo_baz}, @line_editor.instance_variable_get(:@menu_info).list)
end
@@ -1461,54 +889,33 @@ class Reline::KeyActor::Emacs::Test < Reline::TestCase
matched = m
}
input_keys('fo')
- assert_byte_pointer_size('fo')
- assert_cursor(2)
- assert_cursor_max(2)
- assert_line('fo')
+ assert_line_around_cursor('fo', '')
assert_equal(Reline::LineEditor::CompletionState::NORMAL, @line_editor.instance_variable_get(:@completion_state))
assert_equal(nil, matched)
input_keys("\C-i", false)
- assert_byte_pointer_size('foo')
- assert_cursor(3)
- assert_cursor_max(3)
- assert_line('foo')
+ assert_line_around_cursor('foo', '')
assert_equal(Reline::LineEditor::CompletionState::MENU_WITH_PERFECT_MATCH, @line_editor.instance_variable_get(:@completion_state))
assert_equal(nil, matched)
input_keys("\C-i", false)
- assert_byte_pointer_size('foo')
- assert_cursor(3)
- assert_cursor_max(3)
- assert_line('foo')
+ assert_line_around_cursor('foo', '')
assert_equal(Reline::LineEditor::CompletionState::PERFECT_MATCH, @line_editor.instance_variable_get(:@completion_state))
assert_equal(nil, matched)
input_keys("\C-i", false)
- assert_byte_pointer_size('foo')
- assert_cursor(3)
- assert_cursor_max(3)
- assert_line('foo')
+ assert_line_around_cursor('foo', '')
assert_equal(Reline::LineEditor::CompletionState::PERFECT_MATCH, @line_editor.instance_variable_get(:@completion_state))
assert_equal('foo', matched)
matched = nil
input_keys('_')
input_keys("\C-i", false)
- assert_byte_pointer_size('foo_bar')
- assert_cursor(7)
- assert_cursor_max(7)
- assert_line('foo_bar')
+ assert_line_around_cursor('foo_bar', '')
assert_equal(Reline::LineEditor::CompletionState::MENU_WITH_PERFECT_MATCH, @line_editor.instance_variable_get(:@completion_state))
assert_equal(nil, matched)
input_keys("\C-i", false)
- assert_byte_pointer_size('foo_bar')
- assert_cursor(7)
- assert_cursor_max(7)
- assert_line('foo_bar')
+ assert_line_around_cursor('foo_bar', '')
assert_equal(Reline::LineEditor::CompletionState::PERFECT_MATCH, @line_editor.instance_variable_get(:@completion_state))
assert_equal(nil, matched)
input_keys("\C-i", false)
- assert_byte_pointer_size('foo_bar')
- assert_cursor(7)
- assert_cursor_max(7)
- assert_line('foo_bar')
+ assert_line_around_cursor('foo_bar', '')
assert_equal(Reline::LineEditor::CompletionState::PERFECT_MATCH, @line_editor.instance_variable_get(:@completion_state))
assert_equal('foo_bar', matched)
end
@@ -1525,43 +932,25 @@ class Reline::KeyActor::Emacs::Test < Reline::TestCase
}
}
input_keys('fo')
- assert_byte_pointer_size('fo')
- assert_cursor(2)
- assert_cursor_max(2)
- assert_line('fo')
+ assert_line_around_cursor('fo', '')
assert_equal(nil, @line_editor.instance_variable_get(:@menu_info))
input_keys("\C-i", false)
- assert_byte_pointer_size('foo_')
- assert_cursor(4)
- assert_cursor_max(4)
- assert_line('foo_')
+ assert_line_around_cursor('foo_', '')
assert_equal(nil, @line_editor.instance_variable_get(:@menu_info))
input_keys("\C-i", false)
- assert_byte_pointer_size('foo_')
- assert_cursor(4)
- assert_cursor_max(4)
- assert_line('foo_')
+ assert_line_around_cursor('foo_', '')
assert_equal(%w{foo_foo foo_bar}, @line_editor.instance_variable_get(:@menu_info).list)
@config.completion_ignore_case = true
input_keys("\C-i", false)
- assert_byte_pointer_size('foo_')
- assert_cursor(4)
- assert_cursor_max(4)
- assert_line('foo_')
+ assert_line_around_cursor('foo_', '')
assert_equal(%w{foo_foo foo_bar Foo_baz}, @line_editor.instance_variable_get(:@menu_info).list)
input_keys('a')
input_keys("\C-i", false)
- assert_byte_pointer_size('foo_a')
- assert_cursor(5)
- assert_cursor_max(5)
- assert_line('foo_a')
+ assert_line_around_cursor('foo_a', '')
input_keys("\C-h", false)
input_keys('b')
input_keys("\C-i", false)
- assert_byte_pointer_size('foo_ba')
- assert_cursor(6)
- assert_cursor_max(6)
- assert_line('foo_ba')
+ assert_line_around_cursor('foo_ba', '')
end
def test_completion_in_middle_of_line
@@ -1576,17 +965,11 @@ class Reline::KeyActor::Emacs::Test < Reline::TestCase
}
}
input_keys('abcde fo ABCDE')
- assert_line('abcde fo ABCDE')
+ assert_line_around_cursor('abcde fo ABCDE', '')
input_keys("\C-b" * 6 + "\C-i", false)
- assert_byte_pointer_size('abcde foo_')
- assert_cursor(10)
- assert_cursor_max(16)
- assert_line('abcde foo_ ABCDE')
+ assert_line_around_cursor('abcde foo_', ' ABCDE')
input_keys("\C-b" * 2 + "\C-i", false)
- assert_byte_pointer_size('abcde foo_')
- assert_cursor(10)
- assert_cursor_max(18)
- assert_line('abcde foo_o_ ABCDE')
+ assert_line_around_cursor('abcde foo_', 'o_ ABCDE')
end
def test_completion_with_nil_value
@@ -1602,125 +985,65 @@ class Reline::KeyActor::Emacs::Test < Reline::TestCase
}
@config.completion_ignore_case = true
input_keys('fo')
- assert_byte_pointer_size('fo')
- assert_cursor(2)
- assert_cursor_max(2)
- assert_line('fo')
+ assert_line_around_cursor('fo', '')
assert_equal(nil, @line_editor.instance_variable_get(:@menu_info))
input_keys("\C-i", false)
- assert_byte_pointer_size('foo_')
- assert_cursor(4)
- assert_cursor_max(4)
- assert_line('foo_')
+ assert_line_around_cursor('foo_', '')
assert_equal(nil, @line_editor.instance_variable_get(:@menu_info))
input_keys("\C-i", false)
- assert_byte_pointer_size('foo_')
- assert_cursor(4)
- assert_cursor_max(4)
- assert_line('foo_')
+ assert_line_around_cursor('foo_', '')
assert_equal(%w{foo_foo foo_bar Foo_baz}, @line_editor.instance_variable_get(:@menu_info).list)
input_keys('a')
input_keys("\C-i", false)
- assert_byte_pointer_size('foo_a')
- assert_cursor(5)
- assert_cursor_max(5)
- assert_line('foo_a')
+ assert_line_around_cursor('foo_a', '')
input_keys("\C-h", false)
input_keys('b')
input_keys("\C-i", false)
- assert_byte_pointer_size('foo_ba')
- assert_cursor(6)
- assert_cursor_max(6)
- assert_line('foo_ba')
+ assert_line_around_cursor('foo_ba', '')
end
def test_em_kill_region
input_keys('abc def{bbb}ccc ddd ')
- assert_byte_pointer_size('abc def{bbb}ccc ddd ')
- assert_cursor(26)
- assert_cursor_max(26)
- assert_line('abc def{bbb}ccc ddd ')
+ assert_line_around_cursor('abc def{bbb}ccc ddd ', '')
input_keys("\C-w", false)
- assert_byte_pointer_size('abc def{bbb}ccc ')
- assert_cursor(20)
- assert_cursor_max(20)
- assert_line('abc def{bbb}ccc ')
+ assert_line_around_cursor('abc def{bbb}ccc ', '')
input_keys("\C-w", false)
- assert_byte_pointer_size('abc ')
- assert_cursor(6)
- assert_cursor_max(6)
- assert_line('abc ')
+ assert_line_around_cursor('abc ', '')
input_keys("\C-w", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
- assert_line('')
+ assert_line_around_cursor('', '')
input_keys("\C-w", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
- assert_line('')
+ assert_line_around_cursor('', '')
end
def test_em_kill_region_mbchar
input_keys('あ い う{う}う ')
- assert_byte_pointer_size('あ い う{う}う ')
- assert_cursor(21)
- assert_cursor_max(21)
- assert_line('あ い う{う}う ')
+ assert_line_around_cursor('あ い う{う}う ', '')
input_keys("\C-w", false)
- assert_byte_pointer_size('あ い ')
- assert_cursor(10)
- assert_cursor_max(10)
- assert_line('あ い ')
+ assert_line_around_cursor('あ い ', '')
input_keys("\C-w", false)
- assert_byte_pointer_size('あ ')
- assert_cursor(5)
- assert_cursor_max(5)
- assert_line('あ ')
+ assert_line_around_cursor('あ ', '')
input_keys("\C-w", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
- assert_line('')
+ assert_line_around_cursor('', '')
end
def test_vi_search_prev
Reline::HISTORY.concat(%w{abc 123 AAA})
- assert_line('')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
+ assert_line_around_cursor('', '')
input_keys("\C-ra\C-j")
- assert_line('abc')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(3)
+ assert_line_around_cursor('', 'abc')
end
def test_larger_histories_than_history_size
history_size = @config.history_size
@config.history_size = 2
Reline::HISTORY.concat(%w{abc 123 AAA})
- assert_line('')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
+ assert_line_around_cursor('', '')
input_keys("\C-p")
- assert_line('AAA')
- assert_byte_pointer_size('AAA')
- assert_cursor(3)
- assert_cursor_max(3)
+ assert_line_around_cursor('AAA', '')
input_keys("\C-p")
- assert_line('123')
- assert_byte_pointer_size('123')
- assert_cursor(3)
- assert_cursor_max(3)
+ assert_line_around_cursor('123', '')
input_keys("\C-p")
- assert_line('123')
- assert_byte_pointer_size('123')
- assert_cursor(3)
- assert_cursor_max(3)
+ assert_line_around_cursor('123', '')
ensure
@config.history_size = history_size
end
@@ -1731,25 +1054,13 @@ class Reline::KeyActor::Emacs::Test < Reline::TestCase
'12aa',
'1234' # new
])
- assert_line('')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
+ assert_line_around_cursor('', '')
input_keys("\C-r123")
- assert_line('1234')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0) # doesn't determine yet
+ assert_line_around_cursor('1234', '')
input_keys("\C-ha")
- assert_line('12aa')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
+ assert_line_around_cursor('12aa', '')
input_keys("\C-h3")
- assert_line('1235')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
+ assert_line_around_cursor('1235', '')
end
def test_search_history_to_front
@@ -1758,25 +1069,13 @@ class Reline::KeyActor::Emacs::Test < Reline::TestCase
'12aa',
'1234' # new
])
- assert_line('')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
+ assert_line_around_cursor('', '')
input_keys("\C-s123")
- assert_line('1235')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0) # doesn't determine yet
+ assert_line_around_cursor('1235', '')
input_keys("\C-ha")
- assert_line('12aa')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
+ assert_line_around_cursor('12aa', '')
input_keys("\C-h3")
- assert_line('1234')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
+ assert_line_around_cursor('1234', '')
end
def test_search_history_front_and_back
@@ -1785,30 +1084,15 @@ class Reline::KeyActor::Emacs::Test < Reline::TestCase
'12aa',
'1234' # new
])
- assert_line('')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
+ assert_line_around_cursor('', '')
input_keys("\C-s12")
- assert_line('1235')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0) # doesn't determine yet
+ assert_line_around_cursor('1235', '')
input_keys("\C-s")
- assert_line('12aa')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
+ assert_line_around_cursor('12aa', '')
input_keys("\C-r")
- assert_line('12aa')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
+ assert_line_around_cursor('12aa', '')
input_keys("\C-r")
- assert_line('1235')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
+ assert_line_around_cursor('1235', '')
end
def test_search_history_back_and_front
@@ -1817,30 +1101,15 @@ class Reline::KeyActor::Emacs::Test < Reline::TestCase
'12aa',
'1234' # new
])
- assert_line('')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
+ assert_line_around_cursor('', '')
input_keys("\C-r12")
- assert_line('1234')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0) # doesn't determine yet
+ assert_line_around_cursor('1234', '')
input_keys("\C-r")
- assert_line('12aa')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
+ assert_line_around_cursor('12aa', '')
input_keys("\C-s")
- assert_line('12aa')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
+ assert_line_around_cursor('12aa', '')
input_keys("\C-s")
- assert_line('1234')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
+ assert_line_around_cursor('1234', '')
end
def test_search_history_to_back_in_the_middle_of_histories
@@ -1849,20 +1118,11 @@ class Reline::KeyActor::Emacs::Test < Reline::TestCase
'12aa',
'1234' # new
])
- assert_line('')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
+ assert_line_around_cursor('', '')
input_keys("\C-p\C-p")
- assert_line('12aa')
- assert_byte_pointer_size('12aa')
- assert_cursor(4)
- assert_cursor_max(4)
+ assert_line_around_cursor('12aa', '')
input_keys("\C-r123")
- assert_line('1235')
- assert_byte_pointer_size('1235')
- assert_cursor(4)
- assert_cursor_max(4)
+ assert_line_around_cursor('1235', '')
end
def test_search_history_twice
@@ -1871,20 +1131,11 @@ class Reline::KeyActor::Emacs::Test < Reline::TestCase
'12aa',
'1234' # new
])
- assert_line('')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
+ assert_line_around_cursor('', '')
input_keys("\C-r123")
- assert_line('1234')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0) # doesn't determine yet
+ assert_line_around_cursor('1234', '')
input_keys("\C-r")
- assert_line('1235')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
+ assert_line_around_cursor('1235', '')
end
def test_search_history_by_last_determined
@@ -1893,35 +1144,17 @@ class Reline::KeyActor::Emacs::Test < Reline::TestCase
'12aa',
'1234' # new
])
- assert_line('')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
+ assert_line_around_cursor('', '')
input_keys("\C-r123")
- assert_line('1234')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0) # doesn't determine yet
+ assert_line_around_cursor('1234', '')
input_keys("\C-j")
- assert_line('1234')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(4)
+ assert_line_around_cursor('', '1234')
input_keys("\C-k") # delete
- assert_line('')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
+ assert_line_around_cursor('', '')
input_keys("\C-r")
- assert_line('')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
+ assert_line_around_cursor('', '')
input_keys("\C-r")
- assert_line('1235')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
+ assert_line_around_cursor('1235', '')
end
def test_search_history_with_isearch_terminator
@@ -1933,76 +1166,40 @@ class Reline::KeyActor::Emacs::Test < Reline::TestCase
'12aa',
'1234' # new
])
- assert_line('')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
+ assert_line_around_cursor('', '')
input_keys("\C-r12a")
- assert_line('12aa')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0) # doesn't determine yet
+ assert_line_around_cursor('12aa', '')
input_keys('Y')
- assert_line('12aa')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(4)
+ assert_line_around_cursor('', '12aa')
input_keys('x')
- assert_line('x12aa')
- assert_byte_pointer_size('x')
- assert_cursor(1)
- assert_cursor_max(5)
+ assert_line_around_cursor('x', '12aa')
end
def test_em_set_mark_and_em_exchange_mark
input_keys('aaa bbb ccc ddd')
- assert_byte_pointer_size('aaa bbb ccc ddd')
- assert_cursor(15)
- assert_cursor_max(15)
- assert_line('aaa bbb ccc ddd')
+ assert_line_around_cursor('aaa bbb ccc ddd', '')
input_keys("\C-a\M-F\M-F", false)
- assert_byte_pointer_size('aaa bbb')
- assert_cursor(7)
- assert_cursor_max(15)
- assert_line('aaa bbb ccc ddd')
+ assert_line_around_cursor('aaa bbb', ' ccc ddd')
assert_equal(nil, @line_editor.instance_variable_get(:@mark_pointer))
input_keys("\x00", false) # C-Space
- assert_byte_pointer_size('aaa bbb')
- assert_cursor(7)
- assert_cursor_max(15)
- assert_line('aaa bbb ccc ddd')
+ assert_line_around_cursor('aaa bbb', ' ccc ddd')
assert_equal([7, 0], @line_editor.instance_variable_get(:@mark_pointer))
input_keys("\C-a", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(15)
- assert_line('aaa bbb ccc ddd')
+ assert_line_around_cursor('', 'aaa bbb ccc ddd')
assert_equal([7, 0], @line_editor.instance_variable_get(:@mark_pointer))
input_key_by_symbol(:em_exchange_mark)
- assert_byte_pointer_size('aaa bbb')
- assert_cursor(7)
- assert_cursor_max(15)
- assert_line('aaa bbb ccc ddd')
+ assert_line_around_cursor('aaa bbb', ' ccc ddd')
assert_equal([0, 0], @line_editor.instance_variable_get(:@mark_pointer))
end
def test_em_exchange_mark_without_mark
input_keys('aaa bbb ccc ddd')
- assert_byte_pointer_size('aaa bbb ccc ddd')
- assert_cursor(15)
- assert_cursor_max(15)
- assert_line('aaa bbb ccc ddd')
+ assert_line_around_cursor('aaa bbb ccc ddd', '')
input_keys("\C-a\M-f", false)
- assert_byte_pointer_size('aaa')
- assert_cursor(3)
- assert_cursor_max(15)
- assert_line('aaa bbb ccc ddd')
+ assert_line_around_cursor('aaa', ' bbb ccc ddd')
assert_equal(nil, @line_editor.instance_variable_get(:@mark_pointer))
input_key_by_symbol(:em_exchange_mark)
- assert_byte_pointer_size('aaa')
- assert_cursor(3)
- assert_cursor_max(15)
- assert_line('aaa bbb ccc ddd')
+ assert_line_around_cursor('aaa', ' bbb ccc ddd')
assert_equal(nil, @line_editor.instance_variable_get(:@mark_pointer))
end
@@ -2013,7 +1210,7 @@ class Reline::KeyActor::Emacs::Test < Reline::TestCase
$VERBOSE = verbose
@line_editor.output_modifier_proc = proc { |output| Reline::Unicode.escape_for_print(output) }
input_keys("abcdef\n")
- result = @line_editor.__send__(:modify_lines, @line_editor.whole_lines)
+ result = @line_editor.__send__(:modify_lines, @line_editor.whole_lines, @line_editor.finished?)
$/ = nil
assert_equal(['abcdef'], result)
ensure
@@ -2031,20 +1228,11 @@ class Reline::KeyActor::Emacs::Test < Reline::TestCase
input_keys('123')
# The ed_search_prev_history doesn't have default binding
@line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
- assert_byte_pointer_size('123')
- assert_cursor(3)
- assert_cursor_max(5)
- assert_line('12345')
+ assert_line_around_cursor('123', '45')
@line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
- assert_byte_pointer_size('123')
- assert_cursor(3)
- assert_cursor_max(5)
- assert_line('12356')
+ assert_line_around_cursor('123', '56')
@line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
- assert_byte_pointer_size('123')
- assert_cursor(3)
- assert_cursor_max(5)
- assert_line('12356')
+ assert_line_around_cursor('123', '56')
end
def test_ed_search_prev_history_with_empty
@@ -2055,25 +1243,13 @@ class Reline::KeyActor::Emacs::Test < Reline::TestCase
])
# The ed_search_prev_history doesn't have default binding
@line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(5)
- assert_line('12345')
+ assert_line_around_cursor('', '12345')
@line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(5)
- assert_line('12aaa')
+ assert_line_around_cursor('', '12aaa')
@line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(5)
- assert_line('12356')
+ assert_line_around_cursor('', '12356')
@line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(5)
- assert_line('12356')
+ assert_line_around_cursor('', '12356')
end
def test_ed_search_prev_history_without_match
@@ -2085,10 +1261,7 @@ class Reline::KeyActor::Emacs::Test < Reline::TestCase
input_keys('ABC')
# The ed_search_prev_history doesn't have default binding
@line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
- assert_byte_pointer_size('ABC')
- assert_cursor(3)
- assert_cursor_max(3)
- assert_line('ABC')
+ assert_line_around_cursor('ABC', '')
end
def test_ed_search_next_history
@@ -2100,30 +1273,15 @@ class Reline::KeyActor::Emacs::Test < Reline::TestCase
input_keys('123')
# The ed_search_prev_history and ed_search_next_history doesn't have default binding
@line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
- assert_byte_pointer_size('123')
- assert_cursor(3)
- assert_cursor_max(5)
- assert_line('12345')
+ assert_line_around_cursor('123', '45')
@line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
- assert_byte_pointer_size('123')
- assert_cursor(3)
- assert_cursor_max(5)
- assert_line('12356')
+ assert_line_around_cursor('123', '56')
@line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
- assert_byte_pointer_size('123')
- assert_cursor(3)
- assert_cursor_max(5)
- assert_line('12356')
+ assert_line_around_cursor('123', '56')
@line_editor.__send__(:ed_search_next_history, "\C-n".ord)
- assert_byte_pointer_size('123')
- assert_cursor(3)
- assert_cursor_max(5)
- assert_line('12345')
+ assert_line_around_cursor('123', '45')
@line_editor.__send__(:ed_search_next_history, "\C-n".ord)
- assert_byte_pointer_size('123')
- assert_cursor(3)
- assert_cursor_max(5)
- assert_line('12345')
+ assert_line_around_cursor('123', '45')
end
def test_ed_search_next_history_with_empty
@@ -2134,35 +1292,25 @@ class Reline::KeyActor::Emacs::Test < Reline::TestCase
])
# The ed_search_prev_history and ed_search_next_history doesn't have default binding
@line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(5)
- assert_line('12345')
+ assert_line_around_cursor('', '12345')
@line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(5)
- assert_line('12aaa')
+ assert_line_around_cursor('', '12aaa')
@line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(5)
- assert_line('12356')
+ assert_line_around_cursor('', '12356')
@line_editor.__send__(:ed_search_next_history, "\C-n".ord)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(5)
- assert_line('12aaa')
+ assert_line_around_cursor('', '12aaa')
@line_editor.__send__(:ed_search_next_history, "\C-n".ord)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(5)
- assert_line('12345')
+ assert_line_around_cursor('', '12345')
@line_editor.__send__(:ed_search_next_history, "\C-n".ord)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
- assert_line('')
+ assert_line_around_cursor('', '')
+ end
+
+ def test_incremental_search_history_cancel_by_symbol_key
+ # ed_prev_char should move cursor left and cancel incremental search
+ input_keys("abc\C-r")
+ input_key_by_symbol(:ed_prev_char)
+ input_keys('d')
+ assert_line_around_cursor('abd', 'c')
end
# Unicode emoji test
@@ -2170,97 +1318,49 @@ class Reline::KeyActor::Emacs::Test < Reline::TestCase
omit "This test is for UTF-8 but the locale is #{Reline.core.encoding}" if Reline.core.encoding != Encoding::UTF_8
# U+1F468 U+200D U+1F469 U+200D U+1F467 U+200D U+1F466 is family: man, woman, girl, boy "👨‍👩‍👧‍👦"
input_keys("\u{1F468}") # U+1F468 is man "👨"
- assert_line("\u{1F468}")
- assert_byte_pointer_size("\u{1F468}")
- assert_cursor(2)
- assert_cursor_max(2)
+ assert_line_around_cursor('👨', '')
input_keys("\u200D") # U+200D is ZERO WIDTH JOINER
- assert_line("\u{1F468 200D}")
- assert_byte_pointer_size("\u{1F468 200D}")
- assert_cursor(2)
- assert_cursor_max(2)
+ assert_line_around_cursor('👨‍', '')
input_keys("\u{1F469}") # U+1F469 is woman "👩"
- assert_line("\u{1F468 200D 1F469}")
- assert_byte_pointer_size("\u{1F468 200D 1F469}")
- assert_cursor(2)
- assert_cursor_max(2)
+ assert_line_around_cursor('👨‍👩', '')
input_keys("\u200D") # U+200D is ZERO WIDTH JOINER
- assert_line("\u{1F468 200D 1F469 200D}")
- assert_byte_pointer_size("\u{1F468 200D 1F469 200D}")
- assert_cursor(2)
- assert_cursor_max(2)
+ assert_line_around_cursor('👨‍👩‍', '')
input_keys("\u{1F467}") # U+1F467 is girl "👧"
- assert_line("\u{1F468 200D 1F469 200D 1F467}")
- assert_byte_pointer_size("\u{1F468 200D 1F469 200D 1F467}")
- assert_cursor(2)
- assert_cursor_max(2)
+ assert_line_around_cursor('👨‍👩‍👧', '')
input_keys("\u200D") # U+200D is ZERO WIDTH JOINER
- assert_line("\u{1F468 200D 1F469 200D 1F467 200D}")
- assert_byte_pointer_size("\u{1F468 200D 1F469 200D 1F467 200D}")
- assert_cursor(2)
- assert_cursor_max(2)
+ assert_line_around_cursor('👨‍👩‍👧‍', '')
input_keys("\u{1F466}") # U+1F466 is boy "👦"
- assert_line("\u{1F468 200D 1F469 200D 1F467 200D 1F466}")
- assert_byte_pointer_size("\u{1F468 200D 1F469 200D 1F467 200D 1F466}")
- assert_cursor(2)
- assert_cursor_max(2)
+ assert_line_around_cursor('👨‍👩‍👧‍👦', '')
# U+1F468 U+200D U+1F469 U+200D U+1F467 U+200D U+1F466 is family: man, woman, girl, boy "👨‍👩‍👧‍👦"
input_keys("\u{1F468 200D 1F469 200D 1F467 200D 1F466}")
- assert_line("\u{1F468 200D 1F469 200D 1F467 200D 1F466 1F468 200D 1F469 200D 1F467 200D 1F466}")
- assert_byte_pointer_size("\u{1F468 200D 1F469 200D 1F467 200D 1F466 1F468 200D 1F469 200D 1F467 200D 1F466}")
- assert_cursor(4)
- assert_cursor_max(4)
+ assert_line_around_cursor('👨‍👩‍👧‍👦👨‍👩‍👧‍👦', '')
end
def test_ed_insert_for_include_valiation_selector
omit "This test is for UTF-8 but the locale is #{Reline.core.encoding}" if Reline.core.encoding != Encoding::UTF_8
# U+0030 U+FE00 is DIGIT ZERO + VARIATION SELECTOR-1 "0︀"
input_keys("\u0030") # U+0030 is DIGIT ZERO
- assert_line("\u0030")
- assert_byte_pointer_size("\u0030")
- assert_cursor(1)
- assert_cursor_max(1)
+ assert_line_around_cursor('0', '')
input_keys("\uFE00") # U+FE00 is VARIATION SELECTOR-1
- assert_line("\u{0030 FE00}")
- assert_byte_pointer_size("\u{0030 FE00}")
- assert_cursor(1)
- assert_cursor_max(1)
+ assert_line_around_cursor('0︀', '')
end
def test_em_yank_pop
input_keys("def hoge\C-w\C-b\C-f\C-w", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
- assert_line('')
+ assert_line_around_cursor('', '')
input_keys("\C-y", false)
- assert_byte_pointer_size('def ')
- assert_cursor(4)
- assert_cursor_max(4)
- assert_line('def ')
+ assert_line_around_cursor('def ', '')
input_keys("\M-\C-y", false)
- assert_byte_pointer_size('hoge')
- assert_cursor(4)
- assert_cursor_max(4)
- assert_line('hoge')
+ assert_line_around_cursor('hoge', '')
end
def test_em_kill_region_with_kill_ring
input_keys("def hoge\C-b\C-b\C-b\C-b", false)
- assert_byte_pointer_size('def ')
- assert_cursor(4)
- assert_cursor_max(8)
- assert_line('def hoge')
+ assert_line_around_cursor('def ', 'hoge')
input_keys("\C-k\C-w", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
- assert_line('')
+ assert_line_around_cursor('', '')
input_keys("\C-y", false)
- assert_byte_pointer_size('def hoge')
- assert_cursor(8)
- assert_cursor_max(8)
- assert_line('def hoge')
+ assert_line_around_cursor('def hoge', '')
end
def test_ed_search_prev_next_history_in_multibyte
@@ -2276,104 +1376,133 @@ class Reline::KeyActor::Emacs::Test < Reline::TestCase
assert_whole_lines(['def foo', ' 12345', 'end'])
assert_line_index(1)
assert_whole_lines(['def foo', ' 12345', 'end'])
- assert_byte_pointer_size(' 123')
- assert_cursor(5)
- assert_cursor_max(7)
- assert_line(' 12345')
+ assert_line_around_cursor(' 123', '45')
@line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
assert_line_index(2)
assert_whole_lines(['def hoge', ' 67890', ' 12345', 'end'])
- assert_byte_pointer_size(' 123')
- assert_cursor(5)
- assert_cursor_max(7)
- assert_line(' 12345')
+ assert_line_around_cursor(' 123', '45')
@line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
assert_line_index(2)
assert_whole_lines(['def hoge', ' 67890', ' 12345', 'end'])
- assert_byte_pointer_size(' 123')
- assert_cursor(5)
- assert_cursor_max(7)
- assert_line(' 12345')
+ assert_line_around_cursor(' 123', '45')
@line_editor.__send__(:ed_search_next_history, "\C-n".ord)
assert_line_index(1)
assert_whole_lines(['def foo', ' 12345', 'end'])
- assert_byte_pointer_size(' 123')
- assert_cursor(5)
- assert_cursor_max(7)
- assert_line(' 12345')
+ assert_line_around_cursor(' 123', '45')
@line_editor.__send__(:ed_search_next_history, "\C-n".ord)
assert_line_index(1)
assert_whole_lines(['def foo', ' 12345', 'end'])
- assert_byte_pointer_size(' 123')
- assert_cursor(5)
- assert_cursor_max(7)
- assert_line(' 12345')
+ assert_line_around_cursor(' 123', '45')
end
def test_ignore_NUL_by_ed_quoted_insert
input_keys(%Q{"\C-v\C-@"}, false)
- assert_byte_pointer_size('""')
- assert_cursor(2)
- assert_cursor_max(2)
+ assert_line_around_cursor('""', '')
end
def test_ed_argument_digit_by_meta_num
input_keys('abcdef')
- assert_byte_pointer_size('abcdef')
- assert_cursor(6)
- assert_cursor_max(6)
- assert_line('abcdef')
+ assert_line_around_cursor('abcdef', '')
input_keys("\M-2", false)
input_keys("\C-h", false)
- assert_byte_pointer_size('abcd')
- assert_cursor(4)
- assert_cursor_max(4)
- assert_line('abcd')
+ assert_line_around_cursor('abcd', '')
end
def test_halfwidth_kana_width_dakuten
input_raw_keys('ガギゲゴ')
- assert_byte_pointer_size('ガギゲゴ')
- assert_cursor(8)
- assert_cursor_max(8)
+ assert_line_around_cursor('ガギゲゴ', '')
input_keys("\C-b\C-b", false)
- assert_byte_pointer_size('ガギ')
- assert_cursor(4)
- assert_cursor_max(8)
+ assert_line_around_cursor('ガギ', 'ゲゴ')
input_raw_keys('グ', false)
- assert_byte_pointer_size('ガギグ')
- assert_cursor(6)
- assert_cursor_max(10)
- assert_line('ガギグゲゴ')
+ assert_line_around_cursor('ガギグ', 'ゲゴ')
end
def test_input_unknown_char
input_keys('͸') # U+0378 (unassigned)
- assert_line('͸')
- assert_byte_pointer_size('͸')
- assert_cursor(1)
- assert_cursor_max(1)
+ assert_line_around_cursor('͸', '')
end
def test_unix_line_discard
input_keys("\C-u", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
- assert_line('')
+ assert_line_around_cursor('', '')
input_keys('abc')
- assert_byte_pointer_size('abc')
- assert_cursor(3)
- assert_cursor_max(3)
+ assert_line_around_cursor('abc', '')
input_keys("\C-b\C-u", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(1)
- assert_line('c')
+ assert_line_around_cursor('', 'c')
input_keys("\C-f\C-u", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
- assert_line('')
+ assert_line_around_cursor('', '')
+ end
+
+ def test_vi_editing_mode
+ @line_editor.__send__(:vi_editing_mode, nil)
+ assert(@config.editing_mode_is?(:vi_insert))
+ end
+
+ def test_undo
+ input_keys("\C-_", false)
+ assert_line_around_cursor('', '')
+ input_keys("aあb\C-h\C-h\C-h", false)
+ assert_line_around_cursor('', '')
+ input_keys("\C-_", false)
+ assert_line_around_cursor('a', '')
+ input_keys("\C-_", false)
+ assert_line_around_cursor('aあ', '')
+ input_keys("\C-_", false)
+ assert_line_around_cursor('aあb', '')
+ input_keys("\C-_", false)
+ assert_line_around_cursor('aあ', '')
+ input_keys("\C-_", false)
+ assert_line_around_cursor('a', '')
+ input_keys("\C-_", false)
+ assert_line_around_cursor('', '')
+ end
+
+ def test_undo_with_cursor_position
+ input_keys("abc\C-b\C-h", false)
+ assert_line_around_cursor('a', 'c')
+ input_keys("\C-_", false)
+ assert_line_around_cursor('ab', 'c')
+ input_keys("あいう\C-b\C-h", false)
+ assert_line_around_cursor('abあ', 'うc')
+ input_keys("\C-_", false)
+ assert_line_around_cursor('abあい', 'うc')
+ end
+
+ def test_undo_with_multiline
+ @line_editor.multiline_on
+ @line_editor.confirm_multiline_termination_proc = proc {}
+ input_keys("1\n2\n3", false)
+ assert_whole_lines(["1", "2", "3"])
+ assert_line_index(2)
+ assert_line_around_cursor('3', '')
+ input_keys("\C-p\C-h\C-h", false)
+ assert_whole_lines(["1", "3"])
+ assert_line_index(0)
+ assert_line_around_cursor('1', '')
+ input_keys("\C-_", false)
+ assert_whole_lines(["1", "", "3"])
+ assert_line_index(1)
+ assert_line_around_cursor('', '')
+ input_keys("\C-_", false)
+ assert_whole_lines(["1", "2", "3"])
+ assert_line_index(1)
+ assert_line_around_cursor('2', '')
+ input_keys("\C-_", false)
+ assert_whole_lines(["1", "2", ""])
+ assert_line_index(2)
+ assert_line_around_cursor('', '')
+ input_keys("\C-_", false)
+ assert_whole_lines(["1", "2"])
+ assert_line_index(1)
+ assert_line_around_cursor('2', '')
+ end
+
+ def test_undo_with_many_times
+ str = "a" + "b" * 100
+ input_keys(str, false)
+ 100.times { input_keys("\C-_", false) }
+ assert_line_around_cursor('a', '')
+ input_keys("\C-_", false)
+ assert_line_around_cursor('a', '')
end
end
diff --git a/test/reline/test_key_actor_vi.rb b/test/reline/test_key_actor_vi.rb
index 20630e5809..4deae2dd83 100644
--- a/test/reline/test_key_actor_vi.rb
+++ b/test/reline/test_key_actor_vi.rb
@@ -25,950 +25,513 @@ class Reline::KeyActor::ViInsert::Test < Reline::TestCase
def test_vi_command_mode_with_input
input_keys("abc\C-[")
assert_instance_of(Reline::KeyActor::ViCommand, @config.editing_mode)
- assert_line('abc')
+ assert_line_around_cursor('ab', 'c')
end
def test_vi_insert
assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode)
input_keys('i')
- assert_line('i')
- assert_cursor(1)
+ assert_line_around_cursor('i', '')
assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode)
input_keys("\C-[")
- assert_line('i')
- assert_cursor(0)
+ assert_line_around_cursor('', 'i')
assert_instance_of(Reline::KeyActor::ViCommand, @config.editing_mode)
input_keys('i')
- assert_line('i')
- assert_cursor(0)
+ assert_line_around_cursor('', 'i')
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_line_around_cursor('a', '')
assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode)
input_keys("\C-[")
- assert_line('a')
- assert_cursor(0)
+ assert_line_around_cursor('', 'a')
assert_instance_of(Reline::KeyActor::ViCommand, @config.editing_mode)
input_keys('a')
- assert_line('a')
- assert_cursor(1)
+ assert_line_around_cursor('a', '')
assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode)
end
def test_vi_insert_at_bol
input_keys('I')
- assert_line('I')
+ assert_line_around_cursor('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_line_around_cursor('I12', '345')
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_line_around_cursor('', 'I12345')
assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode)
end
def test_vi_add_at_eol
input_keys('A')
- assert_line('A')
+ assert_line_around_cursor('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_line_around_cursor('A12', '345')
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_line_around_cursor('A12345', '')
assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode)
end
def test_ed_insert_one
input_keys('a')
- assert_line('a')
- assert_byte_pointer_size('a')
- assert_cursor(1)
- assert_cursor_max(1)
+ assert_line_around_cursor('a', '')
end
def test_ed_insert_two
input_keys('ab')
- assert_line('ab')
- assert_byte_pointer_size('ab')
- assert_cursor(2)
- assert_cursor_max(2)
+ assert_line_around_cursor('ab', '')
end
def test_ed_insert_mbchar_one
input_keys('か')
- assert_line('か')
- assert_byte_pointer_size('か')
- assert_cursor(2)
- assert_cursor_max(2)
+ assert_line_around_cursor('か', '')
end
def test_ed_insert_mbchar_two
input_keys('かき')
- assert_line('かき')
- assert_byte_pointer_size('かき')
- assert_cursor(4)
- assert_cursor_max(4)
+ assert_line_around_cursor('かき', '')
end
def test_ed_insert_for_mbchar_by_plural_code_points
input_keys("か\u3099")
- assert_line("か\u3099")
- assert_byte_pointer_size("か\u3099")
- assert_cursor(2)
- assert_cursor_max(2)
+ assert_line_around_cursor("か\u3099", '')
end
def test_ed_insert_for_plural_mbchar_by_plural_code_points
input_keys("か\u3099き\u3099")
- assert_line("か\u3099き\u3099")
- assert_byte_pointer_size("か\u3099き\u3099")
- assert_cursor(4)
- assert_cursor_max(4)
+ assert_line_around_cursor("か\u3099き\u3099", '')
end
def test_ed_next_char
input_keys("abcdef\C-[0")
- assert_line('abcdef')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(6)
+ assert_line_around_cursor('', 'abcdef')
input_keys('l')
- assert_line('abcdef')
- assert_byte_pointer_size('a')
- assert_cursor(1)
- assert_cursor_max(6)
+ assert_line_around_cursor('a', 'bcdef')
input_keys('2l')
- assert_line('abcdef')
- assert_byte_pointer_size('abc')
- assert_cursor(3)
- assert_cursor_max(6)
+ assert_line_around_cursor('abc', 'def')
end
def test_ed_prev_char
input_keys("abcdef\C-[")
- assert_line('abcdef')
- assert_byte_pointer_size('abcde')
- assert_cursor(5)
- assert_cursor_max(6)
+ assert_line_around_cursor('abcde', 'f')
input_keys('h')
- assert_line('abcdef')
- assert_byte_pointer_size('abcd')
- assert_cursor(4)
- assert_cursor_max(6)
+ assert_line_around_cursor('abcd', 'ef')
input_keys('2h')
- assert_line('abcdef')
- assert_byte_pointer_size('ab')
- assert_cursor(2)
- assert_cursor_max(6)
+ assert_line_around_cursor('ab', 'cdef')
end
def test_history
Reline::HISTORY.concat(%w{abc 123 AAA})
input_keys("\C-[")
- assert_line('')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
+ assert_line_around_cursor('', '')
input_keys('k')
- assert_line('AAA')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(3)
+ assert_line_around_cursor('', 'AAA')
input_keys('2k')
- assert_line('abc')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(3)
+ assert_line_around_cursor('', 'abc')
input_keys('j')
- assert_line('123')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(3)
+ assert_line_around_cursor('', '123')
input_keys('2j')
- assert_line('')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
+ assert_line_around_cursor('', '')
end
def test_vi_paste_prev
input_keys("abcde\C-[3h")
- assert_line('abcde')
- assert_byte_pointer_size('a')
- assert_cursor(1)
- assert_cursor_max(5)
+ assert_line_around_cursor('a', 'bcde')
input_keys('P')
- assert_line('abcde')
- assert_byte_pointer_size('a')
- assert_cursor(1)
- assert_cursor_max(5)
+ assert_line_around_cursor('a', 'bcde')
input_keys('d$')
- assert_line('a')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(1)
+ assert_line_around_cursor('', 'a')
input_keys('P')
- assert_line('bcdea')
- assert_byte_pointer_size('bcd')
- assert_cursor(3)
- assert_cursor_max(5)
+ assert_line_around_cursor('bcd', 'ea')
input_keys('2P')
- assert_line('bcdbcdbcdeeea')
- assert_byte_pointer_size('bcdbcdbcd')
- assert_cursor(9)
- assert_cursor_max(13)
+ assert_line_around_cursor('bcdbcdbcd', 'eeea')
end
def test_vi_paste_next
input_keys("abcde\C-[3h")
- assert_line('abcde')
- assert_byte_pointer_size('a')
- assert_cursor(1)
- assert_cursor_max(5)
+ assert_line_around_cursor('a', 'bcde')
input_keys('p')
- assert_line('abcde')
- assert_byte_pointer_size('a')
- assert_cursor(1)
- assert_cursor_max(5)
+ assert_line_around_cursor('a', 'bcde')
input_keys('d$')
- assert_line('a')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(1)
+ assert_line_around_cursor('', 'a')
input_keys('p')
- assert_line('abcde')
- assert_byte_pointer_size('abcd')
- assert_cursor(4)
- assert_cursor_max(5)
+ assert_line_around_cursor('abcd', 'e')
input_keys('2p')
- assert_line('abcdebcdebcde')
- assert_byte_pointer_size('abcdebcdebcd')
- assert_cursor(12)
- assert_cursor_max(13)
+ assert_line_around_cursor('abcdebcdebcd', 'e')
end
def test_vi_paste_prev_for_mbchar
input_keys("あいうえお\C-[3h")
- assert_line('あいうえお')
- assert_byte_pointer_size('あ')
- assert_cursor(2)
- assert_cursor_max(10)
+ assert_line_around_cursor('あ', 'いうえお')
input_keys('P')
- assert_line('あいうえお')
- assert_byte_pointer_size('あ')
- assert_cursor(2)
- assert_cursor_max(10)
+ assert_line_around_cursor('あ', 'いうえお')
input_keys('d$')
- assert_line('あ')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(2)
+ assert_line_around_cursor('', 'あ')
input_keys('P')
- assert_line('いうえおあ')
- assert_byte_pointer_size('いうえ')
- assert_cursor(6)
- assert_cursor_max(10)
+ assert_line_around_cursor('いうえ', 'おあ')
input_keys('2P')
- assert_line('いうえいうえいうえおおおあ')
- assert_byte_pointer_size('いうえいうえいうえ')
- assert_cursor(18)
- assert_cursor_max(26)
+ assert_line_around_cursor('いうえいうえいうえ', 'おおおあ')
end
def test_vi_paste_next_for_mbchar
input_keys("あいうえお\C-[3h")
- assert_line('あいうえお')
- assert_byte_pointer_size('あ')
- assert_cursor(2)
- assert_cursor_max(10)
+ assert_line_around_cursor('あ', 'いうえお')
input_keys('p')
- assert_line('あいうえお')
- assert_byte_pointer_size('あ')
- assert_cursor(2)
- assert_cursor_max(10)
+ assert_line_around_cursor('あ', 'いうえお')
input_keys('d$')
- assert_line('あ')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(2)
+ assert_line_around_cursor('', 'あ')
input_keys('p')
- assert_line('あいうえお')
- assert_byte_pointer_size('あいうえ')
- assert_cursor(8)
- assert_cursor_max(10)
+ assert_line_around_cursor('あいうえ', 'お')
input_keys('2p')
- assert_line('あいうえおいうえおいうえお')
- assert_byte_pointer_size('あいうえおいうえおいうえ')
- assert_cursor(24)
- assert_cursor_max(26)
+ assert_line_around_cursor('あいうえおいうえおいうえ', 'お')
end
def test_vi_paste_prev_for_mbchar_by_plural_code_points
input_keys("か\u3099き\u3099く\u3099け\u3099こ\u3099\C-[3h")
- assert_line("か\u3099き\u3099く\u3099け\u3099こ\u3099")
- assert_byte_pointer_size("か\u3099")
- assert_cursor(2)
- assert_cursor_max(10)
+ assert_line_around_cursor("か\u3099", "き\u3099く\u3099け\u3099こ\u3099")
input_keys('P')
- assert_line("か\u3099き\u3099く\u3099け\u3099こ\u3099")
- assert_byte_pointer_size("か\u3099")
- assert_cursor(2)
- assert_cursor_max(10)
+ assert_line_around_cursor("か\u3099", "き\u3099く\u3099け\u3099こ\u3099")
input_keys('d$')
- assert_line("か\u3099")
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(2)
+ assert_line_around_cursor('', "か\u3099")
input_keys('P')
- assert_line("き\u3099く\u3099け\u3099こ\u3099か\u3099")
- assert_byte_pointer_size("き\u3099く\u3099け\u3099")
- assert_cursor(6)
- assert_cursor_max(10)
+ assert_line_around_cursor("き\u3099く\u3099け\u3099", "こ\u3099か\u3099")
input_keys('2P')
- assert_line("き\u3099く\u3099け\u3099き\u3099く\u3099け\u3099き\u3099く\u3099け\u3099こ\u3099こ\u3099こ\u3099か\u3099")
- assert_byte_pointer_size("き\u3099く\u3099け\u3099き\u3099く\u3099け\u3099き\u3099く\u3099け\u3099")
- assert_cursor(18)
- assert_cursor_max(26)
+ assert_line_around_cursor("き\u3099く\u3099け\u3099き\u3099く\u3099け\u3099き\u3099く\u3099け\u3099", "こ\u3099こ\u3099こ\u3099か\u3099")
end
def test_vi_paste_next_for_mbchar_by_plural_code_points
input_keys("か\u3099き\u3099く\u3099け\u3099こ\u3099\C-[3h")
- assert_line("か\u3099き\u3099く\u3099け\u3099こ\u3099")
- assert_byte_pointer_size("か\u3099")
- assert_cursor(2)
- assert_cursor_max(10)
+ assert_line_around_cursor("か\u3099", "き\u3099く\u3099け\u3099こ\u3099")
input_keys('p')
- assert_line("か\u3099き\u3099く\u3099け\u3099こ\u3099")
- assert_byte_pointer_size("か\u3099")
- assert_cursor(2)
- assert_cursor_max(10)
+ assert_line_around_cursor("か\u3099", "き\u3099く\u3099け\u3099こ\u3099")
input_keys('d$')
- assert_line("か\u3099")
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(2)
+ assert_line_around_cursor('', "か\u3099")
input_keys('p')
- assert_line("か\u3099き\u3099く\u3099け\u3099こ\u3099")
- assert_byte_pointer_size("か\u3099き\u3099く\u3099け\u3099")
- assert_cursor(8)
- assert_cursor_max(10)
+ assert_line_around_cursor("か\u3099き\u3099く\u3099け\u3099", "こ\u3099")
input_keys('2p')
- assert_line("か\u3099き\u3099く\u3099け\u3099こ\u3099き\u3099く\u3099け\u3099こ\u3099き\u3099く\u3099け\u3099こ\u3099")
- assert_byte_pointer_size("か\u3099き\u3099く\u3099け\u3099こ\u3099き\u3099く\u3099け\u3099こ\u3099き\u3099く\u3099け\u3099")
- assert_cursor(24)
- assert_cursor_max(26)
+ assert_line_around_cursor("か\u3099き\u3099く\u3099け\u3099こ\u3099き\u3099く\u3099け\u3099こ\u3099き\u3099く\u3099け\u3099", "こ\u3099")
end
def test_vi_prev_next_word
input_keys("aaa b{b}b ccc\C-[0")
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(13)
+ assert_line_around_cursor('', 'aaa b{b}b ccc')
input_keys('w')
- assert_byte_pointer_size('aaa ')
- assert_cursor(4)
- assert_cursor_max(13)
+ assert_line_around_cursor('aaa ', 'b{b}b ccc')
input_keys('w')
- assert_byte_pointer_size('aaa b')
- assert_cursor(5)
- assert_cursor_max(13)
+ assert_line_around_cursor('aaa b', '{b}b ccc')
input_keys('w')
- assert_byte_pointer_size('aaa b{')
- assert_cursor(6)
- assert_cursor_max(13)
+ assert_line_around_cursor('aaa b{', 'b}b ccc')
input_keys('w')
- assert_byte_pointer_size('aaa b{b')
- assert_cursor(7)
- assert_cursor_max(13)
+ assert_line_around_cursor('aaa b{b', '}b ccc')
input_keys('w')
- assert_byte_pointer_size('aaa b{b}')
- assert_cursor(8)
- assert_cursor_max(13)
+ assert_line_around_cursor('aaa b{b}', 'b ccc')
input_keys('w')
- assert_byte_pointer_size('aaa b{b}b ')
- assert_cursor(10)
- assert_cursor_max(13)
+ assert_line_around_cursor('aaa b{b}b ', 'ccc')
input_keys('w')
- assert_byte_pointer_size('aaa b{b}b cc')
- assert_cursor(12)
- assert_cursor_max(13)
+ assert_line_around_cursor('aaa b{b}b cc', 'c')
input_keys('b')
- assert_byte_pointer_size('aaa b{b}b ')
- assert_cursor(10)
- assert_cursor_max(13)
+ assert_line_around_cursor('aaa b{b}b ', 'ccc')
input_keys('b')
- assert_byte_pointer_size('aaa b{b}')
- assert_cursor(8)
- assert_cursor_max(13)
+ assert_line_around_cursor('aaa b{b}', 'b ccc')
input_keys('b')
- assert_byte_pointer_size('aaa b{b')
- assert_cursor(7)
- assert_cursor_max(13)
+ assert_line_around_cursor('aaa b{b', '}b ccc')
input_keys('b')
- assert_byte_pointer_size('aaa b{')
- assert_cursor(6)
- assert_cursor_max(13)
+ assert_line_around_cursor('aaa b{', 'b}b ccc')
input_keys('b')
- assert_byte_pointer_size('aaa b')
- assert_cursor(5)
- assert_cursor_max(13)
+ assert_line_around_cursor('aaa b', '{b}b ccc')
input_keys('b')
- assert_byte_pointer_size('aaa ')
- assert_cursor(4)
- assert_cursor_max(13)
+ assert_line_around_cursor('aaa ', 'b{b}b ccc')
input_keys('b')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(13)
+ assert_line_around_cursor('', 'aaa b{b}b ccc')
input_keys('3w')
- assert_byte_pointer_size('aaa b{')
- assert_cursor(6)
- assert_cursor_max(13)
+ assert_line_around_cursor('aaa b{', 'b}b ccc')
input_keys('3w')
- assert_byte_pointer_size('aaa b{b}b ')
- assert_cursor(10)
- assert_cursor_max(13)
+ assert_line_around_cursor('aaa b{b}b ', 'ccc')
input_keys('3w')
- assert_byte_pointer_size('aaa b{b}b cc')
- assert_cursor(12)
- assert_cursor_max(13)
+ assert_line_around_cursor('aaa b{b}b cc', 'c')
input_keys('3b')
- assert_byte_pointer_size('aaa b{b')
- assert_cursor(7)
- assert_cursor_max(13)
+ assert_line_around_cursor('aaa b{b', '}b ccc')
input_keys('3b')
- assert_byte_pointer_size('aaa ')
- assert_cursor(4)
- assert_cursor_max(13)
+ assert_line_around_cursor('aaa ', 'b{b}b ccc')
input_keys('3b')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(13)
+ assert_line_around_cursor('', 'aaa b{b}b ccc')
end
def test_vi_end_word
input_keys("aaa b{b}}}b ccc\C-[0")
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(19)
+ assert_line_around_cursor('', 'aaa b{b}}}b ccc')
input_keys('e')
- assert_byte_pointer_size('aa')
- assert_cursor(2)
- assert_cursor_max(19)
+ assert_line_around_cursor('aa', 'a b{b}}}b ccc')
input_keys('e')
- assert_byte_pointer_size('aaa ')
- assert_cursor(6)
- assert_cursor_max(19)
+ assert_line_around_cursor('aaa ', 'b{b}}}b ccc')
input_keys('e')
- assert_byte_pointer_size('aaa b')
- assert_cursor(7)
- assert_cursor_max(19)
+ assert_line_around_cursor('aaa b', '{b}}}b ccc')
input_keys('e')
- assert_byte_pointer_size('aaa b{')
- assert_cursor(8)
- assert_cursor_max(19)
+ assert_line_around_cursor('aaa b{', 'b}}}b ccc')
input_keys('e')
- assert_byte_pointer_size('aaa b{b}}')
- assert_cursor(11)
- assert_cursor_max(19)
+ assert_line_around_cursor('aaa b{b}}', '}b ccc')
input_keys('e')
- assert_byte_pointer_size('aaa b{b}}}')
- assert_cursor(12)
- assert_cursor_max(19)
+ assert_line_around_cursor('aaa b{b}}}', 'b ccc')
input_keys('e')
- assert_byte_pointer_size('aaa b{b}}}b cc')
- assert_cursor(18)
- assert_cursor_max(19)
+ assert_line_around_cursor('aaa b{b}}}b cc', 'c')
input_keys('e')
- assert_byte_pointer_size('aaa b{b}}}b cc')
- assert_cursor(18)
- assert_cursor_max(19)
+ assert_line_around_cursor('aaa b{b}}}b cc', 'c')
input_keys('03e')
- assert_byte_pointer_size('aaa b')
- assert_cursor(7)
- assert_cursor_max(19)
+ assert_line_around_cursor('aaa b', '{b}}}b ccc')
input_keys('3e')
- assert_byte_pointer_size('aaa b{b}}}')
- assert_cursor(12)
- assert_cursor_max(19)
+ assert_line_around_cursor('aaa b{b}}}', 'b ccc')
input_keys('3e')
- assert_byte_pointer_size('aaa b{b}}}b cc')
- assert_cursor(18)
- assert_cursor_max(19)
+ assert_line_around_cursor('aaa b{b}}}b cc', 'c')
end
def test_vi_prev_next_big_word
input_keys("aaa b{b}b ccc\C-[0")
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(13)
+ assert_line_around_cursor('', 'aaa b{b}b ccc')
input_keys('W')
- assert_byte_pointer_size('aaa ')
- assert_cursor(4)
- assert_cursor_max(13)
+ assert_line_around_cursor('aaa ', 'b{b}b ccc')
input_keys('W')
- assert_byte_pointer_size('aaa b{b}b ')
- assert_cursor(10)
- assert_cursor_max(13)
+ assert_line_around_cursor('aaa b{b}b ', 'ccc')
input_keys('W')
- assert_byte_pointer_size('aaa b{b}b cc')
- assert_cursor(12)
- assert_cursor_max(13)
+ assert_line_around_cursor('aaa b{b}b cc', 'c')
input_keys('B')
- assert_byte_pointer_size('aaa b{b}b ')
- assert_cursor(10)
- assert_cursor_max(13)
+ assert_line_around_cursor('aaa b{b}b ', 'ccc')
input_keys('B')
- assert_byte_pointer_size('aaa ')
- assert_cursor(4)
- assert_cursor_max(13)
+ assert_line_around_cursor('aaa ', 'b{b}b ccc')
input_keys('B')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(13)
+ assert_line_around_cursor('', 'aaa b{b}b ccc')
input_keys('2W')
- assert_byte_pointer_size('aaa b{b}b ')
- assert_cursor(10)
- assert_cursor_max(13)
+ assert_line_around_cursor('aaa b{b}b ', 'ccc')
input_keys('2W')
- assert_byte_pointer_size('aaa b{b}b cc')
- assert_cursor(12)
- assert_cursor_max(13)
+ assert_line_around_cursor('aaa b{b}b cc', 'c')
input_keys('2B')
- assert_byte_pointer_size('aaa ')
- assert_cursor(4)
- assert_cursor_max(13)
+ assert_line_around_cursor('aaa ', 'b{b}b ccc')
input_keys('2B')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(13)
+ assert_line_around_cursor('', 'aaa b{b}b ccc')
end
def test_vi_end_big_word
input_keys("aaa b{b}}}b ccc\C-[0")
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(19)
+ assert_line_around_cursor('', 'aaa b{b}}}b ccc')
input_keys('E')
- assert_byte_pointer_size('aa')
- assert_cursor(2)
- assert_cursor_max(19)
+ assert_line_around_cursor('aa', 'a b{b}}}b ccc')
input_keys('E')
- assert_byte_pointer_size('aaa b{b}}}')
- assert_cursor(12)
- assert_cursor_max(19)
+ assert_line_around_cursor('aaa b{b}}}', 'b ccc')
input_keys('E')
- assert_byte_pointer_size('aaa b{b}}}b cc')
- assert_cursor(18)
- assert_cursor_max(19)
+ assert_line_around_cursor('aaa b{b}}}b cc', 'c')
input_keys('E')
- assert_byte_pointer_size('aaa b{b}}}b cc')
- assert_cursor(18)
- assert_cursor_max(19)
+ assert_line_around_cursor('aaa b{b}}}b cc', 'c')
end
def test_ed_quoted_insert
input_keys("ab\C-v\C-acd")
- assert_line("ab\C-acd")
- assert_byte_pointer_size("ab\C-acd")
- assert_cursor(6)
- assert_cursor_max(6)
+ assert_line_around_cursor("ab\C-acd", '')
end
def test_ed_quoted_insert_with_vi_arg
input_keys("ab\C-[3\C-v\C-aacd")
- assert_line("a\C-a\C-a\C-abcd")
- assert_byte_pointer_size("a\C-a\C-a\C-abcd")
- assert_cursor(10)
- assert_cursor_max(10)
+ assert_line_around_cursor("a\C-a\C-a\C-abcd", '')
end
def test_vi_replace_char
input_keys("abcdef\C-[03l")
- assert_line('abcdef')
- assert_byte_pointer_size('abc')
- assert_cursor(3)
- assert_cursor_max(6)
+ assert_line_around_cursor('abc', 'def')
input_keys('rz')
- assert_line('abczef')
- assert_byte_pointer_size('abc')
- assert_cursor(3)
- assert_cursor_max(6)
+ assert_line_around_cursor('abc', 'zef')
input_keys('2rx')
- assert_line('abcxxf')
- assert_byte_pointer_size('abcxx')
- assert_cursor(5)
- assert_cursor_max(6)
+ assert_line_around_cursor('abcxx', 'f')
end
def test_vi_replace_char_with_mbchar
input_keys("あいうえお\C-[0l")
- assert_line('あいうえお')
- assert_byte_pointer_size('あ')
- assert_cursor(2)
- assert_cursor_max(10)
+ assert_line_around_cursor('あ', 'いうえお')
input_keys('rx')
- assert_line('あxうえお')
- assert_byte_pointer_size('あ')
- assert_cursor(2)
- assert_cursor_max(9)
+ assert_line_around_cursor('あ', 'xうえお')
input_keys('l2ry')
- assert_line('あxyyお')
- assert_byte_pointer_size('あxyy')
- assert_cursor(5)
- assert_cursor_max(7)
+ assert_line_around_cursor('あxyy', 'お')
end
def test_vi_next_char
input_keys("abcdef\C-[0")
- assert_line('abcdef')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(6)
+ assert_line_around_cursor('', 'abcdef')
input_keys('fz')
- assert_line('abcdef')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(6)
+ assert_line_around_cursor('', 'abcdef')
input_keys('fe')
- assert_line('abcdef')
- assert_byte_pointer_size('abcd')
- assert_cursor(4)
- assert_cursor_max(6)
+ assert_line_around_cursor('abcd', 'ef')
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)
+ assert_line_around_cursor('', 'abcdef')
input_keys('tz')
- assert_line('abcdef')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(6)
+ assert_line_around_cursor('', 'abcdef')
input_keys('te')
- assert_line('abcdef')
- assert_byte_pointer_size('abc')
- assert_cursor(3)
- assert_cursor_max(6)
+ assert_line_around_cursor('abc', 'def')
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)
+ assert_line_around_cursor('abcde', 'f')
input_keys('Fz')
- assert_line('abcdef')
- assert_byte_pointer_size('abcde')
- assert_cursor(5)
- assert_cursor_max(6)
+ assert_line_around_cursor('abcde', 'f')
input_keys('Fa')
- assert_line('abcdef')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(6)
+ assert_line_around_cursor('', 'abcdef')
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)
+ assert_line_around_cursor('abcde', 'f')
input_keys('Tz')
- assert_line('abcdef')
- assert_byte_pointer_size('abcde')
- assert_cursor(5)
- assert_cursor_max(6)
+ assert_line_around_cursor('abcde', 'f')
input_keys('Ta')
- assert_line('abcdef')
- assert_byte_pointer_size('a')
- assert_cursor(1)
- assert_cursor_max(6)
+ assert_line_around_cursor('a', 'bcdef')
end
def test_vi_delete_next_char
input_keys("abc\C-[h")
- assert_byte_pointer_size('a')
- assert_cursor(1)
- assert_cursor_max(3)
- assert_line('abc')
+ assert_line_around_cursor('a', 'bc')
input_keys('x')
- assert_byte_pointer_size('a')
- assert_cursor(1)
- assert_cursor_max(2)
- assert_line('ac')
+ assert_line_around_cursor('a', 'c')
input_keys('x')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(1)
- assert_line('a')
+ assert_line_around_cursor('', 'a')
input_keys('x')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
- assert_line('')
+ assert_line_around_cursor('', '')
input_keys('x')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
- assert_line('')
+ assert_line_around_cursor('', '')
end
def test_vi_delete_next_char_for_mbchar
input_keys("あいう\C-[h")
- assert_byte_pointer_size('あ')
- assert_cursor(2)
- assert_cursor_max(6)
- assert_line('あいう')
+ assert_line_around_cursor('あ', 'いう')
input_keys('x')
- assert_byte_pointer_size('あ')
- assert_cursor(2)
- assert_cursor_max(4)
- assert_line('あう')
+ assert_line_around_cursor('あ', 'う')
input_keys('x')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(2)
- assert_line('あ')
+ assert_line_around_cursor('', 'あ')
input_keys('x')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
- assert_line('')
+ assert_line_around_cursor('', '')
input_keys('x')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
- assert_line('')
+ assert_line_around_cursor('', '')
end
def test_vi_delete_next_char_for_mbchar_by_plural_code_points
input_keys("か\u3099き\u3099く\u3099\C-[h")
- assert_byte_pointer_size("か\u3099")
- assert_cursor(2)
- assert_cursor_max(6)
- assert_line("か\u3099き\u3099く\u3099")
+ assert_line_around_cursor("か\u3099", "き\u3099く\u3099")
input_keys('x')
- assert_byte_pointer_size("か\u3099")
- assert_cursor(2)
- assert_cursor_max(4)
- assert_line("か\u3099く\u3099")
+ assert_line_around_cursor("か\u3099", "く\u3099")
input_keys('x')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(2)
- assert_line("か\u3099")
+ assert_line_around_cursor('', "か\u3099")
input_keys('x')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
- assert_line('')
+ assert_line_around_cursor('', '')
input_keys('x')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
- assert_line('')
+ assert_line_around_cursor('', '')
end
def test_vi_delete_prev_char
input_keys('ab')
- assert_byte_pointer_size('ab')
- assert_cursor(2)
- assert_cursor_max(2)
+ assert_line_around_cursor('ab', '')
input_keys("\C-h")
- assert_byte_pointer_size('a')
- assert_cursor(1)
- assert_cursor_max(1)
- assert_line('a')
+ assert_line_around_cursor('a', '')
end
def test_vi_delete_prev_char_for_mbchar
input_keys('かき')
- assert_byte_pointer_size('かき')
- assert_cursor(4)
- assert_cursor_max(4)
+ assert_line_around_cursor('かき', '')
input_keys("\C-h")
- assert_byte_pointer_size('か')
- assert_cursor(2)
- assert_cursor_max(2)
- assert_line('か')
+ assert_line_around_cursor('か', '')
end
def test_vi_delete_prev_char_for_mbchar_by_plural_code_points
input_keys("か\u3099き\u3099")
- assert_byte_pointer_size("か\u3099き\u3099")
- assert_cursor(4)
- assert_cursor_max(4)
+ assert_line_around_cursor("か\u3099き\u3099", '')
input_keys("\C-h")
- assert_byte_pointer_size("か\u3099")
- assert_cursor(2)
- assert_cursor_max(2)
- assert_line("か\u3099")
+ assert_line_around_cursor("か\u3099", '')
end
def test_ed_delete_prev_char
input_keys("abcdefg\C-[h")
- assert_byte_pointer_size('abcde')
- assert_cursor(5)
- assert_cursor_max(7)
- assert_line('abcdefg')
+ assert_line_around_cursor('abcde', 'fg')
input_keys('X')
- assert_byte_pointer_size('abcd')
- assert_cursor(4)
- assert_cursor_max(6)
- assert_line('abcdfg')
+ assert_line_around_cursor('abcd', 'fg')
input_keys('3X')
- assert_byte_pointer_size('a')
- assert_cursor(1)
- assert_cursor_max(3)
- assert_line('afg')
+ assert_line_around_cursor('a', 'fg')
input_keys('p')
- assert_byte_pointer_size('abcd')
- assert_cursor(4)
- assert_cursor_max(6)
- assert_line('afbcdg')
+ assert_line_around_cursor('afbc', 'dg')
end
def test_ed_delete_prev_word
input_keys('abc def{bbb}ccc')
- assert_byte_pointer_size('abc def{bbb}ccc')
- assert_cursor(15)
- assert_cursor_max(15)
+ assert_line_around_cursor('abc def{bbb}ccc', '')
input_keys("\C-w")
- assert_byte_pointer_size('abc def{bbb}')
- assert_cursor(12)
- assert_cursor_max(12)
- assert_line('abc def{bbb}')
+ assert_line_around_cursor('abc def{bbb}', '')
input_keys("\C-w")
- assert_byte_pointer_size('abc def{')
- assert_cursor(8)
- assert_cursor_max(8)
- assert_line('abc def{')
+ assert_line_around_cursor('abc def{', '')
input_keys("\C-w")
- assert_byte_pointer_size('abc ')
- assert_cursor(4)
- assert_cursor_max(4)
- assert_line('abc ')
+ assert_line_around_cursor('abc ', '')
input_keys("\C-w")
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
- assert_line('')
+ assert_line_around_cursor('', '')
end
def test_ed_delete_prev_word_for_mbchar
input_keys('あいう かきく{さしす}たちつ')
- assert_byte_pointer_size('あいう かきく{さしす}たちつ')
- assert_cursor(27)
- assert_cursor_max(27)
+ assert_line_around_cursor('あいう かきく{さしす}たちつ', '')
input_keys("\C-w")
- assert_byte_pointer_size('あいう かきく{さしす}')
- assert_cursor(21)
- assert_cursor_max(21)
- assert_line('あいう かきく{さしす}')
+ assert_line_around_cursor('あいう かきく{さしす}', '')
input_keys("\C-w")
- assert_byte_pointer_size('あいう かきく{')
- assert_cursor(14)
- assert_cursor_max(14)
- assert_line('あいう かきく{')
+ assert_line_around_cursor('あいう かきく{', '')
input_keys("\C-w")
- assert_byte_pointer_size('あいう ')
- assert_cursor(7)
- assert_cursor_max(7)
- assert_line('あいう ')
+ assert_line_around_cursor('あいう ', '')
input_keys("\C-w")
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
- assert_line('')
+ assert_line_around_cursor('', '')
end
def test_ed_delete_prev_word_for_mbchar_by_plural_code_points
input_keys("あいう か\u3099き\u3099く\u3099{さしす}たちつ")
- assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099{さしす}たちつ")
- assert_cursor(27)
- assert_cursor_max(27)
+ assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}たちつ", '')
input_keys("\C-w")
- assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099{さしす}")
- assert_cursor(21)
- assert_cursor_max(21)
- assert_line("あいう か\u3099き\u3099く\u3099{さしす}")
+ assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}", '')
input_keys("\C-w")
- assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099{")
- assert_cursor(14)
- assert_cursor_max(14)
- assert_line("あいう か\u3099き\u3099く\u3099{")
+ assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{", '')
input_keys("\C-w")
- assert_byte_pointer_size('あいう ')
- assert_cursor(7)
- assert_cursor_max(7)
- assert_line('あいう ')
+ assert_line_around_cursor('あいう ', '')
input_keys("\C-w")
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
- assert_line('')
+ assert_line_around_cursor('', '')
end
def test_ed_newline_with_cr
input_keys('ab')
- assert_byte_pointer_size('ab')
- assert_cursor(2)
- assert_cursor_max(2)
+ assert_line_around_cursor('ab', '')
refute(@line_editor.finished?)
input_keys("\C-m")
- assert_line('ab')
+ assert_line_around_cursor('ab', '')
assert(@line_editor.finished?)
end
def test_ed_newline_with_lf
input_keys('ab')
- assert_byte_pointer_size('ab')
- assert_cursor(2)
- assert_cursor_max(2)
+ assert_line_around_cursor('ab', '')
refute(@line_editor.finished?)
input_keys("\C-j")
- assert_line('ab')
+ assert_line_around_cursor('ab', '')
assert(@line_editor.finished?)
end
def test_vi_list_or_eof
input_keys("\C-d") # quit from inputing
- assert_line(nil)
+ assert_nil(@line_editor.line)
assert(@line_editor.finished?)
end
def test_vi_list_or_eof_with_non_empty_line
input_keys('ab')
- assert_byte_pointer_size('ab')
- assert_cursor(2)
- assert_cursor_max(2)
+ assert_line_around_cursor('ab', '')
refute(@line_editor.finished?)
input_keys("\C-d")
- assert_line('ab')
+ assert_line_around_cursor('ab', '')
assert(@line_editor.finished?)
end
@@ -982,40 +545,19 @@ class Reline::KeyActor::ViInsert::Test < Reline::TestCase
}
}
input_keys('foo')
- assert_byte_pointer_size('foo')
- assert_cursor(3)
- assert_cursor_max(3)
- assert_line('foo')
+ assert_line_around_cursor('foo', '')
input_keys("\C-n")
- assert_byte_pointer_size('foo_bar')
- assert_cursor(7)
- assert_cursor_max(7)
- assert_line('foo_bar')
+ assert_line_around_cursor('foo_bar', '')
input_keys("\C-n")
- assert_byte_pointer_size('foo_bar_baz')
- assert_cursor(11)
- assert_cursor_max(11)
- assert_line('foo_bar_baz')
+ assert_line_around_cursor('foo_bar_baz', '')
input_keys("\C-n")
- assert_byte_pointer_size('foo')
- assert_cursor(3)
- assert_cursor_max(3)
- assert_line('foo')
+ assert_line_around_cursor('foo', '')
input_keys("\C-n")
- assert_byte_pointer_size('foo_bar')
- assert_cursor(7)
- assert_cursor_max(7)
- assert_line('foo_bar')
+ assert_line_around_cursor('foo_bar', '')
input_keys("_\C-n")
- assert_byte_pointer_size('foo_bar_baz')
- assert_cursor(11)
- assert_cursor_max(11)
- assert_line('foo_bar_baz')
+ assert_line_around_cursor('foo_bar_baz', '')
input_keys("\C-n")
- assert_byte_pointer_size('foo_bar_')
- assert_cursor(8)
- assert_cursor_max(8)
- assert_line('foo_bar_')
+ assert_line_around_cursor('foo_bar_', '')
end
def test_completion_journey_reverse
@@ -1028,40 +570,19 @@ class Reline::KeyActor::ViInsert::Test < Reline::TestCase
}
}
input_keys('foo')
- assert_byte_pointer_size('foo')
- assert_cursor(3)
- assert_cursor_max(3)
- assert_line('foo')
+ assert_line_around_cursor('foo', '')
input_keys("\C-p")
- assert_byte_pointer_size('foo_bar_baz')
- assert_cursor(11)
- assert_cursor_max(11)
- assert_line('foo_bar_baz')
+ assert_line_around_cursor('foo_bar_baz', '')
input_keys("\C-p")
- assert_byte_pointer_size('foo_bar')
- assert_cursor(7)
- assert_cursor_max(7)
- assert_line('foo_bar')
+ assert_line_around_cursor('foo_bar', '')
input_keys("\C-p")
- assert_byte_pointer_size('foo')
- assert_cursor(3)
- assert_cursor_max(3)
- assert_line('foo')
+ assert_line_around_cursor('foo', '')
input_keys("\C-p")
- assert_byte_pointer_size('foo_bar_baz')
- assert_cursor(11)
- assert_cursor_max(11)
- assert_line('foo_bar_baz')
+ assert_line_around_cursor('foo_bar_baz', '')
input_keys("\C-h\C-p")
- assert_byte_pointer_size('foo_bar_baz')
- assert_cursor(11)
- assert_cursor_max(11)
- assert_line('foo_bar_baz')
+ assert_line_around_cursor('foo_bar_baz', '')
input_keys("\C-p")
- assert_byte_pointer_size('foo_bar_ba')
- assert_cursor(10)
- assert_cursor_max(10)
- assert_line('foo_bar_ba')
+ assert_line_around_cursor('foo_bar_ba', '')
end
def test_completion_journey_in_middle_of_line
@@ -1074,42 +595,21 @@ class Reline::KeyActor::ViInsert::Test < Reline::TestCase
}
}
input_keys('abcde fo ABCDE')
- assert_line('abcde fo ABCDE')
+ assert_line_around_cursor('abcde fo ABCDE', '')
input_keys("\C-[" + 'h' * 5 + "i\C-n")
- assert_byte_pointer_size('abcde foo_bar')
- assert_cursor(13)
- assert_cursor_max(19)
- assert_line('abcde foo_bar ABCDE')
+ assert_line_around_cursor('abcde foo_bar', ' ABCDE')
input_keys("\C-n")
- assert_byte_pointer_size('abcde foo_bar_baz')
- assert_cursor(17)
- assert_cursor_max(23)
- assert_line('abcde foo_bar_baz ABCDE')
+ assert_line_around_cursor('abcde foo_bar_baz', ' ABCDE')
input_keys("\C-n")
- assert_byte_pointer_size('abcde fo')
- assert_cursor(8)
- assert_cursor_max(14)
- assert_line('abcde fo ABCDE')
+ assert_line_around_cursor('abcde fo', ' ABCDE')
input_keys("\C-n")
- assert_byte_pointer_size('abcde foo_bar')
- assert_cursor(13)
- assert_cursor_max(19)
- assert_line('abcde foo_bar ABCDE')
+ assert_line_around_cursor('abcde foo_bar', ' ABCDE')
input_keys("_\C-n")
- assert_byte_pointer_size('abcde foo_bar_baz')
- assert_cursor(17)
- assert_cursor_max(23)
- assert_line('abcde foo_bar_baz ABCDE')
+ assert_line_around_cursor('abcde foo_bar_baz', ' ABCDE')
input_keys("\C-n")
- assert_byte_pointer_size('abcde foo_bar_')
- assert_cursor(14)
- assert_cursor_max(20)
- assert_line('abcde foo_bar_ ABCDE')
+ assert_line_around_cursor('abcde foo_bar_', ' ABCDE')
input_keys("\C-n")
- assert_byte_pointer_size('abcde foo_bar_baz')
- assert_cursor(17)
- assert_cursor_max(23)
- assert_line('abcde foo_bar_baz ABCDE')
+ assert_line_around_cursor('abcde foo_bar_baz', ' ABCDE')
end
def test_completion
@@ -1122,15 +622,55 @@ class Reline::KeyActor::ViInsert::Test < Reline::TestCase
}
}
input_keys('foo')
- assert_byte_pointer_size('foo')
- assert_cursor(3)
- assert_cursor_max(3)
- assert_line('foo')
+ assert_line_around_cursor('foo', '')
input_keys("\C-i")
- assert_byte_pointer_size('foo_bar')
- assert_cursor(7)
- assert_cursor_max(7)
- assert_line('foo_bar')
+ assert_line_around_cursor('foo_bar', '')
+ end
+
+ def test_autocompletion_with_upward_navigation
+ @config.autocompletion = true
+ @line_editor.completion_proc = proc { |word|
+ %w{
+ Readline
+ Regexp
+ RegexpError
+ }.map { |i|
+ i.encode(@encoding)
+ }
+ }
+ input_keys('Re')
+ assert_line_around_cursor('Re', '')
+ input_keys("\C-i", false)
+ assert_line_around_cursor('Readline', '')
+ input_keys("\C-i", false)
+ assert_line_around_cursor('Regexp', '')
+ @line_editor.input_key(Reline::Key.new(:completion_journey_up, :completion_journey_up, false))
+ assert_line_around_cursor('Readline', '')
+ ensure
+ @config.autocompletion = false
+ end
+
+ def test_autocompletion_with_upward_navigation_and_menu_complete_backward
+ @config.autocompletion = true
+ @line_editor.completion_proc = proc { |word|
+ %w{
+ Readline
+ Regexp
+ RegexpError
+ }.map { |i|
+ i.encode(@encoding)
+ }
+ }
+ input_keys('Re')
+ assert_line_around_cursor('Re', '')
+ input_keys("\C-i", false)
+ assert_line_around_cursor('Readline', '')
+ input_keys("\C-i", false)
+ assert_line_around_cursor('Regexp', '')
+ @line_editor.input_key(Reline::Key.new(:menu_complete_backward, :menu_complete_backward, false))
+ assert_line_around_cursor('Readline', '')
+ ensure
+ @config.autocompletion = false
end
def test_completion_with_disable_completion
@@ -1144,315 +684,224 @@ class Reline::KeyActor::ViInsert::Test < Reline::TestCase
}
}
input_keys('foo')
- assert_byte_pointer_size('foo')
- assert_cursor(3)
- assert_cursor_max(3)
- assert_line('foo')
+ assert_line_around_cursor('foo', '')
input_keys("\C-i")
- assert_byte_pointer_size('foo')
- assert_cursor(3)
- assert_cursor_max(3)
- assert_line('foo')
+ assert_line_around_cursor('foo', '')
end
def test_vi_first_print
input_keys("abcde\C-[^")
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(5)
+ assert_line_around_cursor('', 'abcde')
input_keys("0\C-ki")
input_keys(" abcde\C-[^")
- assert_byte_pointer_size(' ')
- assert_cursor(1)
- assert_cursor_max(6)
+ assert_line_around_cursor(' ', 'abcde')
input_keys("0\C-ki")
input_keys(" abcde ABCDE \C-[^")
- assert_byte_pointer_size(' ')
- assert_cursor(3)
- assert_cursor_max(17)
+ assert_line_around_cursor(' ', 'abcde ABCDE ')
end
def test_ed_move_to_beg
input_keys("abcde\C-[0")
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(5)
+ assert_line_around_cursor('', 'abcde')
input_keys("0\C-ki")
input_keys(" abcde\C-[0")
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(6)
+ assert_line_around_cursor('', ' abcde')
input_keys("0\C-ki")
input_keys(" abcde ABCDE \C-[0")
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(17)
+ assert_line_around_cursor('', ' abcde ABCDE ')
+ end
+
+ def test_vi_to_column
+ input_keys("a一二三\C-[0")
+ input_keys('1|')
+ assert_line_around_cursor('', 'a一二三')
+ input_keys('2|')
+ assert_line_around_cursor('a', '一二三')
+ input_keys('3|')
+ assert_line_around_cursor('a', '一二三')
+ input_keys('4|')
+ assert_line_around_cursor('a一', '二三')
+ input_keys('9|')
+ assert_line_around_cursor('a一二', '三')
end
def test_vi_delete_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')
+ assert_line_around_cursor('aaa bbb ', 'ccc ddd eee')
input_keys('dw')
- assert_byte_pointer_size('aaa bbb ')
- assert_cursor(8)
- assert_cursor_max(15)
- assert_line('aaa bbb ddd eee')
+ assert_line_around_cursor('aaa bbb ', 'ddd eee')
input_keys('db')
- assert_byte_pointer_size('aaa ')
- assert_cursor(4)
- assert_cursor_max(11)
- assert_line('aaa ddd eee')
+ assert_line_around_cursor('aaa ', 'ddd eee')
end
def test_vi_delete_meta_with_vi_next_word_at_eol
input_keys("foo bar\C-[0w")
- assert_byte_pointer_size('foo ')
- assert_cursor(4)
- assert_cursor_max(7)
- assert_line('foo bar')
+ assert_line_around_cursor('foo ', 'bar')
input_keys('w')
- assert_byte_pointer_size('foo ba')
- assert_cursor(6)
- assert_cursor_max(7)
- assert_line('foo bar')
+ assert_line_around_cursor('foo ba', 'r')
input_keys('0dw')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(3)
- assert_line('bar')
+ assert_line_around_cursor('', 'bar')
input_keys('dw')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
- assert_line('')
+ assert_line_around_cursor('', '')
end
def test_vi_delete_meta_with_vi_next_char
input_keys("aaa bbb ccc ___ ddd\C-[02w")
- assert_byte_pointer_size('aaa bbb ')
- assert_cursor(8)
- assert_cursor_max(19)
- assert_line('aaa bbb ccc ___ ddd')
+ assert_line_around_cursor('aaa bbb ', 'ccc ___ ddd')
input_keys('df_')
- assert_byte_pointer_size('aaa bbb ')
- assert_cursor(8)
- assert_cursor_max(14)
- assert_line('aaa bbb __ ddd')
+ assert_line_around_cursor('aaa bbb ', '__ ddd')
end
def test_vi_delete_meta_with_arg
- input_keys("aaa bbb ccc\C-[02w")
- assert_byte_pointer_size('aaa bbb ')
- assert_cursor(8)
- assert_cursor_max(11)
- assert_line('aaa bbb ccc')
+ input_keys("aaa bbb ccc ddd\C-[03w")
+ assert_line_around_cursor('aaa bbb ccc ', 'ddd')
input_keys('2dl')
- assert_byte_pointer_size('aaa bbb ')
- assert_cursor(8)
- assert_cursor_max(9)
- assert_line('aaa bbb c')
+ assert_line_around_cursor('aaa bbb ccc ', 'd')
+ input_keys('d2h')
+ assert_line_around_cursor('aaa bbb cc', 'd')
+ input_keys('2d3h')
+ assert_line_around_cursor('aaa ', 'd')
+ input_keys('dd')
+ assert_line_around_cursor('', '')
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')
+ assert_line_around_cursor('aaa bbb ', 'ccc ddd eee')
input_keys('cwaiueo')
- assert_byte_pointer_size('aaa bbb aiueo')
- assert_cursor(13)
- assert_cursor_max(21)
- assert_line('aaa bbb aiueo ddd eee')
+ assert_line_around_cursor('aaa bbb aiueo', ' ddd eee')
input_keys("\C-[")
- assert_byte_pointer_size('aaa bbb aiue')
- assert_cursor(12)
- assert_cursor_max(21)
- assert_line('aaa bbb aiueo ddd eee')
+ assert_line_around_cursor('aaa bbb aiue', 'o ddd eee')
input_keys('cb')
- assert_byte_pointer_size('aaa bbb ')
- assert_cursor(8)
- assert_cursor_max(17)
- assert_line('aaa bbb o ddd eee')
+ assert_line_around_cursor('aaa bbb ', 'o ddd eee')
end
def test_vi_change_meta_with_vi_next_word
input_keys("foo bar baz\C-[0w")
- assert_byte_pointer_size('foo ')
- assert_cursor(5)
- assert_cursor_max(13)
- assert_line('foo bar baz')
+ assert_line_around_cursor('foo ', 'bar baz')
input_keys('cwhoge')
- assert_byte_pointer_size('foo hoge')
- assert_cursor(9)
- assert_cursor_max(14)
- assert_line('foo hoge baz')
+ assert_line_around_cursor('foo hoge', ' baz')
input_keys("\C-[")
- assert_byte_pointer_size('foo hog')
- assert_cursor(8)
- assert_cursor_max(14)
- assert_line('foo hoge baz')
+ assert_line_around_cursor('foo hog', 'e baz')
+ end
+
+ def test_vi_waiting_operator_with_waiting_proc
+ input_keys("foo foo foo foo foo\C-[0")
+ input_keys('2d3fo')
+ assert_line_around_cursor('', ' foo foo')
+ input_keys('fo')
+ assert_line_around_cursor(' f', 'oo foo')
+ end
+
+ def test_vi_waiting_operator_cancel
+ input_keys("aaa bbb ccc\C-[02w")
+ assert_line_around_cursor('aaa bbb ', 'ccc')
+ # dc dy should cancel delete_meta
+ input_keys('dch')
+ input_keys('dyh')
+ # cd cy should cancel change_meta
+ input_keys('cdh')
+ input_keys('cyh')
+ # yd yc should cancel yank_meta
+ # P should not paste yanked text because yank_meta is canceled
+ input_keys('ydhP')
+ input_keys('ychP')
+ assert_line_around_cursor('aa', 'a bbb ccc')
+ end
+
+ def test_cancel_waiting_with_symbol_key
+ input_keys("aaa bbb lll\C-[0")
+ assert_line_around_cursor('', 'aaa bbb lll')
+ # ed_next_char should move cursor right and cancel vi_next_char
+ input_keys('f')
+ input_key_by_symbol(:ed_next_char)
+ input_keys('l')
+ assert_line_around_cursor('aa', 'a bbb lll')
+ # ed_next_char should move cursor right and cancel delete_meta
+ input_keys('d')
+ input_key_by_symbol(:ed_next_char)
+ input_keys('l')
+ assert_line_around_cursor('aaa ', 'bbb lll')
end
def test_unimplemented_vi_command_should_be_no_op
input_keys("abc\C-[h")
- assert_byte_pointer_size('a')
- assert_cursor(1)
- assert_cursor_max(3)
- assert_line('abc')
+ assert_line_around_cursor('a', 'bc')
input_keys('@')
- assert_byte_pointer_size('a')
- assert_cursor(1)
- assert_cursor_max(3)
- assert_line('abc')
+ assert_line_around_cursor('a', 'bc')
end
def test_vi_yank
- input_keys("foo bar\C-[0")
- assert_line('foo bar')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(7)
+ input_keys("foo bar\C-[2h")
+ assert_line_around_cursor('foo ', 'bar')
input_keys('y3l')
- assert_line('foo bar')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(7)
+ assert_line_around_cursor('foo ', 'bar')
input_keys('P')
- assert_line('foofoo bar')
- assert_byte_pointer_size('fo')
- assert_cursor(2)
- assert_cursor_max(10)
+ assert_line_around_cursor('foo ba', 'rbar')
+ input_keys('3h3yhP')
+ assert_line_around_cursor('foofo', 'o barbar')
+ input_keys('yyP')
+ assert_line_around_cursor('foofofoofoo barba', 'ro barbar')
end
def test_vi_end_word_with_operator
input_keys("foo bar\C-[0")
- assert_line('foo bar')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(7)
+ assert_line_around_cursor('', 'foo bar')
input_keys('de')
- assert_line(' bar')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(4)
+ assert_line_around_cursor('', ' bar')
input_keys('de')
- assert_line('')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
+ assert_line_around_cursor('', '')
input_keys('de')
- assert_line('')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
+ assert_line_around_cursor('', '')
end
def test_vi_end_big_word_with_operator
input_keys("aaa b{b}}}b\C-[0")
- assert_line('aaa b{b}}}b')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(13)
+ assert_line_around_cursor('', 'aaa b{b}}}b')
input_keys('dE')
- assert_line(' b{b}}}b')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(10)
+ assert_line_around_cursor('', ' b{b}}}b')
input_keys('dE')
- assert_line('')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
+ assert_line_around_cursor('', '')
input_keys('dE')
- assert_line('')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
+ assert_line_around_cursor('', '')
end
def test_vi_next_char_with_operator
input_keys("foo bar\C-[0")
- assert_line('foo bar')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(7)
+ assert_line_around_cursor('', 'foo bar')
input_keys('df ')
- assert_line('bar')
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(3)
- end
-
- def test_pasting
- start_pasting
- input_keys('ab')
- finish_pasting
- input_keys('c')
- assert_line('abc')
- assert_byte_pointer_size('abc')
- assert_cursor(3)
- assert_cursor_max(3)
- end
-
- def test_pasting_fullwidth
- start_pasting
- input_keys('あ')
- finish_pasting
- input_keys('い')
- assert_line('あい')
- assert_byte_pointer_size('あい')
- assert_cursor(4)
- assert_cursor_max(4)
+ assert_line_around_cursor('', 'bar')
end
def test_ed_delete_next_char_at_eol
input_keys('"あ"')
- assert_line('"あ"')
- assert_byte_pointer_size('"あ"')
- assert_cursor(4)
- assert_cursor_max(4)
+ assert_line_around_cursor('"あ"', '')
input_keys("\C-[")
- assert_line('"あ"')
- assert_byte_pointer_size('"あ')
- assert_cursor(3)
- assert_cursor_max(4)
+ assert_line_around_cursor('"あ', '"')
input_keys('xa"')
- assert_line('"あ"')
- assert_byte_pointer_size('"あ"')
- assert_cursor(4)
- assert_cursor_max(4)
+ assert_line_around_cursor('"あ"', '')
end
def test_vi_kill_line_prev
input_keys("\C-u", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
- assert_line('')
+ assert_line_around_cursor('', '')
input_keys('abc')
- assert_byte_pointer_size('abc')
- assert_cursor(3)
- assert_cursor_max(3)
+ assert_line_around_cursor('abc', '')
input_keys("\C-u", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(0)
- assert_line('')
+ assert_line_around_cursor('', '')
input_keys('abc')
input_keys("\C-[\C-u", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(1)
- assert_line('c')
+ assert_line_around_cursor('', 'c')
input_keys("\C-u", false)
- assert_byte_pointer_size('')
- assert_cursor(0)
- assert_cursor_max(1)
- assert_line('c')
+ assert_line_around_cursor('', 'c')
+ end
+
+ def test_vi_change_to_eol
+ input_keys("abcdef\C-[2hC")
+ assert_line_around_cursor('abc', '')
+ input_keys("\C-[0C")
+ assert_line_around_cursor('', '')
+ assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode)
end
def test_vi_motion_operators
@@ -1462,4 +911,9 @@ class Reline::KeyActor::ViInsert::Test < Reline::TestCase
input_keys("test = { foo: bar }\C-[BBBldt}b")
end
end
+
+ def test_emacs_editing_mode
+ @line_editor.__send__(:emacs_editing_mode, nil)
+ assert(@config.editing_mode_is?(:emacs))
+ end
end
diff --git a/test/reline/test_line_editor.rb b/test/reline/test_line_editor.rb
index 8399e76e92..7a38ecd596 100644
--- a/test/reline/test_line_editor.rb
+++ b/test/reline/test_line_editor.rb
@@ -1,13 +1,185 @@
require_relative 'helper'
require 'reline/line_editor'
+require 'stringio'
-class Reline::LineEditor::Test < Reline::TestCase
- def test_range_subtract
- dummy_config = nil
- editor = Reline::LineEditor.new(dummy_config, 'ascii-8bit')
- base_ranges = [3...5, 4...10, 6...8, 12...15, 15...20]
- subtract_ranges = [5...7, 8...9, 11...13, 17...18, 18...19]
- expected_result = [3...5, 7...8, 9...10, 13...17, 19...20]
- assert_equal expected_result, editor.send(:range_subtract, base_ranges, subtract_ranges)
+class Reline::LineEditor
+ class RenderLineDifferentialTest < Reline::TestCase
+ module TestIO
+ RESET_COLOR = "\e[0m"
+
+ def self.move_cursor_column(col)
+ @output << "[COL_#{col}]"
+ end
+
+ def self.erase_after_cursor
+ @output << '[ERASE]'
+ end
+ end
+
+ def setup
+ verbose, $VERBOSE = $VERBOSE, nil
+ @line_editor = Reline::LineEditor.new(nil, Encoding::UTF_8)
+ @original_iogate = Reline::IOGate
+ @output = StringIO.new
+ @line_editor.instance_variable_set(:@screen_size, [24, 80])
+ @line_editor.instance_variable_set(:@output, @output)
+ Reline.send(:remove_const, :IOGate)
+ Reline.const_set(:IOGate, TestIO)
+ Reline::IOGate.instance_variable_set(:@output, @output)
+ ensure
+ $VERBOSE = verbose
+ end
+
+ def assert_output(expected)
+ @output.reopen(+'')
+ yield
+ actual = @output.string
+ assert_equal(expected, actual.gsub("\e[0m", ''))
+ end
+
+ def teardown
+ Reline.send(:remove_const, :IOGate)
+ Reline.const_set(:IOGate, @original_iogate)
+ end
+
+ def test_line_increase_decrease
+ assert_output '[COL_0]bb' do
+ @line_editor.render_line_differential([[0, 1, 'a']], [[0, 2, 'bb']])
+ end
+
+ assert_output '[COL_0]b[COL_1][ERASE]' do
+ @line_editor.render_line_differential([[0, 2, 'aa']], [[0, 1, 'b']])
+ end
+ end
+
+ def test_dialog_appear_disappear
+ assert_output '[COL_3]dialog' do
+ @line_editor.render_line_differential([[0, 1, 'a']], [[0, 1, 'a'], [3, 6, 'dialog']])
+ end
+
+ assert_output '[COL_3]dialog' do
+ @line_editor.render_line_differential([[0, 10, 'a' * 10]], [[0, 10, 'a' * 10], [3, 6, 'dialog']])
+ end
+
+ assert_output '[COL_1][ERASE]' do
+ @line_editor.render_line_differential([[0, 1, 'a'], [3, 6, 'dialog']], [[0, 1, 'a']])
+ end
+
+ assert_output '[COL_3]aaaaaa' do
+ @line_editor.render_line_differential([[0, 10, 'a' * 10], [3, 6, 'dialog']], [[0, 10, 'a' * 10]])
+ end
+ end
+
+ def test_dialog_change
+ assert_output '[COL_3]DIALOG' do
+ @line_editor.render_line_differential([[0, 2, 'a'], [3, 6, 'dialog']], [[0, 2, 'a'], [3, 6, 'DIALOG']])
+ end
+
+ assert_output '[COL_3]DIALOG' do
+ @line_editor.render_line_differential([[0, 10, 'a' * 10], [3, 6, 'dialog']], [[0, 10, 'a' * 10], [3, 6, 'DIALOG']])
+ end
+ end
+
+ def test_update_under_dialog
+ assert_output '[COL_0]b[COL_1] ' do
+ @line_editor.render_line_differential([[0, 2, 'aa'], [4, 6, 'dialog']], [[0, 1, 'b'], [4, 6, 'dialog']])
+ end
+
+ assert_output '[COL_0]bbb[COL_9]b' do
+ @line_editor.render_line_differential([[0, 10, 'a' * 10], [3, 6, 'dialog']], [[0, 10, 'b' * 10], [3, 6, 'dialog']])
+ end
+
+ assert_output '[COL_0]b[COL_1] [COL_9][ERASE]' do
+ @line_editor.render_line_differential([[0, 10, 'a' * 10], [3, 6, 'dialog']], [[0, 1, 'b'], [3, 6, 'dialog']])
+ end
+ end
+
+ def test_dialog_move
+ assert_output '[COL_3]dialog[COL_9][ERASE]' do
+ @line_editor.render_line_differential([[0, 1, 'a'], [4, 6, 'dialog']], [[0, 1, 'a'], [3, 6, 'dialog']])
+ end
+
+ assert_output '[COL_4] [COL_5]dialog' do
+ @line_editor.render_line_differential([[0, 1, 'a'], [4, 6, 'dialog']], [[0, 1, 'a'], [5, 6, 'dialog']])
+ end
+
+ assert_output '[COL_2]dialog[COL_8]a' do
+ @line_editor.render_line_differential([[0, 10, 'a' * 10], [3, 6, 'dialog']], [[0, 10, 'a' * 10], [2, 6, 'dialog']])
+ end
+
+ assert_output '[COL_2]a[COL_3]dialog' do
+ @line_editor.render_line_differential([[0, 10, 'a' * 10], [2, 6, 'dialog']], [[0, 10, 'a' * 10], [3, 6, 'dialog']])
+ end
+ end
+
+ def test_multibyte
+ base = [0, 12, '一二三一二三']
+ left = [0, 3, 'LLL']
+ right = [9, 3, 'RRR']
+ front = [3, 6, 'FFFFFF']
+ # 一 FFFFFF 三
+ # 一二三一二三
+ assert_output '[COL_2]二三一二' do
+ @line_editor.render_line_differential([base, front], [base, nil])
+ end
+
+ # LLLFFFFFF 三
+ # LLL 三一二三
+ assert_output '[COL_3] 三一二' do
+ @line_editor.render_line_differential([base, left, front], [base, left, nil])
+ end
+
+ # 一 FFFFFFRRR
+ # 一二三一 RRR
+ assert_output '[COL_2]二三一 ' do
+ @line_editor.render_line_differential([base, right, front], [base, right, nil])
+ end
+
+ # LLLFFFFFFRRR
+ # LLL 三一 RRR
+ assert_output '[COL_3] 三一 ' do
+ @line_editor.render_line_differential([base, left, right, front], [base, left, right, nil])
+ end
+ end
+
+ def test_complicated
+ state_a = [nil, [19, 7, 'bbbbbbb'], [15, 8, 'cccccccc'], [10, 5, 'ddddd'], [18, 4, 'eeee'], [1, 3, 'fff'], [17, 2, 'gg'], [7, 1, 'h']]
+ state_b = [[5, 9, 'aaaaaaaaa'], nil, [15, 8, 'cccccccc'], nil, [18, 4, 'EEEE'], [25, 4, 'ffff'], [17, 2, 'gg'], [2, 2, 'hh']]
+ # state_a: " fff h dddddccggeeecbbb"
+ # state_b: " hh aaaaaaaaa ccggEEEc ffff"
+
+ assert_output '[COL_1] [COL_2]hh[COL_5]aaaaaaaaa[COL_14] [COL_19]EEE[COL_23] [COL_25]ffff' do
+ @line_editor.render_line_differential(state_a, state_b)
+ end
+
+ assert_output '[COL_1]fff[COL_5] [COL_7]h[COL_8] [COL_10]ddddd[COL_19]eee[COL_23]bbb[COL_26][ERASE]' do
+ @line_editor.render_line_differential(state_b, state_a)
+ end
+ end
+ end
+
+ def test_menu_info_format
+ list = %w[aa b c d e f g hhh i j k]
+ col3 = [
+ 'aa e i',
+ 'b f j',
+ 'c g k',
+ 'd hhh'
+ ]
+ col2 = [
+ 'aa g',
+ 'b hhh',
+ 'c i',
+ 'd j',
+ 'e k',
+ 'f'
+ ]
+ assert_equal(col3, Reline::LineEditor::MenuInfo.new(list).lines(19))
+ assert_equal(col3, Reline::LineEditor::MenuInfo.new(list).lines(15))
+ assert_equal(col2, Reline::LineEditor::MenuInfo.new(list).lines(14))
+ assert_equal(col2, Reline::LineEditor::MenuInfo.new(list).lines(10))
+ assert_equal(list, Reline::LineEditor::MenuInfo.new(list).lines(9))
+ assert_equal(list, Reline::LineEditor::MenuInfo.new(list).lines(0))
+ assert_equal([], Reline::LineEditor::MenuInfo.new([]).lines(10))
end
end
diff --git a/test/reline/test_macro.rb b/test/reline/test_macro.rb
index 3096930830..04aa6474b4 100644
--- a/test/reline/test_macro.rb
+++ b/test/reline/test_macro.rb
@@ -6,7 +6,6 @@ class Reline::MacroTest < Reline::TestCase
@config = Reline::Config.new
@encoding = Reline.core.encoding
@line_editor = Reline::LineEditor.new(@config, @encoding)
- @line_editor.instance_variable_set(:@screen_size, [24, 80])
@output = @line_editor.output = File.open(IO::NULL, "w")
end
diff --git a/test/reline/test_reline.rb b/test/reline/test_reline.rb
index 40c880c11f..a20a5c9f44 100644
--- a/test/reline/test_reline.rb
+++ b/test/reline/test_reline.rb
@@ -378,10 +378,28 @@ class Reline::Test < Reline::TestCase
assert_equal("Reline::GeneralIO", out.chomp)
end
+ def test_require_reline_should_not_trigger_winsize
+ pend if win?
+ lib = File.expand_path("../../lib", __dir__)
+ code = <<~RUBY
+ require "io/console"
+ def STDIN.tty?; true; end
+ def STDOUT.tty?; true; end
+ def STDIN.winsize; raise; end
+ require("reline") && p(Reline.core.io_gate)
+ RUBY
+ out = IO.popen([{}, Reline.test_rubybin, "-I#{lib}", "-e", code], &:read)
+ assert_equal("Reline::ANSI", out.chomp)
+ end
+
+ def win?
+ /mswin|mingw/.match?(RUBY_PLATFORM)
+ end
+
def get_reline_encoding
if encoding = Reline.core.encoding
encoding
- elsif RUBY_PLATFORM =~ /mswin|mingw/
+ elsif win?
Encoding::UTF_8
else
Encoding::default_external
diff --git a/test/reline/test_string_processing.rb b/test/reline/test_string_processing.rb
index 2e5d27dc4f..c9b9e38643 100644
--- a/test/reline/test_string_processing.rb
+++ b/test/reline/test_string_processing.rb
@@ -30,10 +30,7 @@ class Reline::LineEditor::StringProcessingTest < Reline::TestCase
@line_editor.instance_variable_set(:@is_multiline, true)
@line_editor.instance_variable_set(:@buffer_of_lines, buf)
- @line_editor.instance_variable_set(:@line, buf[1])
@line_editor.instance_variable_set(:@byte_pointer, 3)
- @line_editor.instance_variable_set(:@cursor, 3)
- @line_editor.instance_variable_set(:@cursor_max, 11)
@line_editor.instance_variable_set(:@line_index, 1)
@line_editor.instance_variable_set(:@completion_proc, proc { |target|
assert_equal('p', target)
@@ -42,10 +39,7 @@ class Reline::LineEditor::StringProcessingTest < Reline::TestCase
@line_editor.instance_variable_set(:@is_multiline, true)
@line_editor.instance_variable_set(:@buffer_of_lines, buf)
- @line_editor.instance_variable_set(:@line, buf[1])
@line_editor.instance_variable_set(:@byte_pointer, 6)
- @line_editor.instance_variable_set(:@cursor, 6)
- @line_editor.instance_variable_set(:@cursor_max, 11)
@line_editor.instance_variable_set(:@line_index, 1)
@line_editor.instance_variable_set(:@completion_proc, proc { |target, pre, post|
assert_equal('puts', target)
@@ -54,10 +48,7 @@ class Reline::LineEditor::StringProcessingTest < Reline::TestCase
})
@line_editor.__send__(:call_completion_proc)
- @line_editor.instance_variable_set(:@line, buf[0])
@line_editor.instance_variable_set(:@byte_pointer, 6)
- @line_editor.instance_variable_set(:@cursor, 6)
- @line_editor.instance_variable_set(:@cursor_max, 8)
@line_editor.instance_variable_set(:@line_index, 0)
@line_editor.instance_variable_set(:@completion_proc, proc { |target, pre, post|
assert_equal('ho', target)
@@ -66,10 +57,7 @@ class Reline::LineEditor::StringProcessingTest < Reline::TestCase
})
@line_editor.__send__(:call_completion_proc)
- @line_editor.instance_variable_set(:@line, buf[2])
@line_editor.instance_variable_set(:@byte_pointer, 1)
- @line_editor.instance_variable_set(:@cursor, 1)
- @line_editor.instance_variable_set(:@cursor_max, 3)
@line_editor.instance_variable_set(:@line_index, 2)
@line_editor.instance_variable_set(:@completion_proc, proc { |target, pre, post|
assert_equal('e', target)
diff --git a/test/reline/test_terminfo.rb b/test/reline/test_terminfo.rb
index dda9b32495..4e59c54838 100644
--- a/test/reline/test_terminfo.rb
+++ b/test/reline/test_terminfo.rb
@@ -58,4 +58,4 @@ class Reline::Terminfo::Test < Reline::TestCase
assert_raise(Reline::Terminfo::TerminfoError) { Reline::Terminfo.tigetnum('unknown') }
assert_raise(Reline::Terminfo::TerminfoError) { Reline::Terminfo.tigetnum(nil) }
end
-end if Reline::Terminfo.enabled?
+end if Reline::Terminfo.enabled? && Reline::Terminfo.term_supported?
diff --git a/test/reline/test_unicode.rb b/test/reline/test_unicode.rb
index 834f7114c4..deba4d4681 100644
--- a/test/reline/test_unicode.rb
+++ b/test/reline/test_unicode.rb
@@ -38,11 +38,16 @@ class Reline::Unicode::Test < Reline::TestCase
assert_equal [["ab\e]0;1\ac", nil, "\e]0;1\ad"], 2], Reline::Unicode.split_by_width("ab\e]0;1\acd", 3)
end
+ def test_split_by_width_csi_reset_sgr_optimization
+ assert_equal [["\e[1ma\e[mb\e[2mc", nil, "\e[2md\e[0me\e[3mf", nil, "\e[3mg"], 3], Reline::Unicode.split_by_width("\e[1ma\e[mb\e[2mcd\e[0me\e[3mfg", 3)
+ assert_equal [["\e[1ma\1\e[mzero\e[0m\2\e[2mb", nil, "\e[1m\e[2mc"], 2], Reline::Unicode.split_by_width("\e[1ma\1\e[mzero\e[0m\2\e[2mbc", 2)
+ end
+
def test_take_range
assert_equal 'cdef', Reline::Unicode.take_range('abcdefghi', 2, 4)
assert_equal 'あde', Reline::Unicode.take_range('abあdef', 2, 4)
- assert_equal 'zerocdef', Reline::Unicode.take_range("ab\1zero\2cdef", 2, 4)
- assert_equal 'bzerocde', Reline::Unicode.take_range("ab\1zero\2cdef", 1, 4)
+ assert_equal "\1zero\2cdef", Reline::Unicode.take_range("ab\1zero\2cdef", 2, 4)
+ assert_equal "b\1zero\2cde", Reline::Unicode.take_range("ab\1zero\2cdef", 1, 4)
assert_equal "\e[31mcd\e[42mef", Reline::Unicode.take_range("\e[31mabcd\e[42mefg", 2, 4)
assert_equal "\e]0;1\acd", Reline::Unicode.take_range("ab\e]0;1\acd", 2, 3)
assert_equal 'いう', Reline::Unicode.take_range('あいうえお', 2, 4)
@@ -62,4 +67,26 @@ class Reline::Unicode::Test < Reline::TestCase
assert_equal 10, Reline::Unicode.calculate_width('あいうえお')
assert_equal 10, Reline::Unicode.calculate_width('あいうえお', true)
end
+
+ def test_take_mbchar_range
+ assert_equal ['cdef', 2, 4], Reline::Unicode.take_mbchar_range('abcdefghi', 2, 4)
+ assert_equal ['cdef', 2, 4], Reline::Unicode.take_mbchar_range('abcdefghi', 2, 4, padding: true)
+ assert_equal ['cdef', 2, 4], Reline::Unicode.take_mbchar_range('abcdefghi', 2, 4, cover_begin: true)
+ assert_equal ['cdef', 2, 4], Reline::Unicode.take_mbchar_range('abcdefghi', 2, 4, cover_end: true)
+ assert_equal ['いう', 2, 4], Reline::Unicode.take_mbchar_range('あいうえお', 2, 4)
+ assert_equal ['いう', 2, 4], Reline::Unicode.take_mbchar_range('あいうえお', 2, 4, padding: true)
+ assert_equal ['いう', 2, 4], Reline::Unicode.take_mbchar_range('あいうえお', 2, 4, cover_begin: true)
+ assert_equal ['いう', 2, 4], Reline::Unicode.take_mbchar_range('あいうえお', 2, 4, cover_end: true)
+ assert_equal ['う', 4, 2], Reline::Unicode.take_mbchar_range('あいうえお', 3, 4)
+ assert_equal [' う ', 3, 4], Reline::Unicode.take_mbchar_range('あいうえお', 3, 4, padding: true)
+ assert_equal ['いう', 2, 4], Reline::Unicode.take_mbchar_range('あいうえお', 3, 4, cover_begin: true)
+ assert_equal ['うえ', 4, 4], Reline::Unicode.take_mbchar_range('あいうえお', 3, 4, cover_end: true)
+ assert_equal ['いう ', 2, 5], Reline::Unicode.take_mbchar_range('あいうえお', 3, 4, cover_begin: true, padding: true)
+ assert_equal [' うえ', 3, 5], Reline::Unicode.take_mbchar_range('あいうえお', 3, 4, cover_end: true, padding: true)
+ assert_equal [' うえお ', 3, 10], Reline::Unicode.take_mbchar_range('あいうえお', 3, 10, padding: true)
+ assert_equal [" \e[41mうえお\e[0m ", 3, 10], Reline::Unicode.take_mbchar_range("あい\e[41mうえお", 3, 10, padding: true)
+ assert_equal ["\e[41m \e[42mい\e[43m ", 1, 4], Reline::Unicode.take_mbchar_range("\e[41mあ\e[42mい\e[43mう", 1, 4, padding: true)
+ assert_equal ["\e[31mc\1ABC\2d\e[0mef", 2, 4], Reline::Unicode.take_mbchar_range("\e[31mabc\1ABC\2d\e[0mefghi", 2, 4)
+ assert_equal ["\e[41m \e[42mい\e[43m ", 1, 4], Reline::Unicode.take_mbchar_range("\e[41mあ\e[42mい\e[43mう", 1, 4, padding: true)
+ end
end
diff --git a/test/reline/test_within_pipe.rb b/test/reline/test_within_pipe.rb
index a42ca755fc..4f05255301 100644
--- a/test/reline/test_within_pipe.rb
+++ b/test/reline/test_within_pipe.rb
@@ -23,7 +23,6 @@ class Reline::WithinPipeTest < Reline::TestCase
@reader.close
@output_writer.close
@config.reset
- @config.reset_default_key_bindings
Reline.test_reset
end
diff --git a/test/reline/yamatanooroti/multiline_repl b/test/reline/yamatanooroti/multiline_repl
index e2a900b251..8b82be60f4 100755
--- a/test/reline/yamatanooroti/multiline_repl
+++ b/test/reline/yamatanooroti/multiline_repl
@@ -9,14 +9,10 @@ require 'optparse'
require_relative 'termination_checker'
opt = OptionParser.new
-opt.on('--prompt-list-cache-timeout VAL') { |v|
- Reline::LineEditor.__send__(:remove_const, :PROMPT_LIST_CACHE_TIMEOUT)
- Reline::LineEditor::PROMPT_LIST_CACHE_TIMEOUT = v.to_f
-}
opt.on('--dynamic-prompt') {
Reline.prompt_proc = proc { |lines|
lines.each_with_index.map { |l, i|
- '[%04d]> ' % i
+ "\e[1m[%04d]>\e[m " % i
}
}
}
@@ -147,12 +143,21 @@ opt.on('--complete') {
%w{String ScriptError SyntaxError Signal}.select{ |c| c.start_with?(target) }
}
}
+opt.on('--complete-menu-with-perfect-match') {
+ Reline.completion_proc = lambda { |target, preposing = nil, postposing = nil|
+ %w{abs abs2}.select{ |c| c.start_with?(target) }
+ }
+}
opt.on('--autocomplete') {
Reline.autocompletion = true
Reline.completion_proc = lambda { |target, preposing = nil, postposing = nil|
%w{String Struct Symbol ScriptError SyntaxError Signal}.select{ |c| c.start_with?(target) }
}
}
+opt.on('--autocomplete-empty') {
+ Reline.autocompletion = true
+ Reline.completion_proc = lambda { |target, preposing = nil, postposing = nil| [] }
+}
opt.on('--autocomplete-long') {
Reline.autocompletion = true
Reline.completion_proc = lambda { |target, preposing = nil, postposing = nil|
@@ -183,7 +188,7 @@ opt.on('--autocomplete-long') {
opt.on('--autocomplete-super-long') {
Reline.autocompletion = true
Reline.completion_proc = lambda { |target, preposing = nil, postposing = nil|
- c = 'A'
+ c = +'A'
2000.times.map{ s = "Str_#{c}"; c.succ!; s }.select{ |c| c.start_with?(target) }
}
}
@@ -217,7 +222,7 @@ rescue
end
begin
- prompt = ENV['RELINE_TEST_PROMPT'] || 'prompt> '
+ prompt = ENV['RELINE_TEST_PROMPT'] || "\e[1mprompt>\e[m "
puts 'Multiline REPL.'
while code = Reline.readmultiline(prompt, true) { |code| TerminationChecker.terminated?(code) }
case code.chomp
diff --git a/test/reline/yamatanooroti/test_rendering.rb b/test/reline/yamatanooroti/test_rendering.rb
index 670432d86b..37a1c1a193 100644
--- a/test/reline/yamatanooroti/test_rendering.rb
+++ b/test/reline/yamatanooroti/test_rendering.rb
@@ -197,9 +197,12 @@ begin
LINES
start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
write(":a\n\C-[k")
+ write("i\n:a")
+ write("\C-[h")
close
assert_screen(<<~EOC)
- Multiline REPL.
+ (ins)prompt> :a
+ => :a
(ins)prompt> :a
=> :a
(cmd)prompt> :a
@@ -306,6 +309,21 @@ begin
EOC
end
+ def test_readline_with_multiline_input
+ start_terminal(5, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --dynamic-prompt}, startup_message: 'Multiline REPL.')
+ write("def foo\n bar\nend\n")
+ write("Reline.readline('prompt> ')\n")
+ write("\C-p\C-p")
+ close
+ assert_screen(<<~EOC)
+ => :foo
+ [0000]> Reline.readline('prompt> ')
+ prompt> def foo
+ bar
+ end
+ EOC
+ end
+
def test_multiline_and_autowrap
start_terminal(10, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
write("def aaaaaaaaaa\n 33333333\n end\C-a\C-pputs\C-e\e\C-m888888888888888")
@@ -464,6 +482,9 @@ begin
write("def a\n 8\nend\ndef b\n 3\nend\C-s8")
close
assert_screen(<<~EOC)
+ prompt> 8
+ prompt> end
+ => :a
(i-search)`8'def a
(i-search)`8' 8
(i-search)`8'end
@@ -475,6 +496,9 @@ begin
write("def a\n 8\nend\ndef b\n 3\nend\C-r8\C-j")
close
assert_screen(<<~EOC)
+ prompt> 8
+ prompt> end
+ => :a
prompt> def a
prompt> 8
prompt> end
@@ -495,18 +519,6 @@ begin
EOC
end
- def test_prompt_list_caching
- start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --prompt-list-cache-timeout 10 --dynamic-prompt}, startup_message: 'Multiline REPL.')
- write("def hoge\n 3\nend")
- close
- assert_screen(<<~EOC)
- Multiline REPL.
- [0000]> def hoge
- [0001]> 3
- [0002]> end
- EOC
- end
-
def test_broken_prompt_list
start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --broken-dynamic-prompt}, startup_message: 'Multiline REPL.')
write("def hoge\n 3\nend")
@@ -531,15 +543,10 @@ begin
EOC
end
- def test_enable_bracketed_paste
+ def test_bracketed_paste
omit if Reline.core.io_gate.win?
- write_inputrc <<~LINES
- set enable-bracketed-paste on
- LINES
start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write("\e[200~,")
- write("def hoge\n 3\nend")
- write("\e[200~.")
+ write("\e[200~def hoge\r\t3\rend\e[201~")
close
assert_screen(<<~EOC)
Multiline REPL.
@@ -549,6 +556,19 @@ begin
EOC
end
+ def test_bracketed_paste_with_undo
+ omit if Reline.core.io_gate.win?
+ start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
+ write("abc")
+ write("\e[200~def hoge\r\t3\rend\e[201~")
+ write("\C-_")
+ close
+ assert_screen(<<~EOC)
+ Multiline REPL.
+ prompt> abc
+ EOC
+ end
+
def test_backspace_until_returns_to_initial
start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
write("ABC")
@@ -834,6 +854,20 @@ begin
EOC
end
+ def test_auto_indent_with_various_spaces
+ start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --auto-indent}, startup_message: 'Multiline REPL.')
+ write "(\n\C-v"
+ write "\C-k\n\C-v"
+ write "\C-k)"
+ close
+ assert_screen(<<~EOC)
+ Multiline REPL.
+ prompt> (
+ prompt> ^K
+ prompt> )
+ EOC
+ end
+
def test_autowrap_in_the_middle_of_a_line
start_terminal(5, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
write("def abcdefg; end\C-b\C-b\C-b\C-b\C-b")
@@ -919,18 +953,30 @@ begin
EOC
end
- def test_with_newline
+ def test_eof_with_newline
omit if Reline.core.io_gate.win?
cmd = %Q{ruby -e 'print(%Q{abc def \\e\\r})' | ruby -I#{@pwd}/lib -rreline -e 'p Reline.readline(%{> })'}
start_terminal(40, 50, ['bash', '-c', cmd])
sleep 1
- close
+ close rescue nil
assert_screen(<<~'EOC')
> abc def
"abc def "
EOC
end
+ def test_eof_without_newline
+ omit if Reline.core.io_gate.win?
+ cmd = %Q{ruby -e 'print(%{hello})' | ruby -I#{@pwd}/lib -rreline -e 'p Reline.readline(%{> })'}
+ start_terminal(40, 50, ['bash', '-c', cmd])
+ sleep 1
+ close rescue nil
+ assert_screen(<<~'EOC')
+ > hello
+ "hello"
+ EOC
+ end
+
def test_em_set_mark_and_em_exchange_mark
start_terminal(10, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
write("aaa bbb ccc ddd\M-b\M-b\M-\x20\M-b\C-x\C-xX\C-x\C-xY")
@@ -980,6 +1026,47 @@ begin
EOC
end
+ def test_completion_menu_is_displayed_horizontally
+ start_terminal(20, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --complete}, startup_message: 'Multiline REPL.')
+ write("S\t\t")
+ close
+ assert_screen(<<~'EOC')
+ Multiline REPL.
+ prompt> S
+ ScriptError String
+ Signal SyntaxError
+ EOC
+ end
+
+ def test_show_all_if_ambiguous_on
+ write_inputrc <<~LINES
+ set show-all-if-ambiguous on
+ LINES
+ start_terminal(20, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --complete}, startup_message: 'Multiline REPL.')
+ write("S\t")
+ close
+ assert_screen(<<~'EOC')
+ Multiline REPL.
+ prompt> S
+ ScriptError String
+ Signal SyntaxError
+ EOC
+ end
+
+ def test_show_all_if_ambiguous_on_and_menu_with_perfect_match
+ write_inputrc <<~LINES
+ set show-all-if-ambiguous on
+ LINES
+ start_terminal(20, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --complete-menu-with-perfect-match}, startup_message: 'Multiline REPL.')
+ write("a\t")
+ close
+ assert_screen(<<~'EOC')
+ Multiline REPL.
+ prompt> abs
+ abs abs2
+ EOC
+ end
+
def test_simple_dialog
iterate_over_face_configs do |config_name, config_file|
start_terminal(20, 50, %W{ruby -I#{@pwd}/lib -r#{config_file.path} #{@pwd}/test/reline/yamatanooroti/multiline_repl --dialog simple}, startup_message: 'Multiline REPL.')
@@ -1026,8 +1113,8 @@ begin
iterate_over_face_configs do |config_name, config_file|
start_terminal(10, 50, %W{ruby -I#{@pwd}/lib -r#{config_file.path} #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.')
write("\n" * 10)
- write("if 1\n sSt\nend")
- write("\C-p\C-h\C-e")
+ write("if 1\n sSts\nend")
+ write("\C-p\C-h\C-e\C-h")
close
assert_screen(<<~'EOC')
prompt>
@@ -1054,8 +1141,8 @@ begin
prompt> 2
prompt> 3#
prompt> 4
- prompt> 5
- prompt> 6 Ruby is...
+ prompt> 5 Ruby is...
+ prompt> 6 A dynamic, open source programming
EOC
end
end
@@ -1106,11 +1193,53 @@ begin
assert_screen(<<~'EOC')
Multiline REPL.
prompt> St
- r String
+ r
+ String
+ Struct
+ EOC
+ end
+
+ def test_autocomplete_target_at_end_of_line
+ start_terminal(20, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.')
+ write(' ')
+ write('Str')
+ write("\C-i")
+ close
+ assert_screen(<<~'EOC')
+ Multiline REPL.
+ prompt> Str
+ ing String
Struct
EOC
end
+ def test_autocomplete_completed_input_is_wrapped
+ start_terminal(20, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.')
+ write(' ')
+ write('Str')
+ write("\C-i")
+ close
+ assert_screen(<<~'EOC')
+ Multiline REPL.
+ prompt> Stri
+ ng String
+ Struct
+ EOC
+ end
+
+ def test_force_insert_before_autocomplete
+ start_terminal(20, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.')
+ write('Sy')
+ write(";St\t\t")
+ close
+ assert_screen(<<~'EOC')
+ Multiline REPL.
+ prompt> Sy;Struct
+ String
+ Struct
+ EOC
+ end
+
def test_simple_dialog_with_scroll_key
start_terminal(20, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --dialog long,scrollkey}, startup_message: 'Multiline REPL.')
write('a')
@@ -1213,6 +1342,32 @@ begin
EOC
end
+ def test_autocomplete_empty_string
+ start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.')
+ write("\C-i")
+ close
+ assert_screen(<<~'EOC')
+ Multiline REPL.
+ prompt> String
+ String █
+ Struct ▀
+ Symbol
+ EOC
+ end
+
+ def test_paste_code_with_tab_indent_does_not_fail
+ start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete-empty}, startup_message: 'Multiline REPL.')
+ write("2.times do\n\tputs\n\tputs\nend")
+ close
+ assert_screen(<<~'EOC')
+ Multiline REPL.
+ prompt> 2.times do
+ prompt> puts
+ prompt> puts
+ prompt> end
+ EOC
+ end
+
def test_autocomplete_after_2nd_line
start_terminal(20, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.')
write("def hoge\n Str")
@@ -1640,6 +1795,23 @@ begin
EOC
end
+ def test_thread_safe
+ start_terminal(6, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --auto-indent}, startup_message: 'Multiline REPL.')
+ write("[Thread.new{Reline.readline'>'},Thread.new{Reline.readmultiline('>'){true}}].map(&:join).size\n")
+ write("exit\n")
+ write("exit\n")
+ write("42\n")
+ close
+ assert_screen(<<~EOC)
+ >exit
+ >exit
+ => 2
+ prompt> 42
+ => 42
+ prompt>
+ EOC
+ end
+
def write_inputrc(content)
File.open(@inputrc_file, 'w') do |f|
f.write content