diff options
153 files changed, 3 insertions, 33341 deletions
diff --git a/gems/bundled_gems b/gems/bundled_gems index c254e979a4..0fa265b352 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -41,3 +41,6 @@ benchmark 0.4.0 https://github.com/ruby/benchmark logger 1.6.5 https://github.com/ruby/logger rdoc 6.11.0 https://github.com/ruby/rdoc win32ole 1.9.1 https://github.com/ruby/win32ole +irb 1.15.1 https://github.com/ruby/irb +reline 0.6.0 https://github.com/ruby/reline +readline 0.0.4 https://github.com/ruby/readline diff --git a/lib/irb.rb b/lib/irb.rb deleted file mode 100644 index fd0bfe35c7..0000000000 --- a/lib/irb.rb +++ /dev/null @@ -1,736 +0,0 @@ -# frozen_string_literal: true - -# :markup: markdown -# irb.rb - irb main module -# by Keiju ISHITSUKA([email protected]) -# - -require "ripper" -require "reline" - -require_relative "irb/init" -require_relative "irb/context" -require_relative "irb/default_commands" - -require_relative "irb/ruby-lex" -require_relative "irb/statement" -require_relative "irb/history" -require_relative "irb/input-method" -require_relative "irb/locale" -require_relative "irb/color" - -require_relative "irb/version" -require_relative "irb/easter-egg" -require_relative "irb/debug" -require_relative "irb/pager" - -module IRB - - # An exception raised by IRB.irb_abort - class Abort < Exception;end # :nodoc: - - class << self - # 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 CurrentContext # :nodoc: - conf[:MAIN_CONTEXT] - end - - # Initializes IRB and creates a new Irb.irb object at the `TOPLEVEL_BINDING` - def start(ap_path = nil) - STDOUT.sync = true - $0 = File::basename(ap_path, ".rb") if ap_path - - setup(ap_path) - - if @CONF[:SCRIPT] - irb = Irb.new(nil, @CONF[:SCRIPT]) - else - irb = Irb.new - end - irb.run(@CONF) - end - - # Quits irb - def irb_exit(*) # :nodoc: - throw :IRB_EXIT, false - end - - # Aborts then interrupts irb. - # - # Will raise an Abort exception, or the given `exception`. - def irb_abort(irb, exception = Abort) # :nodoc: - irb.context.thread.raise exception, "abort then interrupt!" - end - 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 - - PROMPT_MAIN_TRUNCATE_LENGTH = 32 - 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, from_binding: false) - @from_binding = from_binding - @context = Context.new(self, workspace, input_method) - @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 - def debug_break - # it means the debug integration has been activated - if defined?(DEBUGGER__) && DEBUGGER__.respond_to?(:capture_frames_without_irb) - # after leaving this initial breakpoint, revert the capture_frames patch - DEBUGGER__.singleton_class.send(:alias_method, :capture_frames, :capture_frames_without_irb) - # and remove the redundant method - DEBUGGER__.singleton_class.send(:undef_method, :capture_frames_without_irb) - end - end - - def debug_readline(binding) - workspace = IRB::WorkSpace.new(binding) - 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` - # - # - # 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 History.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 - input = eval_input - ensure - context.io.save_history - end - else - input = eval_input - end - false - end - - Kernel.exit if forced_exit - - if input&.include?("\n") - @line_no += input.count("\n") - 1 - end - - input - end - - 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 - - load_history = !in_nested_session && context.io.support_history_saving? - save_history = load_history && History.save_history? - - if load_history - context.io.load_history - end - - prev_trap = trap("SIGINT") do - signal_handle - end - - begin - 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 - - # Evaluates input for this session. - def eval_input - configure_io - - 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 @context.with_debugger && statement.should_be_handled_by_debugger? - return statement.code - end - - @context.evaluate(statement, line_no) - - if @context.echo? && !statement.suppresses_echo? - if statement.is_assignment? - if @context.echo_on_assignment? - output_value(@context.echo_on_assignment? == :truncate) - end - else - output_value - end - end - rescue SystemExit, SignalException - raise - rescue Interrupt, Exception => exc - handle_exception(exc) - @context.workspace.local_variable_set(:_, exc) - end - end - end - end - - def read_input(prompt) - signal_status(:IN_INPUT) do - @context.io.prompt = prompt - if l = @context.io.gets - print l if @context.verbose? - else - if @context.ignore_eof? and @context.io.readable_after_eof? - l = "\n" - if @context.verbose? - printf "Use \"exit\" to leave %s\n", @context.ap_name - end - else - print "\n" if @context.prompting? - end - end - l - end - end - - def readmultiline - prompt = generate_prompt([], false, 0) - - # multiline - return read_input(prompt) if @context.io.respond_to?(:check_termination) - - # nomultiline - code = +'' - line_offset = 0 - loop do - line = read_input(prompt) - unless line - return code.empty? ? nil : code - end - - code << line - return code if command?(code) - - tokens, opens, terminated = @scanner.check_code_state(code, local_variables: @context.local_variables) - return code if terminated - - line_offset += 1 - continue = @scanner.should_continue?(tokens) - prompt = generate_prompt(opens, continue, line_offset) - end - end - - def each_top_level_statement - loop do - code = readmultiline - break unless code - yield parse_input(code), @line_no - @line_no += code.count("\n") - rescue RubyLex::TerminateLineInput - end - end - - def parse_input(code) - if code.match?(/\A\n*\z/) - return Statement::EmptyInput.new - end - - code = code.dup.force_encoding(@context.io.encoding) - is_assignment_expression = @scanner.assignment_expression?(code, local_variables: @context.local_variables) - - @context.parse_input(code, is_assignment_expression) - end - - def command?(code) - parse_input(code).is_a?(Statement::Command) - end - - def configure_io - if @context.io.respond_to?(:check_termination) - @context.io.check_termination do |code| - if Reline::IOGate.in_pasting? - rest = @scanner.check_termination_in_prev_line(code, local_variables: @context.local_variables) - if rest - Reline.delete_text - rest.bytes.reverse_each do |c| - Reline.ungetc(c) - end - true - else - false - end - else - next true if command?(code) - - _tokens, _opens, terminated = @scanner.check_code_state(code, local_variables: @context.local_variables) - terminated - end - end - end - if @context.io.respond_to?(:dynamic_prompt) - @context.io.dynamic_prompt do |lines| - 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. - tokens_until_line << token if token != tokens_until_line.last - end - continue = @scanner.should_continue?(tokens_until_line) - generate_prompt(next_opens, continue, line_num_offset) - end - end - end - - if @context.io.respond_to?(:auto_indent) and @context.auto_indent_mode - @context.io.auto_indent do |lines, line_index, byte_pointer, is_newline| - next nil if lines == [nil] # Workaround for exit IRB with CTRL+d - next nil if !is_newline && lines[line_index]&.byteslice(0, byte_pointer)&.match?(/\A\s*\z/) - - code = lines[0..line_index].map { |l| "#{l}\n" }.join - tokens = RubyLex.ripper_lex_without_warning(code, local_variables: @context.local_variables) - @scanner.process_indent_level(tokens, lines, line_index, is_newline) - end - end - end - - def convert_invalid_byte_sequence(str, enc) - str.force_encoding(enc) - str.scrub { |c| - c.bytes.map{ |b| "\\x#{b.to_s(16).upcase}" }.join - } - end - - def encode_with_invalid_byte_sequence(str, enc) - conv = Encoding::Converter.new(str.encoding, enc) - dst = String.new - begin - ret = conv.primitive_convert(str, dst) - case ret - when :invalid_byte_sequence - conv.insert_output(conv.primitive_errinfo[3].dump[1..-2]) - redo - when :undefined_conversion - c = conv.primitive_errinfo[3].dup.force_encoding(conv.primitive_errinfo[1]) - conv.insert_output(c.dump[1..-2]) - redo - when :incomplete_input - conv.insert_output(conv.primitive_errinfo[3].dump[1..-2]) - when :finished - end - break - end while nil - dst - end - - def handle_exception(exc) - if exc.backtrace[0] =~ /\/irb(2)?(\/.*|-.*|\.rb)?:/ && exc.class.to_s !~ /^IRB/ && - !(SyntaxError === exc) && !(EncodingError === exc) - # The backtrace of invalid encoding hash (ex. {"\xAE": 1}) raises EncodingError without lineno. - 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 - - highlight = Color.colorable? - - order = - if RUBY_VERSION < '3.0.0' - STDOUT.tty? ? :bottom : :top - else # '3.0.0' <= RUBY_VERSION - :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| - case order - when :top - lines = m.split("\n") - when :bottom - lines = m.split("\n").reverse - end - unless irb_bug - if lines.size > @context.back_trace_limit - omit = lines.size - @context.back_trace_limit - lines = lines[0..(@context.back_trace_limit - 1)] - lines << "\t... %d levels..." % omit - end - end - lines = lines.reverse if order == :bottom - 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 (?<open_quote>[`'])<(?<frame>top \(required\))>'/) { "(irb):#{$~[:num]}:in #{$~[:open_quote]}<main>'" } - puts message - - if irb_bug - puts "This may be an issue with IRB. If you believe this is an unexpected behavior, please report it to https://github.com/ruby/irb/issues" - end - rescue Exception => handler_exc - begin - puts exc.inspect - puts "backtraces are hidden because #{handler_exc} was raised when processing them" - rescue Exception - puts 'Uninspectable exception occurred' - end - end - - # 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. - 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 - begin - yield back_path, back_name - ensure - @context.irb_path = back_path if path - @context.irb_name = back_name if name - end - end - - # 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. - def suspend_workspace(workspace) - 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. - # - # 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} - begin - yield back_io - ensure - @context.instance_eval{@io = back_io} - end - end - - # Handler for the signal SIGINT, see Kernel#trap for more information. - def signal_handle - unless @context.ignore_sigint? - print "\nabort!\n" if @context.verbose? - exit - end - - case @signal_status - when :IN_INPUT - print "^C\n" - raise RubyLex::TerminateLineInput - when :IN_EVAL - IRB.irb_abort(self) - when :IN_LOAD - IRB.irb_abort(self, LoadAbort) - when :IN_IRB - # ignore - else - # ignore other cases as well - end - end - - # Evaluates the given block using the given `status`. - def signal_status(status) - return yield if @signal_status == :IN_LOAD - - signal_status_back = @signal_status - @signal_status = status - begin - yield - ensure - @signal_status = signal_status_back - end - end - - def output_value(omit = false) # :nodoc: - unless @context.return_format.include?('%') - puts @context.return_format - return - end - - winheight, winwidth = @context.io.winsize - if omit - content, overflow = Pager.take_first_page(winwidth, 1) do |out| - @context.inspect_last_value(out) - end - if overflow - content = "\n#{content}" if @context.newline_before_multiline_output? - content = "#{content}..." - content = "#{content}\e[0m" if Color.colorable? - end - puts format(@context.return_format, content.chomp) - elsif Pager.should_page? && @context.inspector_support_stream_output? - formatter_proc = ->(content, multipage) do - content = content.chomp - content = "\n#{content}" if @context.newline_before_multiline_output? && (multipage || content.include?("\n")) - format(@context.return_format, content) - end - Pager.page_with_preview(winwidth, winheight, formatter_proc) do |out| - @context.inspect_last_value(out) - end - else - content = @context.inspect_last_value.chomp - content = "\n#{content}" if @context.newline_before_multiline_output? && content.include?("\n") - Pager.page_content(format(@context.return_format, content), retain_content: true) - end - end - - # Outputs the local variables to this current session, including #signal_status - # and #context, using IRB::Locale. - def inspect - ary = [] - for iv in instance_variables - case (iv = iv.to_s) - when "@signal_status" - ary.push format("%s=:%s", iv, @signal_status.id2name) - when "@context" - ary.push format("%s=%s", iv, eval(iv).__to_s__) - else - ary.push format("%s=%s", iv, eval(iv)) - end - end - format("#<%s: %s>", self.class, ary.join(", ")) - end - - private - - def generate_prompt(opens, continue, line_offset) - ltype = @scanner.ltype_from_open_tokens(opens) - indent = @scanner.calc_indent_level(opens) - continue = opens.any? || continue - line_no = @line_no + line_offset - - if ltype - f = @context.prompt_s - elsif continue - f = @context.prompt_c - else - f = @context.prompt_i - end - f = "" unless f - if @context.prompting? - p = format_prompt(f, ltype, indent, line_no) - else - p = "" - end - if @context.auto_indent_mode and [email protected]_to?(:auto_indent) - unless ltype - prompt_i = @context.prompt_i.nil? ? "" : @context.prompt_i - ind = format_prompt(prompt_i, ltype, indent, line_no)[/.*\z/].size + - indent * 2 - p.size - p += " " * ind if ind > 0 - end - end - p - end - - def truncate_prompt_main(str) # :nodoc: - str = str.tr(CONTROL_CHARACTERS_PATTERN, ' ') - if str.size <= PROMPT_MAIN_TRUNCATE_LENGTH - str - else - str[0, PROMPT_MAIN_TRUNCATE_LENGTH - PROMPT_MAIN_TRUNCATE_OMISSION.size] + PROMPT_MAIN_TRUNCATE_OMISSION - end - end - - def format_prompt(format, ltype, indent, line_no) # :nodoc: - format.gsub(/%([0-9]+)?([a-zA-Z%])/) do - case $2 - when "N" - @context.irb_name - when "m" - main_str = @context.safe_method_call_on_main(:to_s) rescue "!#{$!.class}" - truncate_prompt_main(main_str) - when "M" - main_str = @context.safe_method_call_on_main(:inspect) rescue "!#{$!.class}" - truncate_prompt_main(main_str) - when "l" - ltype - when "i" - if indent < 0 - if $1 - "-".rjust($1.to_i) - else - "-" - end - else - if $1 - format("%" + $1 + "d", indent) - else - indent.to_s - end - end - when "n" - if $1 - format("%" + $1 + "d", line_no) - else - line_no.to_s - end - when "%" - "%" unless $1 - end - end - end - end -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. - # - # Given a Ruby file called `potato.rb` containing the following code: - # - # class Potato - # def initialize - # @cooked = false - # binding.irb - # puts "Cooked potato: #{@cooked}" - # end - # end - # - # Potato.new - # - # Running `ruby potato.rb` will open an IRB session where `binding.irb` is - # called, and you will see the following: - # - # $ ruby potato.rb - # - # From: potato.rb @ line 4 : - # - # 1: class Potato - # 2: def initialize - # 3: @cooked = false - # => 4: binding.irb - # 5: puts "Cooked potato: #{@cooked}" - # 6: end - # 7: end - # 8: - # 9: Potato.new - # - # irb(#<Potato:0x00007feea1916670>):001:0> - # - # You can type any valid Ruby code and it will be evaluated in the current - # context. This allows you to debug without having to run your code repeatedly: - # - # irb(#<Potato:0x00007feea1916670>):001:0> @cooked - # => false - # irb(#<Potato:0x00007feea1916670>):002:0> self.class - # => Potato - # irb(#<Potato:0x00007feea1916670>):003:0> caller.first - # => ".../2.5.1/lib/ruby/2.5.0/irb/workspace.rb:85:in `eval'" - # 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 - # output printed to standard output in this example: - # - # irb(#<Potato:0x00007feea1916670>):005:0> exit - # Cooked potato: true - # - # 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: []) 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 - STDOUT.print(workspace.code_around_binding) if show_code - # Get the original IRB instance - debugger_irb = IRB.instance_variable_get(:@debugger_irb) - - irb_path = File.expand_path(source_location[0]) - - 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.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. - 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, from_binding: true) - binding_irb.context.irb_path = irb_path - binding_irb.run(IRB.conf) - binding_irb.debug_break - end - end -end diff --git a/lib/irb/cmd/nop.rb b/lib/irb/cmd/nop.rb deleted file mode 100644 index 9d2e3c4d47..0000000000 --- a/lib/irb/cmd/nop.rb +++ /dev/null @@ -1,4 +0,0 @@ -# frozen_string_literal: true - -# 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/color.rb b/lib/irb/color.rb deleted file mode 100644 index a7e311087a..0000000000 --- a/lib/irb/color.rb +++ /dev/null @@ -1,263 +0,0 @@ -# frozen_string_literal: true -require 'reline' -require 'ripper' -require_relative 'ruby-lex' - -module IRB # :nodoc: - module Color - CLEAR = 0 - BOLD = 1 - UNDERLINE = 4 - REVERSE = 7 - BLACK = 30 - RED = 31 - GREEN = 32 - YELLOW = 33 - BLUE = 34 - MAGENTA = 35 - CYAN = 36 - WHITE = 37 - - TOKEN_KEYWORDS = { - on_kw: ['nil', 'self', 'true', 'false', '__FILE__', '__LINE__', '__ENCODING__'], - on_const: ['ENV'], - } - private_constant :TOKEN_KEYWORDS - - # A constant of all-bit 1 to match any Ripper's state in #dispatch_seq - ALL = -1 - private_constant :ALL - - begin - # Following pry's colors where possible, but sometimes having a compromise like making - # backtick and regexp as red (string's color, because they're sharing tokens). - TOKEN_SEQ_EXPRS = { - on_CHAR: [[BLUE, BOLD], ALL], - on_backtick: [[RED, BOLD], ALL], - on_comment: [[BLUE, BOLD], ALL], - on_const: [[BLUE, BOLD, UNDERLINE], ALL], - on_embexpr_beg: [[RED], ALL], - on_embexpr_end: [[RED], ALL], - on_embvar: [[RED], ALL], - on_float: [[MAGENTA, BOLD], ALL], - on_gvar: [[GREEN, BOLD], ALL], - on_backref: [[GREEN, BOLD], ALL], - on_heredoc_beg: [[RED], ALL], - on_heredoc_end: [[RED], ALL], - on_ident: [[BLUE, BOLD], Ripper::EXPR_ENDFN], - on_imaginary: [[BLUE, BOLD], ALL], - on_int: [[BLUE, BOLD], ALL], - on_kw: [[GREEN], ALL], - on_label: [[MAGENTA], ALL], - on_label_end: [[RED, BOLD], ALL], - on_qsymbols_beg: [[RED, BOLD], ALL], - on_qwords_beg: [[RED, BOLD], ALL], - on_rational: [[BLUE, BOLD], ALL], - on_regexp_beg: [[RED, BOLD], ALL], - on_regexp_end: [[RED, BOLD], ALL], - on_symbeg: [[YELLOW], ALL], - on_symbols_beg: [[RED, BOLD], ALL], - on_tstring_beg: [[RED, BOLD], ALL], - on_tstring_content: [[RED], ALL], - on_tstring_end: [[RED, BOLD], ALL], - on_words_beg: [[RED, BOLD], ALL], - on_parse_error: [[RED, REVERSE], ALL], - compile_error: [[RED, REVERSE], ALL], - on_assign_error: [[RED, REVERSE], ALL], - on_alias_error: [[RED, REVERSE], ALL], - on_class_name_error:[[RED, REVERSE], ALL], - on_param_error: [[RED, REVERSE], ALL], - on___end__: [[GREEN], ALL], - } - rescue NameError - # Give up highlighting Ripper-incompatible older Ruby - TOKEN_SEQ_EXPRS = {} - end - private_constant :TOKEN_SEQ_EXPRS - - ERROR_TOKENS = TOKEN_SEQ_EXPRS.keys.select { |k| k.to_s.end_with?('error') } - private_constant :ERROR_TOKENS - - class << self - def colorable? - 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) - else - supported - end - end - - def inspect_colorable?(obj, seen: {}.compare_by_identity) - case obj - when String, Symbol, Regexp, Integer, Float, FalseClass, TrueClass, NilClass - true - when Hash - without_circular_ref(obj, seen: seen) do - obj.all? { |k, v| inspect_colorable?(k, seen: seen) && inspect_colorable?(v, seen: seen) } - end - when Array - without_circular_ref(obj, seen: seen) do - obj.all? { |o| inspect_colorable?(o, seen: seen) } - end - when Range - inspect_colorable?(obj.begin, seen: seen) && inspect_colorable?(obj.end, seen: seen) - when Module - !obj.name.nil? - else - false - end - end - - def clear(colorable: colorable?) - return '' unless colorable - "\e[#{CLEAR}m" - end - - def colorize(text, seq, colorable: colorable?) - return text unless colorable - seq = seq.map { |s| "\e[#{const_get(s)}m" }.join('') - "#{seq}#{text}#{clear(colorable: colorable)}" - end - - # If `complete` is false (code is incomplete), this does not warn compile_error. - # This option is needed to avoid warning a user when the compile_error is happening - # because the input is not wrong but just incomplete. - def colorize_code(code, complete: true, ignore_error: false, colorable: colorable?, local_variables: []) - return code unless colorable - - symbol_state = SymbolState.new - colored = +'' - lvars_code = RubyLex.generate_local_variables_assign_code(local_variables) - code_with_lvars = lvars_code ? "#{lvars_code}\n#{code}" : code - - scan(code_with_lvars, allow_last_error: !complete) do |token, str, expr| - # handle uncolorable code - if token.nil? - colored << Reline::Unicode.escape_for_print(str) - next - end - - # IRB::ColorPrinter skips colorizing fragments with any invalid token - if ignore_error && ERROR_TOKENS.include?(token) - return Reline::Unicode.escape_for_print(code) - end - - in_symbol = symbol_state.scan_token(token) - str.each_line do |line| - line = Reline::Unicode.escape_for_print(line) - if seq = dispatch_seq(token, expr, line, in_symbol: in_symbol) - colored << seq.map { |s| "\e[#{s}m" }.join('') - colored << line.sub(/\Z/, clear(colorable: colorable)) - else - colored << line - end - end - end - - if lvars_code - raise "#{lvars_code.dump} should have no \\n" if lvars_code.include?("\n") - colored.sub!(/\A.+\n/, '') # delete_prefix lvars_code with colors - end - colored - end - - private - - def without_circular_ref(obj, seen:, &block) - return false if seen.key?(obj) - seen[obj] = true - block.call - ensure - seen.delete(obj) - end - - def scan(code, allow_last_error:) - verbose, $VERBOSE = $VERBOSE, nil - RubyLex.compile_with_errors_suppressed(code) do |inner_code, line_no| - lexer = Ripper::Lexer.new(inner_code, '(ripper)', line_no) - byte_pos = 0 - line_positions = [0] - inner_code.lines.each do |line| - line_positions << line_positions.last + line.bytesize - end - - on_scan = proc do |elem| - start_pos = line_positions[elem.pos[0] - 1] + elem.pos[1] - - # yield uncolorable code - if byte_pos < start_pos - yield(nil, inner_code.byteslice(byte_pos...start_pos), nil) - end - - if byte_pos <= start_pos - str = elem.tok - yield(elem.event, str, elem.state) - byte_pos = start_pos + str.bytesize - end - end - - lexer.scan.each do |elem| - next if allow_last_error and /meets end of file|unexpected end-of-input/ =~ elem.message - on_scan.call(elem) - end - # yield uncolorable DATA section - yield(nil, inner_code.byteslice(byte_pos...inner_code.bytesize), nil) if byte_pos < inner_code.bytesize - end - ensure - $VERBOSE = verbose - end - - def dispatch_seq(token, expr, str, in_symbol:) - if ERROR_TOKENS.include?(token) - TOKEN_SEQ_EXPRS[token][0] - elsif in_symbol - [YELLOW] - elsif TOKEN_KEYWORDS.fetch(token, []).include?(str) - [CYAN, BOLD] - elsif (seq, exprs = TOKEN_SEQ_EXPRS[token]; (expr & (exprs || 0)) != 0) - seq - else - nil - end - end - end - - # A class to manage a state to know whether the current token is for Symbol or not. - class SymbolState - def initialize - # Push `true` to detect Symbol. `false` to increase the nest level for non-Symbol. - @stack = [] - end - - # Return true if the token is a part of Symbol. - def scan_token(token) - prev_state = @stack.last - case token - when :on_symbeg, :on_symbols_beg, :on_qsymbols_beg - @stack << true - when :on_ident, :on_op, :on_const, :on_ivar, :on_cvar, :on_gvar, :on_kw, :on_backtick - if @stack.last # Pop only when it's Symbol - @stack.pop - return prev_state - end - when :on_tstring_beg - @stack << false - when :on_embexpr_beg - @stack << false - return prev_state - when :on_tstring_end # :on_tstring_end may close Symbol - @stack.pop - return prev_state - when :on_embexpr_end - @stack.pop - end - @stack.last - end - end - private_constant :SymbolState - end -end diff --git a/lib/irb/color_printer.rb b/lib/irb/color_printer.rb deleted file mode 100644 index 7a7e817858..0000000000 --- a/lib/irb/color_printer.rb +++ /dev/null @@ -1,56 +0,0 @@ -# frozen_string_literal: true -require 'pp' -require_relative 'color' - -module IRB - class ColorPrinter < ::PP - class << self - def pp(obj, out = $>, width = screen_width, colorize: true) - q = ColorPrinter.new(out, width, colorize: colorize) - q.guard_inspect_key {q.pp obj} - q.flush - out << "\n" - end - - private - - def screen_width - Reline.get_screen_size.last - rescue Errno::EINVAL # in `winsize': Invalid argument - <STDIN> - 79 - end - end - - def initialize(out, width, colorize: true) - @colorize = colorize - - super(out, width) - end - - def pp(obj) - if String === obj - # Avoid calling Ruby 2.4+ String#pretty_print that splits a string by "\n" - text(obj.inspect) - else - super - end - end - - def text(str, width = nil) - unless str.is_a?(String) - str = str.inspect - end - width ||= str.length - - case str - when '' - when ',', '=>', '[', ']', '{', '}', '..', '...', /\A@\w+\z/ - super(str, width) - when /\A#</, '=', '>' - super(@colorize ? Color.colorize(str, [:GREEN]) : str, width) - else - super(@colorize ? Color.colorize_code(str, ignore_error: true) : str, width) - end - end - end -end diff --git a/lib/irb/command.rb b/lib/irb/command.rb deleted file mode 100644 index 68a4b52727..0000000000 --- a/lib/irb/command.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true -# -# irb/command.rb - irb command -# by Keiju ISHITSUKA([email protected]) -# - -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 deleted file mode 100644 index 687bb075ac..0000000000 --- a/lib/irb/command/backtrace.rb +++ /dev/null @@ -1,17 +0,0 @@ -# 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 deleted file mode 100644 index 2f39b75cca..0000000000 --- a/lib/irb/command/base.rb +++ /dev/null @@ -1,60 +0,0 @@ -# frozen_string_literal: true -# -# nop.rb - -# by Keiju ISHITSUKA([email protected]) -# - -module IRB - module Command - class CommandArgumentError < StandardError; end # :nodoc: - - class << self - def extract_ruby_args(*args, **kwargs) # :nodoc: - throw :EXTRACT_RUBY_ARGS, [args, kwargs] - end - 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 - - def execute(irb_context, arg) - new(irb_context).execute(arg) - rescue CommandArgumentError => e - puts e.message - end - - private - - def highlight(text) - Color.colorize(text, [:BOLD, :BLUE]) - end - end - - def initialize(irb_context) - @irb_context = irb_context - end - - attr_reader :irb_context - - def execute(arg) - #nop - end - end - - Nop = Base # :nodoc: - end -end diff --git a/lib/irb/command/break.rb b/lib/irb/command/break.rb deleted file mode 100644 index a8f81fe665..0000000000 --- a/lib/irb/command/break.rb +++ /dev/null @@ -1,17 +0,0 @@ -# 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 deleted file mode 100644 index 529dcbca5a..0000000000 --- a/lib/irb/command/catch.rb +++ /dev/null @@ -1,17 +0,0 @@ -# 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/cd.rb b/lib/irb/command/cd.rb deleted file mode 100644 index b83c8689ae..0000000000 --- a/lib/irb/command/cd.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true - -module IRB - module Command - class CD < Base - category "Workspace" - description "Move into the given object or leave the current context." - - help_message(<<~HELP) - Usage: cd ([target]|..) - - IRB uses a stack of workspaces to keep track of context(s), with `pushws` and `popws` commands to manipulate the stack. - The `cd` command is an attempt to simplify the operation and will be subject to change. - - When given: - - an object, cd will use that object as the new context by pushing it onto the workspace stack. - - "..", cd will leave the current context by popping the top workspace off the stack. - - no arguments, cd will move to the top workspace on the stack by popping off all workspaces. - - Examples: - - cd Foo - cd Foo.new - cd @ivar - cd .. - cd - HELP - - def execute(arg) - case arg - when ".." - irb_context.pop_workspace - when "" - # TODO: decide what workspace commands should be kept, and underlying APIs should look like, - # and perhaps add a new API to clear the workspace stack. - prev_workspace = irb_context.pop_workspace - while prev_workspace - prev_workspace = irb_context.pop_workspace - end - else - begin - obj = eval(arg, irb_context.workspace.binding) - irb_context.push_workspace(obj) - rescue StandardError => e - warn "Error: #{e}" - end - end - end - end - end -end diff --git a/lib/irb/command/chws.rb b/lib/irb/command/chws.rb deleted file mode 100644 index ef456d0961..0000000000 --- a/lib/irb/command/chws.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true -# -# change-ws.rb - -# by Keiju ISHITSUKA([email protected]) -# -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 deleted file mode 100644 index b4fc807343..0000000000 --- a/lib/irb/command/context.rb +++ /dev/null @@ -1,16 +0,0 @@ -# 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/command/continue.rb b/lib/irb/command/continue.rb deleted file mode 100644 index 0daa029b15..0000000000 --- a/lib/irb/command/continue.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -require_relative "debug" - -module IRB - # :stopdoc: - - module Command - class Continue < DebugCommand - def execute(arg) - execute_debug_command(do_cmds: "continue #{arg}") - end - end - end - - # :startdoc: -end diff --git a/lib/irb/command/copy.rb b/lib/irb/command/copy.rb deleted file mode 100644 index 93410b878a..0000000000 --- a/lib/irb/command/copy.rb +++ /dev/null @@ -1,73 +0,0 @@ -# frozen_string_literal: true - -module IRB - module Command - class Copy < Base - category "Misc" - description "Copy expression output to clipboard" - - help_message(<<~HELP) - Usage: copy ([expression]) - - When given: - - an expression, copy the inspect result of the expression to the clipboard. - - no arguments, copy the last evaluated result (`_`) to the clipboard. - - Examples: - - copy Foo.new - copy User.all.to_a - copy - HELP - - def execute(arg) - # Copy last value if no expression was supplied - arg = '_' if arg.to_s.strip.empty? - - value = irb_context.workspace.binding.eval(arg) - output = irb_context.inspect_method.inspect_value(value, +'', colorize: false).chomp - - if clipboard_available? - copy_to_clipboard(output) - else - warn "System clipboard not found" - end - rescue StandardError => e - warn "Error: #{e}" - end - - private - - def copy_to_clipboard(text) - IO.popen(clipboard_program, 'w') do |io| - io.write(text) - end - - raise IOError.new("Copying to clipboard failed") unless $? == 0 - - puts "Copied to system clipboard" - rescue Errno::ENOENT => e - warn e.message - warn "Is IRB.conf[:COPY_COMMAND] set to a bad value?" - end - - def clipboard_program - @clipboard_program ||= if IRB.conf[:COPY_COMMAND] - IRB.conf[:COPY_COMMAND] - elsif executable?("pbcopy") - "pbcopy" - elsif executable?("xclip") - "xclip -selection clipboard" - end - end - - def executable?(command) - system("which #{command} > /dev/null 2>&1") - end - - def clipboard_available? - !!clipboard_program - end - end - end -end diff --git a/lib/irb/command/debug.rb b/lib/irb/command/debug.rb deleted file mode 100644 index 3ebb57fe54..0000000000 --- a/lib/irb/command/debug.rb +++ /dev/null @@ -1,73 +0,0 @@ -require_relative "../debug" - -module IRB - # :stopdoc: - - module Command - class Debug < Base - category "Debugging" - description "Start the debugger of debug.gem." - - 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 - - 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 - throw :IRB_EXIT, cmd - else - puts "IRB is already running with a debug session." - return - end - else - # If IRB is not running with a debug session yet, then: - # 1. Check if the debugging command is run from a `binding.irb` call. - # 2. If so, try setting up the debug gem. - # 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 irb_context.from_binding? - puts "Debugging commands are only available when IRB is started with binding.irb" - return - end - - if IRB.respond_to?(:JobManager) - warn "Can't start the debugger when IRB is running in a multi-IRB session." - return - end - - unless IRB::Debug.setup(irb_context.irb) - puts <<~MSG - You need to install the debug gem before using this command. - If you use `bundle exec`, please add `gem "debug"` into your Gemfile. - MSG - return - end - - IRB::Debug.insert_debug_break(pre_cmds: pre_cmds, do_cmds: do_cmds) - - # exit current Irb#run call - throw :IRB_EXIT - end - end - end - - class DebugCommand < Debug - class << self - def category - "Debugging" - end - - def description - command_name = self.name.split("::").last.downcase - "Start the debugger of debug.gem and run its `#{command_name}` command." - end - end - end - end -end diff --git a/lib/irb/command/delete.rb b/lib/irb/command/delete.rb deleted file mode 100644 index 2a57a4a3de..0000000000 --- a/lib/irb/command/delete.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -require_relative "debug" - -module IRB - # :stopdoc: - - module Command - class Delete < DebugCommand - def execute(arg) - execute_debug_command(pre_cmds: "delete #{arg}") - end - end - end - - # :startdoc: -end diff --git a/lib/irb/command/disable_irb.rb b/lib/irb/command/disable_irb.rb deleted file mode 100644 index 0b00d0302b..0000000000 --- a/lib/irb/command/disable_irb.rb +++ /dev/null @@ -1,19 +0,0 @@ -# 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 deleted file mode 100644 index cb7e0c4873..0000000000 --- a/lib/irb/command/edit.rb +++ /dev/null @@ -1,63 +0,0 @@ -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 deleted file mode 100644 index b4436f0343..0000000000 --- a/lib/irb/command/exit.rb +++ /dev/null @@ -1,18 +0,0 @@ -# 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/command/finish.rb b/lib/irb/command/finish.rb deleted file mode 100644 index 3311a0e6e9..0000000000 --- a/lib/irb/command/finish.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -require_relative "debug" - -module IRB - # :stopdoc: - - module Command - class Finish < DebugCommand - def execute(arg) - execute_debug_command(do_cmds: "finish #{arg}") - end - end - end - - # :startdoc: -end diff --git a/lib/irb/command/force_exit.rb b/lib/irb/command/force_exit.rb deleted file mode 100644 index 14086aa849..0000000000 --- a/lib/irb/command/force_exit.rb +++ /dev/null @@ -1,18 +0,0 @@ -# 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 deleted file mode 100644 index 12b468fefc..0000000000 --- a/lib/irb/command/help.rb +++ /dev/null @@ -1,83 +0,0 @@ -# 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(:@command_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/command/history.rb b/lib/irb/command/history.rb deleted file mode 100644 index e385c66102..0000000000 --- a/lib/irb/command/history.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -require "stringio" - -require_relative "../pager" - -module IRB - # :stopdoc: - - module Command - class History < Base - category "IRB" - description "Shows the input history. `-g [query]` or `-G [query]` allows you to filter the output." - - def execute(arg) - - if (match = arg&.match(/(-g|-G)\s+(?<grep>.+)\s*\z/)) - grep = Regexp.new(match[:grep]) - end - - formatted_inputs = irb_context.io.class::HISTORY.each_with_index.reverse_each.filter_map do |input, index| - next if grep && !input.match?(grep) - - header = "#{index}: " - - first_line, *other_lines = input.split("\n") - first_line = "#{header}#{first_line}" - - truncated_lines = other_lines.slice!(1..) # Show 1 additional line (2 total) - other_lines << "..." if truncated_lines&.any? - - other_lines.map! do |line| - " " * header.length + line - end - - [first_line, *other_lines].join("\n") + "\n" - end - - Pager.page_content(formatted_inputs.join) - end - end - end - - # :startdoc: -end diff --git a/lib/irb/command/info.rb b/lib/irb/command/info.rb deleted file mode 100644 index d08ce00a32..0000000000 --- a/lib/irb/command/info.rb +++ /dev/null @@ -1,17 +0,0 @@ -# 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 deleted file mode 100644 index a01ddb1d45..0000000000 --- a/lib/irb/command/internal_helpers.rb +++ /dev/null @@ -1,27 +0,0 @@ -# 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/command/irb_info.rb b/lib/irb/command/irb_info.rb deleted file mode 100644 index 6d868de94c..0000000000 --- a/lib/irb/command/irb_info.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -module IRB - # :stopdoc: - - module Command - class IrbInfo < Base - category "IRB" - description "Show information about IRB." - - 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" - 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? - str += "East Asian Ambiguous Width: #{Reline.ambiguous_width.inspect}\n" - if RbConfig::CONFIG['host_os'] =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/ - codepage = `chcp`.b.sub(/.*: (\d+)\n/, '\1') - str += "Code page: #{codepage}\n" - end - puts str - nil - end - end - end - - # :startdoc: -end diff --git a/lib/irb/command/load.rb b/lib/irb/command/load.rb deleted file mode 100644 index 1cd3f279d1..0000000000 --- a/lib/irb/command/load.rb +++ /dev/null @@ -1,91 +0,0 @@ -# frozen_string_literal: true -# -# load.rb - -# by Keiju ISHITSUKA([email protected]) -# -require_relative "../ext/loader" - -module IRB - # :stopdoc: - - module Command - class LoaderCommand < Base - include RubyArgsExtractor - include IrbLoader - - def raise_cmd_argument_error - raise CommandArgumentError.new("Please specify the file name.") - end - end - - class Load < LoaderCommand - category "IRB" - description "Load a Ruby file." - - 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 - end - - class Require < LoaderCommand - category "IRB" - description "Require a Ruby file." - - 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)?") - return false if $".find{|f| f =~ rex} - - case file_name - when /\.rb$/ - begin - if irb_load(file_name) - $".push file_name - return true - end - rescue LoadError - end - when /\.(so|o|sl)$/ - return ruby_require(file_name) - end - - begin - irb_load(f = file_name + ".rb") - $".push f - return true - rescue LoadError - return ruby_require(file_name) - end - end - end - - class Source < LoaderCommand - category "IRB" - description "Loads a given file in the current session." - - 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) - end - end - end - # :startdoc: -end diff --git a/lib/irb/command/ls.rb b/lib/irb/command/ls.rb deleted file mode 100644 index 944efd7570..0000000000 --- a/lib/irb/command/ls.rb +++ /dev/null @@ -1,167 +0,0 @@ -# frozen_string_literal: true - -require "reline" -require "stringio" - -require_relative "../pager" -require_relative "../color" - -module IRB - # :stopdoc: - - module Command - class Ls < Base - class EvaluationError < StandardError; end - - category "Context" - 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 evaluate(code) - @irb_context.workspace.binding.eval(code) - rescue Exception => e - puts "#{e.class}: #{e.message}" - raise EvaluationError - end - - def execute(arg) - if match = arg.match(/\A(?<target>.+\s|)(-g|-G)\s+(?<grep>.+)$/) - target = match[:target] - grep = Regexp.new(match[:grep]) - elsif match = arg.match(/\A((?<target>.+),|)\s*grep:(?<grep>.+)/) - # Legacy style `ls obj, grep: /regexp/` - # Evaluation order should be eval(target) then eval(grep) - target = match[:target] || '' - grep_regexp_code = match[:grep] - else - target = arg.strip - end - - if target.empty? - obj = irb_context.workspace.main - locals = irb_context.workspace.binding.local_variables - else - obj = evaluate(target) - end - - if grep_regexp_code - grep = evaluate(grep_regexp_code) - end - - o = Output.new(grep: grep) - - 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) if locals - o.print_result - rescue EvaluationError - end - - def dump_methods(o, klass, obj) - singleton_class = begin obj.singleton_class; rescue TypeError; nil end - dumped_mods = Array.new - ancestors = klass.ancestors - ancestors = ancestors.reject { |c| c >= Object } if klass < Object - singleton_ancestors = (singleton_class&.ancestors || []).reject { |c| c >= Class } - - # singleton_class' ancestors should be at the front - maps = class_method_map(singleton_ancestors, dumped_mods) + class_method_map(ancestors, dumped_mods) - maps.each do |mod, methods| - name = mod == singleton_class ? "#{klass}.methods" : "#{mod}#methods" - o.dump(name, methods) - end - end - - def class_method_map(classes, dumped_mods) - dumped_methods = Array.new - classes.map do |mod| - next if dumped_mods.include? mod - - dumped_mods << mod - - methods = mod.public_instance_methods(false).select do |method| - if dumped_methods.include? method - false - else - dumped_methods << method - true - end - end - - [mod, methods] - end.compact - end - - class Output - MARGIN = " " - - def initialize(grep: nil) - @grep = grep - @line_width = screen_width - MARGIN.length # right padding - @io = StringIO.new - end - - def print_result - Pager.page_content(@io.string) - end - - def dump(name, strs) - strs = strs.grep(@grep) if @grep - strs = strs.sort - return if strs.empty? - - # Attempt a single line - @io.print "#{Color.colorize(name, [:BOLD, :BLUE])}: " - if fits_on_line?(strs, cols: strs.size, offset: "#{name}: ".length) - @io.puts strs.join(MARGIN) - return - end - @io.puts - - # Dump with the largest # of columns that fits on a line - cols = strs.size - until fits_on_line?(strs, cols: cols, offset: MARGIN.length) || cols == 1 - cols -= 1 - end - widths = col_widths(strs, cols: cols) - strs.each_slice(cols) do |ss| - @io.puts ss.map.with_index { |s, i| "#{MARGIN}%-#{widths[i]}s" % s }.join - end - end - - private - - def fits_on_line?(strs, cols:, offset: 0) - width = col_widths(strs, cols: cols).sum + MARGIN.length * (cols - 1) - width <= @line_width - offset - end - - def col_widths(strs, cols:) - cols.times.map do |col| - (col...strs.size).step(cols).map do |i| - strs[i].length - end.max - end - end - - def screen_width - Reline.get_screen_size.last - rescue Errno::EINVAL # in `winsize': Invalid argument - <STDIN> - 80 - end - end - private_constant :Output - end - end - - # :startdoc: -end diff --git a/lib/irb/command/measure.rb b/lib/irb/command/measure.rb deleted file mode 100644 index f96be20de8..0000000000 --- a/lib/irb/command/measure.rb +++ /dev/null @@ -1,49 +0,0 @@ -module IRB - # :stopdoc: - - module Command - class Measure < Base - include RubyArgsExtractor - - category "Misc" - description "`measure` enables the mode to measure processing time. `measure :off` disables it." - - def initialize(*args) - super(*args) - end - - 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 - IRB.unset_measure_callback(arg) - when :list - IRB.conf[:MEASURE_CALLBACKS].each do |type_name, _, arg_val| - puts "- #{type_name}" + (arg_val ? "(#{arg_val.inspect})" : '') - end - when :on - added = IRB.set_measure_callback(arg) - puts "#{added[0]} is added." if added - else - added = IRB.set_measure_callback(type, arg) - puts "#{added[0]} is added." if added - end - nil - end - end - end - - # :startdoc: -end diff --git a/lib/irb/command/next.rb b/lib/irb/command/next.rb deleted file mode 100644 index 3fc6b68d21..0000000000 --- a/lib/irb/command/next.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -require_relative "debug" - -module IRB - # :stopdoc: - - module Command - class Next < DebugCommand - def execute(arg) - execute_debug_command(do_cmds: "next #{arg}") - end - end - end - - # :startdoc: -end diff --git a/lib/irb/command/pushws.rb b/lib/irb/command/pushws.rb deleted file mode 100644 index b51928c650..0000000000 --- a/lib/irb/command/pushws.rb +++ /dev/null @@ -1,65 +0,0 @@ -# frozen_string_literal: true -# -# change-ws.rb - -# by Keiju ISHITSUKA([email protected]) -# - -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 deleted file mode 100644 index 8a2188e4eb..0000000000 --- a/lib/irb/command/show_doc.rb +++ /dev/null @@ -1,51 +0,0 @@ -# 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 deleted file mode 100644 index f4c6f104a2..0000000000 --- a/lib/irb/command/show_source.rb +++ /dev/null @@ -1,74 +0,0 @@ -# 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/command/step.rb b/lib/irb/command/step.rb deleted file mode 100644 index 29e5e35ac0..0000000000 --- a/lib/irb/command/step.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -require_relative "debug" - -module IRB - # :stopdoc: - - module Command - class Step < DebugCommand - def execute(arg) - execute_debug_command(do_cmds: "step #{arg}") - end - end - end - - # :startdoc: -end diff --git a/lib/irb/command/subirb.rb b/lib/irb/command/subirb.rb deleted file mode 100644 index 85af28c1a5..0000000000 --- a/lib/irb/command/subirb.rb +++ /dev/null @@ -1,123 +0,0 @@ -# frozen_string_literal: true -# -# multi.rb - -# by Keiju ISHITSUKA([email protected]) -# - -module IRB - # :stopdoc: - - module Command - class MultiIRBCommand < Base - include RubyArgsExtractor - - private - - def print_deprecated_warning - warn <<~MSG - Multi-irb commands are deprecated and will be removed in IRB 2.0.0. Please use workspace commands instead. - If you have any use case for multi-irb, please leave a comment at https://github.com/ruby/irb/issues/653 - MSG - end - - def extend_irb_context - # this extension patches IRB context like IRB.CurrentContext - require_relative "../ext/multi-irb" - end - - def print_debugger_warning - warn "Multi-IRB commands are not available when the debugger is enabled." - end - end - - class IrbCommand < MultiIRBCommand - category "Multi-irb (DEPRECATED)" - description "Start a child IRB." - - 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 - print_debugger_warning - return - end - - extend_irb_context - IRB.irb(nil, *obj) - puts IRB.JobManager.inspect - end - end - - class Jobs < MultiIRBCommand - category "Multi-irb (DEPRECATED)" - description "List of current sessions." - - def execute(_arg) - print_deprecated_warning - - if irb_context.with_debugger - print_debugger_warning - return - end - - extend_irb_context - puts IRB.JobManager.inspect - end - end - - class Foreground < MultiIRBCommand - category "Multi-irb (DEPRECATED)" - description "Switches to the session of the given number." - - 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 - print_debugger_warning - return - end - - 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 - - class Kill < MultiIRBCommand - category "Multi-irb (DEPRECATED)" - description "Kills the session with the given number." - - 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 - print_debugger_warning - return - end - - extend_irb_context - IRB.JobManager.kill(*keys) - puts IRB.JobManager.inspect - end - end - end - - # :startdoc: -end diff --git a/lib/irb/command/whereami.rb b/lib/irb/command/whereami.rb deleted file mode 100644 index c8439f1212..0000000000 --- a/lib/irb/command/whereami.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -module IRB - # :stopdoc: - - module Command - class Whereami < Base - category "Context" - description "Show the source code around binding.irb again." - - def execute(_arg) - code = irb_context.workspace.code_around_binding - if code - puts code - else - puts "The current context doesn't have code." - end - end - end - end - - # :startdoc: -end diff --git a/lib/irb/completion.rb b/lib/irb/completion.rb deleted file mode 100644 index 3e97047067..0000000000 --- a/lib/irb/completion.rb +++ /dev/null @@ -1,504 +0,0 @@ -# frozen_string_literal: true -# -# irb/completion.rb - -# by Keiju ISHITSUKA([email protected]) -# From Original Idea of [email protected] -# - -require_relative 'ruby-lex' - -module IRB - class BaseCompletor # :nodoc: - - # Set of reserved words used by Ruby, you should not use these for - # constants or variables - ReservedWords = %w[ - __ENCODING__ __LINE__ __FILE__ - BEGIN END - alias and - begin break - case class - def defined? do - else elsif end ensure - false for - if in - module - next nil not - or - redo rescue retry return - self super - then true - undef unless until - when while - yield - ] - - HELP_COMMAND_PREPOSING = /\Ahelp\s+/ - - def completion_candidates(preposing, target, postposing, bind:) - raise NotImplementedError - end - - def doc_namespace(preposing, matched, postposing, bind:) - raise NotImplementedError - end - - GEM_PATHS = - if defined?(Gem::Specification) - Gem::Specification.latest_specs(true).map { |s| - s.require_paths.map { |p| - if File.absolute_path?(p) - p - else - File.join(s.full_gem_path, p) - end - } - }.flatten - else - [] - end.freeze - - def retrieve_gem_and_system_load_path - candidates = (GEM_PATHS | $LOAD_PATH) - candidates.map do |p| - if p.respond_to?(:to_path) - p.to_path - else - String(p) rescue nil - end - end.compact.sort - end - - def retrieve_files_to_require_from_load_path - @files_from_load_path ||= - ( - shortest = [] - rest = retrieve_gem_and_system_load_path.each_with_object([]) { |path, result| - begin - names = Dir.glob("**/*.{rb,#{RbConfig::CONFIG['DLEXT']}}", base: path) - rescue Errno::ENOENT - nil - end - next if names.empty? - names.map! { |n| n.sub(/\.(rb|#{RbConfig::CONFIG['DLEXT']})\z/, '') }.sort! - shortest << names.shift - result.concat(names) - } - shortest.sort! | rest - ) - end - - def command_candidates(target) - if !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/, '') - } - end - end - - class TypeCompletor < BaseCompletor # :nodoc: - def initialize(context) - @context = context - end - - def inspect - ReplTypeCompletor.info - end - - def completion_candidates(preposing, target, _postposing, bind:) - # When completing the argument of `help` command, only commands should be candidates - return command_candidates(target) if preposing.match?(HELP_COMMAND_PREPOSING) - - commands = if preposing.empty? - command_candidates(target) - # It doesn't make sense to propose commands with other preposing - else - [] - end - - result = ReplTypeCompletor.analyze(preposing + target, binding: bind, filename: @context.irb_path) - - return commands unless result - - commands | result.completion_candidates.map { target + _1 } - end - - def doc_namespace(preposing, matched, _postposing, bind:) - result = ReplTypeCompletor.analyze(preposing + matched, binding: bind, filename: @context.irb_path) - result&.doc_namespace('') - end - end - - class RegexpCompletor < BaseCompletor # :nodoc: - KERNEL_METHODS = ::Kernel.instance_method(:methods) - KERNEL_PRIVATE_METHODS = ::Kernel.instance_method(:private_methods) - KERNEL_INSTANCE_VARIABLES = ::Kernel.instance_method(:instance_variables) - OBJECT_CLASS_INSTANCE_METHOD = ::Object.instance_method(:class) - MODULE_CONSTANTS_INSTANCE_METHOD = ::Module.instance_method(:constants) - - using Module.new { - refine ::Binding do - def eval_methods - KERNEL_METHODS.bind_call(receiver) - end - - def eval_private_methods - KERNEL_PRIVATE_METHODS.bind_call(receiver) - end - - def eval_instance_variables - KERNEL_INSTANCE_VARIABLES.bind_call(receiver) - end - - def eval_global_variables - ::Kernel.global_variables - end - - def eval_class_constants - klass = OBJECT_CLASS_INSTANCE_METHOD.bind_call(receiver) - MODULE_CONSTANTS_INSTANCE_METHOD.bind_call(klass) - end - end - } - - def inspect - 'RegexpCompletor' - end - - def complete_require_path(target, preposing, postposing) - if target =~ /\A(['"])([^'"]+)\Z/ - quote = $1 - actual_target = $2 - else - return nil # It's not String literal - end - tokens = RubyLex.ripper_lex_without_warning(preposing.gsub(/\s*\z/, '')) - tok = nil - tokens.reverse_each do |t| - unless [:on_lparen, :on_sp, :on_ignored_sp, :on_nl, :on_ignored_nl, :on_comment].include?(t.event) - tok = t - break - end - end - return unless tok&.event == :on_ident && tok.state == Ripper::EXPR_CMDARG - - case tok.tok - when 'require' - retrieve_files_to_require_from_load_path.select { |path| - path.start_with?(actual_target) - }.map { |path| - quote + path - } - when 'require_relative' - retrieve_files_to_require_relative_from_current_dir.select { |path| - path.start_with?(actual_target) - }.map { |path| - quote + path - } - end - end - - def completion_candidates(preposing, target, postposing, bind:) - if result = complete_require_path(target, preposing, postposing) - return result - end - - commands = command_candidates(target) - - # When completing the argument of `help` command, only commands should be candidates - return commands if preposing.match?(HELP_COMMAND_PREPOSING) - - # It doesn't make sense to propose commands with other preposing - commands = [] unless preposing.empty? - - completion_data = retrieve_completion_data(target, bind: bind, doc_namespace: false).compact.map{ |i| i.encode(Encoding.default_external) } - commands | completion_data - end - - def doc_namespace(_preposing, matched, _postposing, bind:) - retrieve_completion_data(matched, bind: bind, doc_namespace: true) - end - - def retrieve_completion_data(input, bind:, doc_namespace:) - case input - # this regexp only matches the closing character because of irb's Reline.completer_quote_characters setting - # details are described in: https://github.com/ruby/irb/pull/523 - when /^(.*["'`])\.([^.]*)$/ - # String - receiver = $1 - message = $2 - - if doc_namespace - "String.#{message}" - else - candidates = String.instance_methods.collect{|m| m.to_s} - select_message(receiver, message, candidates) - end - - # this regexp only matches the closing character because of irb's Reline.completer_quote_characters setting - # details are described in: https://github.com/ruby/irb/pull/523 - when /^(.*\/)\.([^.]*)$/ - # Regexp - receiver = $1 - message = $2 - - if doc_namespace - "Regexp.#{message}" - else - candidates = Regexp.instance_methods.collect{|m| m.to_s} - select_message(receiver, message, candidates) - end - - when /^([^\]]*\])\.([^.]*)$/ - # Array - receiver = $1 - message = $2 - - if doc_namespace - "Array.#{message}" - else - candidates = Array.instance_methods.collect{|m| m.to_s} - select_message(receiver, message, candidates) - end - - when /^([^\}]*\})\.([^.]*)$/ - # Hash or Proc - receiver = $1 - message = $2 - - if doc_namespace - ["Hash.#{message}", "Proc.#{message}"] - else - hash_candidates = Hash.instance_methods.collect{|m| m.to_s} - proc_candidates = Proc.instance_methods.collect{|m| m.to_s} - select_message(receiver, message, hash_candidates | proc_candidates) - end - - when /^(:[^:.]+)$/ - # Symbol - if doc_namespace - nil - else - sym = $1 - candidates = Symbol.all_symbols.collect do |s| - s.inspect - rescue EncodingError - # ignore - end - candidates.grep(/^#{Regexp.quote(sym)}/) - end - when /^::([A-Z][^:\.\(\)]*)$/ - # Absolute Constant or class methods - receiver = $1 - - candidates = Object.constants.collect{|m| m.to_s} - - if doc_namespace - candidates.find { |i| i == receiver } - else - candidates.grep(/^#{Regexp.quote(receiver)}/).collect{|e| "::" + e} - end - - when /^([A-Z].*)::([^:.]*)$/ - # Constant or class methods - receiver = $1 - message = $2 - - if doc_namespace - "#{receiver}::#{message}" - else - begin - candidates = eval("#{receiver}.constants.collect{|m| m.to_s}", bind) - candidates |= eval("#{receiver}.methods.collect{|m| m.to_s}", bind) - rescue Exception - candidates = [] - end - - select_message(receiver, message, candidates.sort, "::") - end - - when /^(:[^:.]+)(\.|::)([^.]*)$/ - # Symbol - receiver = $1 - sep = $2 - message = $3 - - if doc_namespace - "Symbol.#{message}" - else - candidates = Symbol.instance_methods.collect{|m| m.to_s} - select_message(receiver, message, candidates, sep) - end - - when /^(?<num>-?(?:0[dbo])?[0-9_]+(?:\.[0-9_]+)?(?:(?:[eE][+-]?[0-9]+)?i?|r)?)(?<sep>\.|::)(?<mes>[^.]*)$/ - # Numeric - receiver = $~[:num] - sep = $~[:sep] - message = $~[:mes] - - begin - instance = eval(receiver, bind) - - if doc_namespace - "#{instance.class.name}.#{message}" - else - candidates = instance.methods.collect{|m| m.to_s} - select_message(receiver, message, candidates, sep) - end - rescue Exception - if doc_namespace - nil - else - [] - end - end - - when /^(-?0x[0-9a-fA-F_]+)(\.|::)([^.]*)$/ - # Numeric(0xFFFF) - receiver = $1 - sep = $2 - message = $3 - - begin - instance = eval(receiver, bind) - if doc_namespace - "#{instance.class.name}.#{message}" - else - candidates = instance.methods.collect{|m| m.to_s} - select_message(receiver, message, candidates, sep) - end - rescue Exception - if doc_namespace - nil - else - [] - end - end - - when /^(\$[^.]*)$/ - # global var - gvar = $1 - all_gvars = global_variables.collect{|m| m.to_s} - - if doc_namespace - all_gvars.find{ |i| i == gvar } - else - all_gvars.grep(Regexp.new(Regexp.quote(gvar))) - end - - when /^([^.:"].*)(\.|::)([^.]*)$/ - # variable.func or func.func - receiver = $1 - sep = $2 - message = $3 - - gv = bind.eval_global_variables.collect{|m| m.to_s}.push("true", "false", "nil") - lv = bind.local_variables.collect{|m| m.to_s} - iv = bind.eval_instance_variables.collect{|m| m.to_s} - cv = bind.eval_class_constants.collect{|m| m.to_s} - - if (gv | lv | iv | cv).include?(receiver) or /^[A-Z]/ =~ receiver && /\./ !~ receiver - # foo.func and foo is var. OR - # foo::func and foo is var. OR - # foo::Const and foo is var. OR - # Foo::Bar.func - begin - candidates = [] - rec = eval(receiver, bind) - if sep == "::" and rec.kind_of?(Module) - candidates = rec.constants.collect{|m| m.to_s} - end - candidates |= rec.methods.collect{|m| m.to_s} - rescue Exception - candidates = [] - end - else - # func1.func2 - candidates = [] - end - - if doc_namespace - rec_class = rec.is_a?(Module) ? rec : rec.class - "#{rec_class.name}#{sep}#{candidates.find{ |i| i == message }}" rescue nil - else - select_message(receiver, message, candidates, sep) - end - - when /^\.([^.]*)$/ - # unknown(maybe String) - - receiver = "" - message = $1 - - candidates = String.instance_methods(true).collect{|m| m.to_s} - - if doc_namespace - "String.#{candidates.find{ |i| i == message }}" - 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) 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 - candidates.find{ |i| i == input } - end - 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 - candidates.grep(/^#{Regexp.quote(input)}/).sort - end - end - end - - # Set of available operators in Ruby - Operators = %w[% & * ** + - / < << <= <=> == === =~ > >= >> [] []= ^ ! != !~] - - def select_message(receiver, message, candidates, sep = ".") - candidates.grep(/^#{Regexp.quote(message)}/).collect do |e| - case e - when /^[a-zA-Z_]/ - receiver + sep + e - when /^[0-9]/ - when *Operators - #receiver + " " + e - end - end - end - end - - module InputCompletor - class << self - private def regexp_completor - @regexp_completor ||= RegexpCompletor.new - end - - def retrieve_completion_data(input, bind: IRB.conf[:MAIN_CONTEXT].workspace.binding, doc_namespace: false) - regexp_completor.retrieve_completion_data(input, bind: bind, doc_namespace: doc_namespace) - end - end - CompletionProc = ->(target, preposing = nil, postposing = nil) { - regexp_completor.completion_candidates(preposing || '', target, postposing || '', bind: IRB.conf[:MAIN_CONTEXT].workspace.binding) - } - end - deprecate_constant :InputCompletor -end diff --git a/lib/irb/context.rb b/lib/irb/context.rb deleted file mode 100644 index 395d9081f8..0000000000 --- a/lib/irb/context.rb +++ /dev/null @@ -1,751 +0,0 @@ -# frozen_string_literal: true -# -# irb/context.rb - irb context -# by Keiju ISHITSUKA([email protected]) -# - -require_relative "workspace" -require_relative "inspector" -require_relative "input-method" -require_relative "output-method" - -module IRB - # A class that wraps the current state of the irb session, including the - # configuration of IRB.conf. - class Context - KERNEL_PUBLIC_METHOD = ::Kernel.instance_method(:public_method) - KERNEL_METHOD = ::Kernel.instance_method(:method) - - ASSIGN_OPERATORS_REGEXP = Regexp.union(%w[= += -= *= /= %= **= &= |= &&= ||= ^= <<= >>=]) - # Creates a new IRB context. - # - # The optional +input_method+ argument: - # - # +nil+:: uses stdin or Reline or Readline - # +String+:: uses a File - # +other+:: uses this as InputMethod - def initialize(irb, workspace = nil, input_method = nil) - @irb = irb - @workspace_stack = [] - if workspace - @workspace_stack << workspace - else - @workspace_stack << WorkSpace.new - end - @thread = Thread.current - - # copy of default configuration - @ap_name = IRB.conf[:AP_NAME] - @rc = IRB.conf[:RC] - @load_modules = IRB.conf[:LOAD_MODULES] - - if IRB.conf.has_key?(:USE_SINGLELINE) - @use_singleline = IRB.conf[:USE_SINGLELINE] - elsif IRB.conf.has_key?(:USE_READLINE) # backward compatibility - @use_singleline = IRB.conf[:USE_READLINE] - else - @use_singleline = nil - end - if IRB.conf.has_key?(:USE_MULTILINE) - @use_multiline = IRB.conf[:USE_MULTILINE] - elsif IRB.conf.has_key?(:USE_RELINE) # backward compatibility - warn <<~MSG.strip - USE_RELINE is deprecated, please use USE_MULTILINE instead. - MSG - @use_multiline = IRB.conf[:USE_RELINE] - elsif IRB.conf.has_key?(:USE_REIDLINE) - warn <<~MSG.strip - USE_REIDLINE is deprecated, please use USE_MULTILINE instead. - MSG - @use_multiline = IRB.conf[:USE_REIDLINE] - else - @use_multiline = nil - end - @use_autocomplete = IRB.conf[:USE_AUTOCOMPLETE] - @verbose = IRB.conf[:VERBOSE] - @io = nil - - self.inspect_mode = IRB.conf[:INSPECT_MODE] - 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] - - @ignore_sigint = IRB.conf[:IGNORE_SIGINT] - @ignore_eof = IRB.conf[:IGNORE_EOF] - - @back_trace_limit = IRB.conf[:BACK_TRACE_LIMIT] - - self.prompt_mode = IRB.conf[:PROMPT_MODE] - - @irb_name = IRB.conf[:IRB_NAME] - - unless IRB.conf[:SINGLE_IRB] or !defined?(IRB::JobManager) - @irb_name = @irb_name + "#" + IRB.JobManager.n_jobs.to_s - end - - self.irb_path = "(" + @irb_name + ")" - - case input_method - when nil - @io = nil - case use_multiline? - when nil - 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 - @io = nil - end - when false - @io = nil - when true - @io = RelineInputMethod.new(build_completor) - end - unless @io - case use_singleline? - when nil - if (defined?(ReadlineInputMethod) && term_interactive? && - IRB.conf[:PROMPT_MODE] != :INF_RUBY) - @io = ReadlineInputMethod.new - else - @io = nil - end - when false - @io = nil - when true - if defined?(ReadlineInputMethod) - @io = ReadlineInputMethod.new - else - @io = nil - end - else - @io = nil - end - end - @io = StdioInputMethod.new unless @io - - when '-' - @io = FileInputMethod.new($stdin) - @irb_name = '-' - self.irb_path = '-' - when String - @io = FileInputMethod.new(input_method) - @irb_name = File.basename(input_method) - self.irb_path = input_method - else - @io = input_method - end - @extra_doc_dirs = IRB.conf[:EXTRA_DOC_DIRS] - - @echo = IRB.conf[:ECHO] - if @echo.nil? - @echo = true - end - - @echo_on_assignment = IRB.conf[:ECHO_ON_ASSIGNMENT] - if @echo_on_assignment.nil? - @echo_on_assignment = :truncate - end - - @newline_before_multiline_output = IRB.conf[:NEWLINE_BEFORE_MULTILINE_OUTPUT] - if @newline_before_multiline_output.nil? - @newline_before_multiline_output = true - end - - @command_aliases = IRB.conf[:COMMAND_ALIASES].dup - end - - 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 - - def save_history=(val) - IRB.conf[:SAVE_HISTORY] = val - end - - def save_history - IRB.conf[:SAVE_HISTORY] - end - - # A copy of the default <code>IRB.conf[:HISTORY_FILE]</code> - def history_file - IRB.conf[:HISTORY_FILE] - end - - # Set <code>IRB.conf[:HISTORY_FILE]</code> to the given +hist+. - def history_file=(hist) - 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 - end - - # The toplevel workspace, see #home_workspace - attr_reader :workspace_home - # The current thread in this context. - attr_reader :thread - # The current input method. - # - # Can be either StdioInputMethod, ReadlineInputMethod, - # RelineInputMethod, FileInputMethod or other specified when the - # context is created. See ::new for more # information on +input_method+. - attr_accessor :io - - # Current irb session. - attr_accessor :irb - # A copy of the default <code>IRB.conf[:AP_NAME]</code> - attr_accessor :ap_name - # A copy of the default <code>IRB.conf[:RC]</code> - attr_accessor :rc - # A copy of the default <code>IRB.conf[:LOAD_MODULES]</code> - attr_accessor :load_modules - # 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 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. - # - # A copy of the default <code>IRB.conf[:USE_MULTILINE]</code> - attr_reader :use_multiline - # Whether singleline editor mode is enabled or not. - # - # A copy of the default <code>IRB.conf[:USE_SINGLELINE]</code> - attr_reader :use_singleline - # Whether colorization is enabled or not. - # - # A copy of the default <code>IRB.conf[:USE_AUTOCOMPLETE]</code> - attr_reader :use_autocomplete - # A copy of the default <code>IRB.conf[:INSPECT_MODE]</code> - attr_reader :inspect_mode - # Inspector for the current context - attr_reader :inspect_method - - # A copy of the default <code>IRB.conf[:PROMPT_MODE]</code> - attr_reader :prompt_mode - # Standard IRB prompt. - # - # See {Custom Prompts}[rdoc-ref:IRB@Custom+Prompts] for more information. - attr_accessor :prompt_i - # IRB prompt for continuated strings. - # - # See {Custom Prompts}[rdoc-ref:IRB@Custom+Prompts] for more information. - attr_accessor :prompt_s - # IRB prompt for continuated statement. (e.g. immediately after an +if+) - # - # See {Custom Prompts}[rdoc-ref:IRB@Custom+Prompts] for more information. - attr_accessor :prompt_c - - # TODO: Remove this when developing v2.0 - def prompt_n - warn "IRB::Context#prompt_n is deprecated and will be removed in the next major release." - "" - end - - # TODO: Remove this when developing v2.0 - def prompt_n=(_) - warn "IRB::Context#prompt_n= is deprecated and will be removed in the next major release." - "" - end - - # Can be either the default <code>IRB.conf[:AUTO_INDENT]</code>, or the - # mode set by #prompt_mode= - # - # To disable auto-indentation in irb: - # - # IRB.conf[:AUTO_INDENT] = false - # - # or - # - # irb_context.auto_indent_mode = false - # - # or - # - # IRB.CurrentContext.auto_indent_mode = false - # - # See IRB@Configuration for more information. - attr_accessor :auto_indent_mode - # The format of the return statement, set by #prompt_mode= using the - # +:RETURN+ of the +mode+ passed to set the current #prompt_mode. - attr_accessor :return_format - - # Whether <code>^C</code> (+control-c+) will be ignored or not. - # - # If set to +false+, <code>^C</code> will quit irb. - # - # If set to +true+, - # - # * during input: cancel input then return to top level. - # * during execute: abandon current execution. - attr_accessor :ignore_sigint - # Whether <code>^D</code> (+control-d+) will be ignored or not. - # - # If set to +false+, <code>^D</code> will quit irb. - attr_accessor :ignore_eof - # Specify the installation locations of the ri file to be displayed in the - # document dialog. - attr_accessor :extra_doc_dirs - # Whether to echo the return value to output or not. - # - # Uses <code>IRB.conf[:ECHO]</code> if available, or defaults to +true+. - # - # puts "hello" - # # hello - # #=> nil - # IRB.CurrentContext.echo = false - # puts "omg" - # # omg - attr_accessor :echo - # Whether to echo for assignment expressions. - # - # If set to +false+, the value of assignment will not be shown. - # - # If set to +true+, the value of assignment will be shown. - # - # If set to +:truncate+, the value of assignment will be shown and truncated. - # - # It defaults to +:truncate+. - # - # a = "omg" - # #=> omg - # - # a = "omg" * 10 - # #=> omgomgomgomgomgomgomg... - # - # IRB.CurrentContext.echo_on_assignment = false - # a = "omg" - # - # IRB.CurrentContext.echo_on_assignment = true - # a = "omg" * 10 - # #=> omgomgomgomgomgomgomgomgomgomg - # - # To set the behaviour of showing on assignment in irb: - # - # IRB.conf[:ECHO_ON_ASSIGNMENT] = :truncate or true or false - # - # or - # - # irb_context.echo_on_assignment = :truncate or true or false - # - # or - # - # IRB.CurrentContext.echo_on_assignment = :truncate or true or false - attr_accessor :echo_on_assignment - # Whether a newline is put before multiline output. - # - # Uses <code>IRB.conf[:NEWLINE_BEFORE_MULTILINE_OUTPUT]</code> if available, - # or defaults to +true+. - # - # "abc\ndef" - # #=> - # abc - # def - # IRB.CurrentContext.newline_before_multiline_output = false - # "abc\ndef" - # #=> abc - # def - attr_accessor :newline_before_multiline_output - # Whether verbose messages are displayed or not. - # - # A copy of the default <code>IRB.conf[:VERBOSE]</code> - attr_accessor :verbose - - # The limit of backtrace lines displayed as top +n+ and tail +n+. - # - # The default value is 16. - # - # Can also be set using the +--back-trace-limit+ command line option. - attr_accessor :back_trace_limit - - # User-defined IRB command aliases - attr_accessor :command_aliases - - attr_accessor :with_debugger - - # Alias for #use_multiline - alias use_multiline? use_multiline - # Alias for #use_singleline - alias use_singleline? use_singleline - # backward compatibility - alias use_reline use_multiline - # backward compatibility - alias use_reline? use_multiline - # backward compatibility - alias use_readline use_singleline - # backward compatibility - alias use_readline? use_singleline - # Alias for #use_autocomplete - alias use_autocomplete? use_autocomplete - # Alias for #rc - alias rc? rc - alias ignore_sigint? ignore_sigint - alias ignore_eof? ignore_eof - alias echo? echo - alias echo_on_assignment? echo_on_assignment - alias newline_before_multiline_output? newline_before_multiline_output - - # Returns whether messages are displayed or not. - def verbose? - if @verbose.nil? - if @io.kind_of?(RelineInputMethod) - false - elsif defined?(ReadlineInputMethod) && @io.kind_of?(ReadlineInputMethod) - false - elsif !STDIN.tty? or @io.kind_of?(FileInputMethod) - true - else - false - end - else - @verbose - end - end - - # Whether #verbose? is +true+, and +input_method+ is either - # StdioInputMethod or RelineInputMethod or ReadlineInputMethod, see #io - # for more information. - def prompting? - verbose? || @io.prompting? - end - - # The return value of the last statement evaluated. - attr_reader :last_value - - # Sets the return value from the last statement evaluated in this context - # to #last_value. - def set_last_value(value) - @last_value = value - workspace.local_variable_set :_, value - end - - # Sets the +mode+ of the prompt in this context. - # - # See {Custom Prompts}[rdoc-ref:IRB@Custom+Prompts] for more information. - def prompt_mode=(mode) - @prompt_mode = mode - pconf = IRB.conf[:PROMPT][mode] - @prompt_i = pconf[:PROMPT_I] - @prompt_s = pconf[:PROMPT_S] - @prompt_c = pconf[:PROMPT_C] - @return_format = pconf[:RETURN] - @return_format = "%s\n" if @return_format == nil - if ai = pconf.include?(:AUTO_INDENT) - @auto_indent_mode = ai - else - @auto_indent_mode = IRB.conf[:AUTO_INDENT] - end - end - - # Whether #inspect_mode is set or not, see #inspect_mode= for more detail. - def inspect? - @inspect_mode.nil? or @inspect_mode - end - - # Whether #io uses a File for the +input_method+ passed when creating the - # current context, see ::new - def file_input? - @io.class == FileInputMethod - end - - # Specifies the inspect mode with +opt+: - # - # +true+:: display +inspect+ - # +false+:: display +to_s+ - # +nil+:: inspect mode in non-math mode, - # non-inspect mode in math mode - # - # See IRB::Inspector for more information. - # - # Can also be set using the +--inspect+ and +--noinspect+ command line - # options. - def inspect_mode=(opt) - - if i = Inspector::INSPECTORS[opt] - @inspect_mode = opt - @inspect_method = i - i.init - else - case opt - when nil - if Inspector.keys_with_inspector(Inspector::INSPECTORS[true]).include?(@inspect_mode) - self.inspect_mode = false - elsif Inspector.keys_with_inspector(Inspector::INSPECTORS[false]).include?(@inspect_mode) - self.inspect_mode = true - else - puts "Can't switch inspect mode." - return - end - when /^\s*\{.*\}\s*$/ - begin - inspector = eval "proc#{opt}" - rescue Exception - puts "Can't switch inspect mode(#{opt})." - return - end - self.inspect_mode = inspector - when Proc - self.inspect_mode = IRB::Inspector(opt) - when Inspector - prefix = "usr%d" - i = 1 - while Inspector::INSPECTORS[format(prefix, i)]; i += 1; end - @inspect_mode = format(prefix, i) - @inspect_method = opt - Inspector.def_inspector(format(prefix, i), @inspect_method) - else - puts "Can't switch inspect mode(#{opt})." - return - end - end - print "Switch to#{unless @inspect_mode; ' non';end} inspect mode.\n" if verbose? - @inspect_mode - end - - def evaluate(statement, line_no) # :nodoc: - @line_no = line_no - - 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) - when Statement::IncorrectAlias - warn statement.message - 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(code, @eval_path, line_no) - end - IRB.conf[:MEASURE_CALLBACKS].inject(last_proc) do |chain, item| - _name, callback, arg = item - proc do - callback.(self, code, line_no, arg) do - chain.call - end - end - end.call - else - result = workspace.evaluate(code, @eval_path, line_no) - end - result - end - - def parse_input(code, is_assignment_expression) - command_name, arg = code.strip.split(/\s+/, 2) - arg ||= '' - - # command can only be 1 line - if code.lines.size != 1 || - # command name is required - command_name.nil? || - # local variable have precedence over command - local_variables.include?(command_name.to_sym) || - # assignment expression is not a command - (is_assignment_expression || - (arg.start_with?(ASSIGN_OPERATORS_REGEXP) && !arg.start_with?(/==|=~/))) - return Statement::Expression.new(code, is_assignment_expression) - end - - command = command_name.to_sym - - # Check command aliases - if aliased_name = command_aliases[command] - if command_class = Command.load_command(aliased_name) - command = aliased_name - elsif HelperMethod.helper_methods[aliased_name] - message = <<~MESSAGE - Using command alias `#{command}` for helper method `#{aliased_name}` is not supported. - Please check the value of `IRB.conf[:COMMAND_ALIASES]`. - MESSAGE - return Statement::IncorrectAlias.new(message) - else - message = <<~MESSAGE - You're trying to use command alias `#{command}` for command `#{aliased_name}`, but `#{aliased_name}` does not exist. - Please check the value of `IRB.conf[:COMMAND_ALIASES]`. - MESSAGE - return Statement::IncorrectAlias.new(message) - end - else - command_class = Command.load_command(command) - end - - # Check visibility - public_method = !!KERNEL_PUBLIC_METHOD.bind_call(main, command) rescue false - private_method = !public_method && !!KERNEL_METHOD.bind_call(main, command) rescue false - if command_class && Command.execute_as_command?(command, public_method: public_method, private_method: private_method) - Statement::Command.new(code, command_class, arg) - else - Statement::Expression.new(code, is_assignment_expression) - end - end - - def colorize_input(input, complete:) - if IRB.conf[:USE_COLORIZE] && IRB::Color.colorable? - lvars = local_variables || [] - parsed_input = parse_input(input, false) - if parsed_input.is_a?(Statement::Command) - name, sep, arg = input.split(/(\s+)/, 2) - arg = IRB::Color.colorize_code(arg, complete: complete, local_variables: lvars) - "#{IRB::Color.colorize(name, [:BOLD])}\e[m#{sep}#{arg}" - else - IRB::Color.colorize_code(input, complete: complete, local_variables: lvars) - end - else - Reline::Unicode.escape_for_print(input) - end - end - - def inspect_last_value(output = +'') # :nodoc: - @inspect_method.inspect_value(@last_value, output) - end - - def inspector_support_stream_output? - @inspect_method.support_stream_output? - end - - NOPRINTING_IVARS = ["@last_value"] # :nodoc: - NO_INSPECTING_IVARS = ["@irb", "@io"] # :nodoc: - IDNAME_IVARS = ["@prompt_mode"] # :nodoc: - - alias __inspect__ inspect - def inspect # :nodoc: - array = [] - for ivar in instance_variables.sort{|e1, e2| e1 <=> e2} - ivar = ivar.to_s - name = ivar.sub(/^@(.*)$/, '\1') - val = instance_eval(ivar) - case ivar - when *NOPRINTING_IVARS - array.push format("conf.%s=%s", name, "...") - when *NO_INSPECTING_IVARS - array.push format("conf.%s=%s", name, val.to_s) - when *IDNAME_IVARS - array.push format("conf.%s=:%s", name, val.id2name) - else - array.push format("conf.%s=%s", name, val.inspect) - end - end - array.join("\n") - end - alias __to_s__ to_s - alias to_s inspect - - def local_variables # :nodoc: - workspace.binding.local_variables - end - - def safe_method_call_on_main(method_name) - main_object = main - Object === main_object ? main_object.__send__(method_name) : Object.instance_method(method_name).bind_call(main_object) - end - - private - - def term_interactive? - return true if ENV['TEST_IRB_FORCE_INTERACTIVE'] - STDIN.tty? && ENV['TERM'] != 'dumb' - end - - def build_completor - completor_type = IRB.conf[:COMPLETOR] - - # Gem repl_type_completor is added to bundled gems in Ruby 3.4. - # Use :type as default completor only in Ruby 3.4 or later. - verbose = !!completor_type - completor_type ||= RUBY_VERSION >= '3.4' ? :type : :regexp - - case completor_type - when :regexp - return RegexpCompletor.new - when :type - completor = build_type_completor(verbose: verbose) - return completor if completor - else - warn "Invalid value for IRB.conf[:COMPLETOR]: #{completor_type}" - end - # Fallback to RegexpCompletor - RegexpCompletor.new - end - - def build_type_completor(verbose:) - if RUBY_ENGINE == 'truffleruby' - # Avoid SyntaxError. truffleruby does not support endless method definition yet. - warn 'TypeCompletor is not supported on TruffleRuby yet' if verbose - return - end - - begin - require 'repl_type_completor' - rescue LoadError => e - warn "TypeCompletor requires `gem repl_type_completor`: #{e.message}" if verbose - return - end - - ReplTypeCompletor.preload_rbs - TypeCompletor.new(self) - end - end -end diff --git a/lib/irb/debug.rb b/lib/irb/debug.rb deleted file mode 100644 index 59be1365bd..0000000000 --- a/lib/irb/debug.rb +++ /dev/null @@ -1,127 +0,0 @@ -# frozen_string_literal: true - -module IRB - module Debug - IRB_DIR = File.expand_path('..', __dir__) - - class << self - def insert_debug_break(pre_cmds: nil, do_cmds: nil) - options = { oneshot: true, hook_call: false } - - if pre_cmds || do_cmds - options[:command] = ['irb', pre_cmds, do_cmds] - end - if DEBUGGER__::LineBreakpoint.instance_method(:initialize).parameters.include?([:key, :skip_src]) - options[:skip_src] = true - end - - # To make debugger commands like `next` or `continue` work without asking - # the user to quit IRB after that, we need to exit IRB first and then hit - # a TracePoint on #debug_break. - file, lineno = IRB::Irb.instance_method(:debug_break).source_location - DEBUGGER__::SESSION.add_line_breakpoint(file, lineno + 1, **options) - end - - def setup(irb) - # When debug session is not started at all - unless defined?(DEBUGGER__::SESSION) - begin - require "debug/session" - rescue LoadError # debug.gem is not written in Gemfile - return false unless load_bundled_debug_gem - end - DEBUGGER__::CONFIG.set_config - configure_irb_for_debugger(irb) - - DEBUGGER__.initialize_session{ IRB::Debug::UI.new(irb) } - end - - # When debug session was previously started but not by IRB - if defined?(DEBUGGER__::SESSION) && !irb.context.with_debugger - configure_irb_for_debugger(irb) - DEBUGGER__::SESSION.reset_ui(IRB::Debug::UI.new(irb)) - end - - # Apply patches to debug gem so it skips IRB frames - unless DEBUGGER__.respond_to?(:capture_frames_without_irb) - DEBUGGER__.singleton_class.send(:alias_method, :capture_frames_without_irb, :capture_frames) - - def DEBUGGER__.capture_frames(*args) - frames = capture_frames_without_irb(*args) - frames.reject! do |frame| - frame.realpath&.start_with?(IRB_DIR) || frame.path == "<internal:prelude>" - end - frames - end - - DEBUGGER__::ThreadClient.prepend(SkipPathHelperForIRB) - end - - if !DEBUGGER__::CONFIG[:no_hint] && irb.context.io.is_a?(RelineInputMethod) - Reline.output_modifier_proc = proc do |input, complete:| - unless input.strip.empty? - cmd = input.split(/\s/, 2).first - - if !complete && DEBUGGER__.commands.key?(cmd) - input = input.sub(/\n$/, " # debug command\n") - end - end - - irb.context.colorize_input(input, complete: complete) - end - end - - true - end - - private - - def configure_irb_for_debugger(irb) - require 'irb/debug/ui' - IRB.instance_variable_set(:@debugger_irb, irb) - irb.context.with_debugger = true - irb.context.irb_name += ":rdbg" - irb.context.io.load_history if irb.context.io.class < HistorySavingAbility - end - - module SkipPathHelperForIRB - def skip_internal_path?(path) - # The latter can be removed once https://github.com/ruby/debug/issues/866 is resolved - super || path.match?(IRB_DIR) || path.match?('<internal:prelude>') - end - end - - # This is used when debug.gem is not written in Gemfile. Even if it's not - # installed by `bundle install`, debug.gem is installed by default because - # it's a bundled gem. This method tries to activate and load that. - def load_bundled_debug_gem - # Discover latest debug.gem under GEM_PATH - debug_gem = Gem.paths.path.flat_map { |path| Dir.glob("#{path}/gems/debug-*") }.select do |path| - File.basename(path).match?(/\Adebug-\d+\.\d+\.\d+(\w+)?\z/) - end.sort_by do |path| - Gem::Version.new(File.basename(path).delete_prefix('debug-')) - end.last - return false unless debug_gem - - # Discover debug/debug.so under extensions for Ruby 3.2+ - ext_name = "/debug/debug.#{RbConfig::CONFIG['DLEXT']}" - ext_path = Gem.paths.path.flat_map do |path| - Dir.glob("#{path}/extensions/**/#{File.basename(debug_gem)}#{ext_name}") - end.first - - # Attempt to forcibly load the bundled gem - if ext_path - $LOAD_PATH << ext_path.delete_suffix(ext_name) - end - $LOAD_PATH << "#{debug_gem}/lib" - begin - require "debug/session" - puts "Loaded #{File.basename(debug_gem)}" - true - rescue LoadError - false - end - end - end - end -end diff --git a/lib/irb/debug/ui.rb b/lib/irb/debug/ui.rb deleted file mode 100644 index a21ec6b11d..0000000000 --- a/lib/irb/debug/ui.rb +++ /dev/null @@ -1,101 +0,0 @@ -require 'io/console/size' -require 'debug/console' - -module IRB - module Debug - class UI < DEBUGGER__::UI_Base - def initialize(irb) - @irb = irb - end - - def remote? - false - end - - def activate session, on_fork: false - end - - def deactivate - end - - def width - if (w = IO.console_size[1]) == 0 # for tests PTY - 80 - else - w - end - end - - def quit n - yield - exit n - end - - def ask prompt - setup_interrupt do - print prompt - ($stdin.gets || '').strip - end - end - - def puts str = nil - case str - when Array - str.each{|line| - $stdout.puts line.chomp - } - when String - Pager.page_content(str, retain_content: true) - when nil - $stdout.puts - end - end - - def readline _ - setup_interrupt do - tc = DEBUGGER__::SESSION.instance_variable_get(:@tc) - cmd = @irb.debug_readline(tc.current_frame.eval_binding || TOPLEVEL_BINDING) - - case cmd - when nil # when user types C-d - "continue" - else - cmd - end - end - end - - def setup_interrupt - DEBUGGER__::SESSION.intercept_trap_sigint false do - current_thread = Thread.current # should be session_server thread - - prev_handler = trap(:INT){ - current_thread.raise Interrupt - } - - yield - ensure - trap(:INT, prev_handler) - end - end - - def after_fork_parent - parent_pid = Process.pid - - at_exit{ - DEBUGGER__::SESSION.intercept_trap_sigint_end - trap(:SIGINT, :IGNORE) - - if Process.pid == parent_pid - # only check child process from its parent - begin - # wait for all child processes to keep terminal - Process.waitpid - rescue Errno::ESRCH, Errno::ECHILD - end - end - } - end - end - end -end diff --git a/lib/irb/default_commands.rb b/lib/irb/default_commands.rb deleted file mode 100644 index 9820a1f304..0000000000 --- a/lib/irb/default_commands.rb +++ /dev/null @@ -1,279 +0,0 @@ -# frozen_string_literal: true - -require_relative "command" -require_relative "command/internal_helpers" -require_relative "command/backtrace" -require_relative "command/break" -require_relative "command/catch" -require_relative "command/cd" -require_relative "command/chws" -require_relative "command/context" -require_relative "command/continue" -require_relative "command/copy" -require_relative "command/debug" -require_relative "command/delete" -require_relative "command/disable_irb" -require_relative "command/edit" -require_relative "command/exit" -require_relative "command/finish" -require_relative "command/force_exit" -require_relative "command/help" -require_relative "command/history" -require_relative "command/info" -require_relative "command/irb_info" -require_relative "command/load" -require_relative "command/ls" -require_relative "command/measure" -require_relative "command/next" -require_relative "command/pushws" -require_relative "command/show_doc" -require_relative "command/show_source" -require_relative "command/step" -require_relative "command/subirb" -require_relative "command/whereami" - -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, - [:break, OVERRIDE_ALL] - ) - _register_with_aliases(:irb_catch, Command::Catch, - [:catch, OVERRIDE_PRIVATE_ONLY] - ) - _register_with_aliases(:irb_next, Command::Next, - [:next, OVERRIDE_ALL] - ) - _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], - [:ri, 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] - ) - - _register_with_aliases(:irb_disable_irb, Command::DisableIrb, - [:disable_irb, NO_OVERRIDE] - ) - - register(:cd, Command::CD) - register(:copy, Command::Copy) - 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 = [] - - class << self - # Drepcated. Use Command.regiser instead. - def 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 -end diff --git a/lib/irb/easter-egg.rb b/lib/irb/easter-egg.rb deleted file mode 100644 index 07b6137be3..0000000000 --- a/lib/irb/easter-egg.rb +++ /dev/null @@ -1,152 +0,0 @@ -require "reline" - -module IRB - class << self - class Vec - def initialize(x, y, z) - @x, @y, @z = x, y, z - end - - attr_reader :x, :y, :z - - def sub(other) - Vec.new(@x - other.x, @y - other.y, @z - other.z) - end - - def dot(other) - @x*other.x + @y*other.y + @z*other.z - end - - def cross(other) - ox, oy, oz = other.x, other.y, other.z - Vec.new(@y*oz-@z*oy, @z*ox-@x*oz, @x*oy-@y*ox) - end - - def normalize - r = Math.sqrt(self.dot(self)) - Vec.new(@x / r, @y / r, @z / r) - end - end - - class Canvas - def initialize((h, w)) - @data = (0..h-2).map { [0] * w } - @scale = [w / 2.0, h-2].min - @center = Complex(w / 2, h-2) - end - - def line((x1, y1), (x2, y2)) - p1 = Complex(x1, y1) / 2 * @scale + @center - p2 = Complex(x2, y2) / 2 * @scale + @center - line0(p1, p2) - end - - private def line0(p1, p2) - mid = (p1 + p2) / 2 - if (p1 - p2).abs < 1 - x, y = mid.rect - @data[y / 2][x] |= (y % 2 > 1 ? 2 : 1) - else - line0(p1, mid) - line0(p2, mid) - end - end - - def draw - @data.each {|row| row.fill(0) } - yield - @data.map {|row| row.map {|n| " ',;"[n] }.join }.join("\n") - end - end - - class RubyModel - def initialize - @faces = init_ruby_model - end - - def init_ruby_model - cap_vertices = (0..5).map {|i| Vec.new(*Complex.polar(1, i * Math::PI / 3).rect, 1) } - middle_vertices = (0..5).map {|i| Vec.new(*Complex.polar(2, (i + 0.5) * Math::PI / 3).rect, 0) } - bottom_vertex = Vec.new(0, 0, -2) - - faces = [cap_vertices] - 6.times do |j| - i = j-1 - faces << [cap_vertices[i], middle_vertices[i], cap_vertices[j]] - faces << [cap_vertices[j], middle_vertices[i], middle_vertices[j]] - faces << [middle_vertices[i], bottom_vertex, middle_vertices[j]] - end - - faces - end - - def render_frame(i) - angle = i / 10.0 - dir = Vec.new(*Complex.polar(1, angle).rect, Math.sin(angle)).normalize - dir2 = Vec.new(*Complex.polar(1, angle - Math::PI/2).rect, 0) - up = dir.cross(dir2) - nm = dir.cross(up) - @faces.each do |vertices| - v0, v1, v2, = vertices - if v1.sub(v0).cross(v2.sub(v0)).dot(dir) > 0 - points = vertices.map {|p| [nm.dot(p), up.dot(p)] } - (points + [points[0]]).each_cons(2) do |p1, p2| - yield p1, p2 - end - end - end - end - end - - private def easter_egg_logo(type) - @easter_egg_logos ||= File.read(File.join(__dir__, 'ruby_logo.aa'), encoding: 'UTF-8:UTF-8') - .split(/TYPE: ([A-Z_]+)\n/)[1..] - .each_slice(2) - .to_h - @easter_egg_logos[type.to_s.upcase] - end - - private def easter_egg(type = nil) - print "\e[?1049h" - type ||= [:logo, :dancing].sample - case type - when :logo - Pager.page do |io| - logo_type = STDOUT.external_encoding == Encoding::UTF_8 ? :unicode_large : :ascii_large - io.write easter_egg_logo(logo_type) - STDIN.raw { STDIN.getc } if io == STDOUT - end - when :dancing - STDOUT.cooked do - interrupted = false - prev_trap = trap("SIGINT") { interrupted = true } - canvas = Canvas.new(Reline.get_screen_size) - Reline::IOGate.set_winch_handler do - canvas = Canvas.new(Reline.get_screen_size) - end - ruby_model = RubyModel.new - print "\e[?25l" # hide cursor - 0.step do |i| # TODO (0..).each needs Ruby 2.6 or later - buff = canvas.draw do - ruby_model.render_frame(i) do |p1, p2| - canvas.line(p1, p2) - end - end - buff[0, 20] = "\e[0mPress Ctrl+C to stop\e[31m\e[1m" - print "\e[H" + buff - sleep 0.05 - break if interrupted - end - rescue Interrupt - ensure - print "\e[?25h" # show cursor - trap("SIGINT", prev_trap) - end - end - ensure - print "\e[0m\e[?1049l" - end - end -end - -IRB.__send__(:easter_egg, ARGV[0]&.to_sym) if $0 == __FILE__ diff --git a/lib/irb/ext/change-ws.rb b/lib/irb/ext/change-ws.rb deleted file mode 100644 index 60e8afe31f..0000000000 --- a/lib/irb/ext/change-ws.rb +++ /dev/null @@ -1,37 +0,0 @@ -# frozen_string_literal: true -# -# irb/ext/cb.rb - -# by Keiju ISHITSUKA([email protected]) -# - -module IRB # :nodoc: - class Context - - # Inherited from +TOPLEVEL_BINDING+. - def home_workspace - if defined? @home_workspace - @home_workspace - else - @home_workspace = workspace - end - end - - # Changes the current workspace to given object or binding. - # - # If the optional argument is omitted, the workspace will be - # #home_workspace which is inherited from +TOPLEVEL_BINDING+ or the main - # object, <code>IRB.conf[:MAIN_CONTEXT]</code> when irb was initialized. - # - # See IRB::WorkSpace.new for more information. - def change_workspace(*_main) - if _main.empty? - replace_workspace(home_workspace) - return main - 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 deleted file mode 100644 index 6c21ff00ee..0000000000 --- a/lib/irb/ext/eval_history.rb +++ /dev/null @@ -1,149 +0,0 @@ -# frozen_string_literal: true -# -# history.rb - -# by Keiju ISHITSUKA([email protected]) -# - -module IRB # :nodoc: - - class Context - - NOPRINTING_IVARS.push "@eval_history_values" - - # See #set_last_value - alias _set_last_value set_last_value - - def set_last_value(value) - _set_last_value(value) - - if defined?(@eval_history) && @eval_history - @eval_history_values.push @line_no, @last_value - workspace.evaluate "__ = IRB.CurrentContext.instance_eval{@eval_history_values}" - end - - @last_value - end - - remove_method :eval_history= if method_defined?(:eval_history=) - # The command result history limit. This method is not available until - # #eval_history= was called with non-nil value (directly or via - # setting <code>IRB.conf[:EVAL_HISTORY]</code> in <code>.irbrc</code>). - attr_reader :eval_history - # Sets command result history limit. Default value is set from - # <code>IRB.conf[:EVAL_HISTORY]</code>. - # - # +no+ is an Integer or +nil+. - # - # Returns +no+ of history items if greater than 0. - # - # If +no+ is 0, the number of history items is unlimited. - # - # If +no+ is +nil+, execution result history isn't used (default). - # - # EvalHistory values are available via <code>__</code> variable, see - # IRB::EvalHistory. - def eval_history=(no) - if no - if defined?(@eval_history) && @eval_history - @eval_history_values.size(no) - else - @eval_history_values = EvalHistory.new(no) - IRB.conf[:__TMP__EHV__] = @eval_history_values - workspace.evaluate("__ = IRB.conf[:__TMP__EHV__]") - IRB.conf.delete(:__TMP_EHV__) - end - else - @eval_history_values = nil - end - @eval_history = no - end - end - - # Represents history of results of previously evaluated commands. - # - # Available via <code>__</code> variable, only if <code>IRB.conf[:EVAL_HISTORY]</code> - # or <code>IRB::CurrentContext().eval_history</code> is non-nil integer value - # (by default it is +nil+). - # - # Example (in `irb`): - # - # # Initialize history - # IRB::CurrentContext().eval_history = 10 - # # => 10 - # - # # Perform some commands... - # 1 + 2 - # # => 3 - # puts 'x' - # # x - # # => nil - # raise RuntimeError - # # ...error raised - # - # # Inspect history (format is "<item number> <evaluated value>": - # __ - # # => 1 10 - # # 2 3 - # # 3 nil - # - # __[1] - # # => 10 - # - class EvalHistory - - def initialize(size = 16) # :nodoc: - @size = size - @contents = [] - end - - def size(size) # :nodoc: - if size != 0 && size < @size - @contents = @contents[@size - size .. @size] - end - @size = size - end - - # Get one item of the content (both positive and negative indexes work). - def [](idx) - begin - if idx >= 0 - @contents.find{|no, val| no == idx}[1] - else - @contents[idx][1] - end - rescue NameError - nil - end - end - - def push(no, val) # :nodoc: - @contents.push [no, val] - @contents.shift if @size != 0 && @contents.size > @size - end - - alias real_inspect inspect - - def inspect # :nodoc: - if @contents.empty? - return real_inspect - end - - unless (last = @contents.pop)[1].equal?(self) - @contents.push last - last = nil - end - str = @contents.collect{|no, val| - if val.equal?(self) - "#{no} ...self-history..." - else - "#{no} #{val.inspect}" - end - }.join("\n") - if str == "" - str = "Empty." - end - @contents.push last if last - str - end - end -end diff --git a/lib/irb/ext/loader.rb b/lib/irb/ext/loader.rb deleted file mode 100644 index df5aaa8e5a..0000000000 --- a/lib/irb/ext/loader.rb +++ /dev/null @@ -1,127 +0,0 @@ -# frozen_string_literal: true -# -# loader.rb - -# by Keiju ISHITSUKA([email protected]) -# - -module IRB # :nodoc: - # Raised in the event of an exception in a file loaded from an Irb session - class LoadAbort < Exception;end - - # Provides a few commands for loading files within an irb session. - # - # See ExtendCommandBundle for more information. - module IrbLoader - - alias ruby_load load - alias ruby_require require - - # Loads the given file similarly to Kernel#load - def irb_load(fn, priv = nil) - path = search_file_from_ruby_path(fn) - raise LoadError, "No such file to load -- #{fn}" unless path - - load_file(path, priv) - end - - def search_file_from_ruby_path(fn) # :nodoc: - if File.absolute_path?(fn) - return fn if File.exist?(fn) - return nil - end - - for path in $: - if File.exist?(f = File.join(path, fn)) - return f - end - end - return nil - end - - # Loads a given file in the current session and displays the source lines - # - # See Irb#suspend_input_method for more information. - def source_file(path) - irb = irb_context.irb - irb.suspend_name(path, File.basename(path)) do - FileInputMethod.open(path) do |io| - irb.suspend_input_method(io) do - |back_io| - irb.signal_status(:IN_LOAD) do - if back_io.kind_of?(FileInputMethod) - irb.eval_input - else - begin - irb.eval_input - rescue LoadAbort - print "load abort!!\n" - end - end - end - end - end - end - end - - # Loads the given file in the current session's context and evaluates it. - # - # See Irb#suspend_input_method for more information. - def load_file(path, priv = nil) - irb = irb_context.irb - irb.suspend_name(path, File.basename(path)) do - - if priv - ws = WorkSpace.new(Module.new) - else - ws = WorkSpace.new - end - irb.suspend_workspace(ws) do - FileInputMethod.open(path) do |io| - irb.suspend_input_method(io) do - |back_io| - irb.signal_status(:IN_LOAD) do - if back_io.kind_of?(FileInputMethod) - irb.eval_input - else - begin - irb.eval_input - rescue LoadAbort - print "load abort!!\n" - end - end - end - end - end - end - end - end - - def old # :nodoc: - back_io = @io - back_path = irb_path - back_name = @irb_name - back_scanner = @irb.scanner - begin - @io = FileInputMethod.new(path) - @irb_name = File.basename(path) - self.irb_path = path - @irb.signal_status(:IN_LOAD) do - if back_io.kind_of?(FileInputMethod) - @irb.eval_input - else - begin - @irb.eval_input - rescue LoadAbort - print "load abort!!\n" - end - end - end - ensure - @io = back_io - @irb_name = back_name - self.irb_path = back_path - @irb.scanner = back_scanner - end - end - end -end diff --git a/lib/irb/ext/multi-irb.rb b/lib/irb/ext/multi-irb.rb deleted file mode 100644 index 9f234f0cdc..0000000000 --- a/lib/irb/ext/multi-irb.rb +++ /dev/null @@ -1,258 +0,0 @@ -# frozen_string_literal: true -# -# irb/multi-irb.rb - multiple irb module -# by Keiju ISHITSUKA([email protected]) -# - -module IRB - class JobManager # :nodoc: - - # Creates a new JobManager object - def initialize - @jobs = [] - @current_job = nil - end - - # The active irb session - attr_accessor :current_job - - # The total number of irb sessions, used to set +irb_name+ of the current - # Context. - def n_jobs - @jobs.size - end - - # Returns the thread for the given +key+ object, see #search for more - # information. - def thread(key) - th, = search(key) - th - end - - # Returns the irb session for the given +key+ object, see #search for more - # information. - def irb(key) - _, irb = search(key) - irb - end - - # Returns the top level thread. - def main_thread - @jobs[0][0] - end - - # Returns the top level irb session. - def main_irb - @jobs[0][1] - end - - # Add the given +irb+ session to the jobs Array. - def insert(irb) - @jobs.push [Thread.current, irb] - end - - # Changes the current active irb session to the given +key+ in the jobs - # Array. - # - # Raises an IrbAlreadyDead exception if the given +key+ is no longer alive. - # - # If the given irb session is already active, an IrbSwitchedToCurrentThread - # exception is raised. - def switch(key) - th, irb = search(key) - fail IrbAlreadyDead unless th.alive? - fail IrbSwitchedToCurrentThread if th == Thread.current - @current_job = irb - th.run - Thread.stop - @current_job = irb(Thread.current) - end - - # Terminates the irb sessions specified by the given +keys+. - # - # Raises an IrbAlreadyDead exception if one of the given +keys+ is already - # terminated. - # - # See Thread#exit for more information. - def kill(*keys) - for key in keys - th, _ = search(key) - fail IrbAlreadyDead unless th.alive? - th.exit - end - end - - # Returns the associated job for the given +key+. - # - # If given an Integer, it will return the +key+ index for the jobs Array. - # - # When an instance of Irb is given, it will return the irb session - # associated with +key+. - # - # If given an instance of Thread, it will return the associated thread - # +key+ using Object#=== on the jobs Array. - # - # Otherwise returns the irb session with the same top-level binding as the - # given +key+. - # - # Raises a NoSuchJob exception if no job can be found with the given +key+. - def search(key) - job = case key - when Integer - @jobs[key] - when Irb - @jobs.find{|k, v| v.equal?(key)} - when Thread - @jobs.assoc(key) - else - @jobs.find{|k, v| v.context.main.equal?(key)} - end - fail NoSuchJob, key if job.nil? - job - end - - # Deletes the job at the given +key+. - def delete(key) - case key - when Integer - fail NoSuchJob, key unless @jobs[key] - @jobs[key] = nil - else - catch(:EXISTS) do - @jobs.each_index do - |i| - if @jobs[i] and (@jobs[i][0] == key || - @jobs[i][1] == key || - @jobs[i][1].context.main.equal?(key)) - @jobs[i] = nil - throw :EXISTS - end - end - fail NoSuchJob, key - end - end - until assoc = @jobs.pop; end unless @jobs.empty? - @jobs.push assoc - end - - # Outputs a list of jobs, see the irb command +irb_jobs+, or +jobs+. - def inspect - ary = [] - @jobs.each_index do - |i| - th, irb = @jobs[i] - next if th.nil? - - if th.alive? - if th.stop? - t_status = "stop" - else - t_status = "running" - end - else - t_status = "exited" - end - ary.push format("#%d->%s on %s (%s: %s)", - i, - irb.context.irb_name, - irb.context.main, - th, - t_status) - end - ary.join("\n") - end - end - - @JobManager = JobManager.new - - # The current JobManager in the session - def IRB.JobManager # :nodoc: - @JobManager - end - - # The current Context in this session - def IRB.CurrentContext # :nodoc: - IRB.JobManager.irb(Thread.current).context - end - - # Creates a new IRB session, see Irb.new. - # - # 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) # :nodoc: - workspace = WorkSpace.new(*main) - parent_thread = Thread.current - Thread.start do - begin - irb = Irb.new(workspace, file) - rescue - print "Subirb can't start with context(self): ", workspace.main.inspect, "\n" - print "return to main irb\n" - Thread.pass - Thread.main.wakeup - Thread.exit - end - @CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC] - @JobManager.insert(irb) - @JobManager.current_job = irb - begin - system_exit = false - catch(:IRB_EXIT) do - irb.eval_input - end - rescue SystemExit - system_exit = true - raise - #fail - ensure - unless system_exit - @JobManager.delete(irb) - if @JobManager.current_job == irb - if parent_thread.alive? - @JobManager.current_job = @JobManager.irb(parent_thread) - parent_thread.run - else - @JobManager.current_job = @JobManager.main_irb - @JobManager.main_thread.run - end - end - end - end - end - Thread.stop - @JobManager.current_job = @JobManager.irb(Thread.current) - end - - @CONF[:SINGLE_IRB_MODE] = false - @JobManager.insert(@CONF[:MAIN_CONTEXT].irb) - @JobManager.current_job = @CONF[:MAIN_CONTEXT].irb - - class Irb - def signal_handle - unless @context.ignore_sigint? - print "\nabort!!\n" if @context.verbose? - exit - end - - case @signal_status - when :IN_INPUT - print "^C\n" - IRB.JobManager.thread(self).raise RubyLex::TerminateLineInput - when :IN_EVAL - IRB.irb_abort(self) - when :IN_LOAD - IRB.irb_abort(self, LoadAbort) - when :IN_IRB - # ignore - else - # ignore other cases as well - end - end - end - - trap("SIGINT") do - @JobManager.current_job.signal_handle - Thread.stop - end - -end diff --git a/lib/irb/ext/tracer.rb b/lib/irb/ext/tracer.rb deleted file mode 100644 index fd6daa88ae..0000000000 --- a/lib/irb/ext/tracer.rb +++ /dev/null @@ -1,39 +0,0 @@ -# frozen_string_literal: true -# -# irb/lib/tracer.rb - -# by Keiju ISHITSUKA([email protected]) -# -# 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." - return # This is about to disable loading below -end - -module IRB - class CallTracer < ::CallTracer - IRB_DIR = File.expand_path('../..', __dir__) - - 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 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) - end - else - __evaluate__(statements, file, line) - end - end - end -end diff --git a/lib/irb/ext/use-loader.rb b/lib/irb/ext/use-loader.rb deleted file mode 100644 index c8a3ea1fe8..0000000000 --- a/lib/irb/ext/use-loader.rb +++ /dev/null @@ -1,67 +0,0 @@ -# frozen_string_literal: true -# -# use-loader.rb - -# by Keiju ISHITSUKA([email protected]) -# - -require_relative "../command/load" -require_relative "loader" - -class Object - alias __original__load__IRB_use_loader__ load - alias __original__require__IRB_use_loader__ require -end - -module IRB - module ExtendCommandBundle - 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) - 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) - Command::Require.execute(irb_context, *opts, &b) - end - end - - class Context - - IRB.conf[:USE_LOADER] = false - - # Returns whether +irb+'s own file reader method is used by - # +load+/+require+ or not. - # - # This mode is globally affected (irb-wide). - def use_loader - IRB.conf[:USE_LOADER] - end - - alias use_loader? use_loader - - remove_method :use_loader= if method_defined?(:use_loader=) - # Sets <code>IRB.conf[:USE_LOADER]</code> - # - # See #use_loader for more information. - def use_loader=(opt) - - if IRB.conf[:USE_LOADER] != opt - IRB.conf[:USE_LOADER] = opt - if opt - (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 { - alias_method :load, :__original__load__IRB_use_loader__ - alias_method :require, :__original__require__IRB_use_loader__ - } - end - end - print "Switch to load/require#{unless use_loader; ' non';end} trace mode.\n" if verbose? - opt - end - end -end diff --git a/lib/irb/ext/workspaces.rb b/lib/irb/ext/workspaces.rb deleted file mode 100644 index da09faa83e..0000000000 --- a/lib/irb/ext/workspaces.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true -# -# push-ws.rb - -# by Keiju ISHITSUKA([email protected]) -# - -module IRB # :nodoc: - class Context - # Creates a new workspace with the given object or binding, and appends it - # onto the current #workspaces stack. - # - # See IRB::Context#change_workspace and IRB::WorkSpace.new for more - # information. - def push_workspace(*_main) - if _main.empty? - 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 - else - new_workspace = WorkSpace.new(workspace.binding, _main[0]) - @workspace_stack.push new_workspace - new_workspace.load_helper_methods_to_main - end - end - - # Removes the last element from the current #workspaces stack and returns - # it, or +nil+ if the current workspace stack is empty. - # - # Also, see #push_workspace. - def pop_workspace - @workspace_stack.pop if @workspace_stack.size > 1 - end - end -end diff --git a/lib/irb/frame.rb b/lib/irb/frame.rb deleted file mode 100644 index 4b697c8719..0000000000 --- a/lib/irb/frame.rb +++ /dev/null @@ -1,80 +0,0 @@ -# frozen_string_literal: true -# -# frame.rb - -# by Keiju ISHITSUKA(Nihon Rational Software Co.,Ltd) -# - -module IRB - class Frame - class FrameOverflow < StandardError - def initialize - super("frame overflow") - end - end - class FrameUnderflow < StandardError - def initialize - super("frame underflow") - end - end - - # Default number of stack frames - INIT_STACK_TIMES = 3 - # Default number of frames offset - CALL_STACK_OFFSET = 3 - - # Creates a new stack frame - def initialize - @frames = [TOPLEVEL_BINDING] * INIT_STACK_TIMES - end - - # Used by Kernel#set_trace_func to register each event in the call stack - def trace_func(event, file, line, id, binding) - case event - when 'call', 'class' - @frames.push binding - when 'return', 'end' - @frames.pop - end - end - - # Returns the +n+ number of frames on the call stack from the last frame - # initialized. - # - # Raises FrameUnderflow if there are no frames in the given stack range. - def top(n = 0) - bind = @frames[-(n + CALL_STACK_OFFSET)] - fail FrameUnderflow unless bind - bind - end - - # Returns the +n+ number of frames on the call stack from the first frame - # initialized. - # - # Raises FrameOverflow if there are no frames in the given stack range. - def bottom(n = 0) - bind = @frames[n] - fail FrameOverflow unless bind - bind - end - - # Convenience method for Frame#bottom - def Frame.bottom(n = 0) - @backtrace.bottom(n) - end - - # Convenience method for Frame#top - def Frame.top(n = 0) - @backtrace.top(n) - end - - # Returns the binding context of the caller from the last frame initialized - def Frame.sender - eval "self", @backtrace.top - end - - @backtrace = Frame.new - set_trace_func proc{|event, file, line, id, binding, klass| - @backtrace.trace_func(event, file, line, id, binding) - } - end -end diff --git a/lib/irb/help.rb b/lib/irb/help.rb deleted file mode 100644 index a24bc10a15..0000000000 --- a/lib/irb/help.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true -# -# irb/help.rb - print usage module -# by Keiju ISHITSUKA([email protected]) -# - -module IRB - # Outputs the irb help message, see IRB@Command-Line+Options. - def IRB.print_usage # :nodoc: - lc = IRB.conf[:LC_MESSAGES] - path = lc.find("irb/help-message") - space_line = false - File.open(path){|f| - f.each_line do |l| - if /^\s*$/ =~ l - lc.puts l unless space_line - space_line = true - next - end - space_line = false - - l.sub!(/#.*$/, "") - next if /^\s*$/ =~ l - lc.puts l - end - } - end -end diff --git a/lib/irb/helper_method.rb b/lib/irb/helper_method.rb deleted file mode 100644 index f1f6fff915..0000000000 --- a/lib/irb/helper_method.rb +++ /dev/null @@ -1,29 +0,0 @@ -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 deleted file mode 100644 index a68001ed28..0000000000 --- a/lib/irb/helper_method/base.rb +++ /dev/null @@ -1,16 +0,0 @@ -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 deleted file mode 100644 index 718ed279c0..0000000000 --- a/lib/irb/helper_method/conf.rb +++ /dev/null @@ -1,11 +0,0 @@ -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 deleted file mode 100644 index 0beff15539..0000000000 --- a/lib/irb/history.rb +++ /dev/null @@ -1,116 +0,0 @@ -require "pathname" - -module IRB - module History - DEFAULT_ENTRY_LIMIT = 1000 - - class << self - # Integer representation of <code>IRB.conf[:HISTORY_FILE]</code>. - def save_history - return 0 if IRB.conf[:SAVE_HISTORY] == false - return DEFAULT_ENTRY_LIMIT if IRB.conf[:SAVE_HISTORY] == true - IRB.conf[:SAVE_HISTORY].to_i - end - - def save_history? - !save_history.zero? - end - - def infinite? - save_history.negative? - end - - # Might be nil when HOME and XDG_CONFIG_HOME are not available. - def history_file - if (history_file = IRB.conf[:HISTORY_FILE]) - File.expand_path(history_file) - else - IRB.rc_file("_history") - end - end - end - end - - module HistorySavingAbility # :nodoc: - def support_history_saving? - true - end - - def reset_history_counter - @loaded_history_lines = self.class::HISTORY.size - end - - def load_history - history_file = History.history_file - return unless File.exist?(history_file.to_s) - - history = self.class::HISTORY - - File.open(history_file, "r:#{IRB.conf[:LC_MESSAGES].encoding}") do |f| - f.each { |l| - l = l.chomp - if self.class == RelineInputMethod and history.last&.end_with?("\\") - history.last.delete_suffix!("\\") - history.last << "\n" << l - else - history << l - end - } - end - @loaded_history_lines = history.size - @loaded_history_mtime = File.mtime(history_file) - end - - def save_history - return unless History.save_history? - return unless (history_file = History.history_file) - unless ensure_history_file_writable(history_file) - warn <<~WARN - Can't write history to #{History.history_file.inspect} due to insufficient permissions. - Please verify the value of `IRB.conf[:HISTORY_FILE]`. Ensure the folder exists and that both the folder and file (if it exists) are writable. - WARN - return - end - - history = self.class::HISTORY.to_a - - if File.exist?(history_file) && - File.mtime(history_file) != @loaded_history_mtime - history = history[@loaded_history_lines..-1] if @loaded_history_lines - append_history = true - 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 || History.infinite? - # Check size before slicing because array.last(huge_number) raises RangeError. - hist = hist.last(History.save_history) if hist.size > History.save_history - end - - f.puts(hist) - end - end - - private - - # Returns boolean whether writing to +history_file+ will be possible. - # Permissions of already existing +history_file+ are changed to - # owner-only-readable if necessary [BUG #7694]. - def ensure_history_file_writable(history_file) - history_file = Pathname.new(history_file) - - return false unless history_file.dirname.writable? - return true unless history_file.exist? - - begin - if history_file.stat.mode & 0o66 != 0 - history_file.chmod 0o600 - end - true - rescue Errno::EPERM # no permissions - false - end - end - end -end diff --git a/lib/irb/init.rb b/lib/irb/init.rb deleted file mode 100644 index 720c4fec46..0000000000 --- a/lib/irb/init.rb +++ /dev/null @@ -1,540 +0,0 @@ -# frozen_string_literal: true -# -# irb/init.rb - irb initialize module -# by Keiju ISHITSUKA([email protected]) -# - -module IRB # :nodoc: - @CONF = {} - @INITIALIZED = false - # Displays current configuration. - # - # Modifying the configuration is achieved by sending a message to IRB.conf. - # - # See IRB@Configuration for more information. - def IRB.conf - @CONF - end - - def @CONF.inspect - array = [] - for k, v in sort{|a1, a2| a1[0].id2name <=> a2[0].id2name} - case k - when :MAIN_CONTEXT, :__TMP__EHV__ - array.push format("CONF[:%s]=...myself...", k.id2name) - when :PROMPT - s = v.collect{ - |kk, vv| - ss = vv.collect{|kkk, vvv| ":#{kkk.id2name}=>#{vvv.inspect}"} - format(":%s=>{%s}", kk.id2name, ss.join(", ")) - } - array.push format("CONF[:%s]={%s}", k.id2name, s.join(", ")) - else - array.push format("CONF[:%s]=%s", k.id2name, v.inspect) - end - end - array.join("\n") - end - - # Returns the current version of IRB, including release version and last - # updated date. - def IRB.version - 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) - IRB.init_error - IRB.parse_opts(argv: argv) - IRB.run_config - IRB.validate_config - IRB.load_modules - - unless @CONF[:PROMPT][@CONF[:PROMPT_MODE]] - fail UndefinedPromptMode, @CONF[:PROMPT_MODE] - end - @INITIALIZED = true - end - - # @CONF default setting - def IRB.init_config(ap_path) - # default configurations - unless ap_path and @CONF[:AP_NAME] - ap_path = File.join(File.dirname(File.dirname(__FILE__)), "irb.rb") - end - @CONF[:VERSION] = version - @CONF[:AP_NAME] = File::basename(ap_path, ".rb") - - @CONF[:IRB_NAME] = "irb" - @CONF[:IRB_LIB_PATH] = File.dirname(__FILE__) - - @CONF[:RC] = true - @CONF[:LOAD_MODULES] = [] - @CONF[:IRB_RC] = nil - - @CONF[:USE_SINGLELINE] = false unless defined?(ReadlineInputMethod) - @CONF[:USE_COLORIZE] = (nc = ENV['NO_COLOR']).nil? || nc.empty? - @CONF[:USE_AUTOCOMPLETE] = ENV.fetch("IRB_USE_AUTOCOMPLETE", "true") != "false" - @CONF[:COMPLETOR] = ENV["IRB_COMPLETOR"]&.to_sym - @CONF[:INSPECT_MODE] = true - @CONF[:USE_TRACER] = false - @CONF[:USE_LOADER] = false - @CONF[:IGNORE_SIGINT] = true - @CONF[:IGNORE_EOF] = false - @CONF[:USE_PAGER] = true - @CONF[:EXTRA_DOC_DIRS] = [] - @CONF[:ECHO] = nil - @CONF[:ECHO_ON_ASSIGNMENT] = nil - @CONF[:VERBOSE] = nil - - @CONF[:EVAL_HISTORY] = nil - @CONF[:SAVE_HISTORY] = History::DEFAULT_ENTRY_LIMIT - - @CONF[:BACK_TRACE_LIMIT] = 16 - - @CONF[:PROMPT] = { - :NULL => { - :PROMPT_I => nil, - :PROMPT_S => nil, - :PROMPT_C => nil, - :RETURN => "%s\n" - }, - :DEFAULT => { - :PROMPT_I => "%N(%m):%03n> ", - :PROMPT_S => "%N(%m):%03n%l ", - :PROMPT_C => "%N(%m):%03n* ", - :RETURN => "=> %s\n" - }, - :CLASSIC => { - :PROMPT_I => "%N(%m):%03n:%i> ", - :PROMPT_S => "%N(%m):%03n:%i%l ", - :PROMPT_C => "%N(%m):%03n:%i* ", - :RETURN => "%s\n" - }, - :SIMPLE => { - :PROMPT_I => ">> ", - :PROMPT_S => "%l> ", - :PROMPT_C => "?> ", - :RETURN => "=> %s\n" - }, - :INF_RUBY => { - :PROMPT_I => "%N(%m):%03n> ", - :PROMPT_S => nil, - :PROMPT_C => nil, - :RETURN => "%s\n", - :AUTO_INDENT => true - }, - :XMP => { - :PROMPT_I => nil, - :PROMPT_S => nil, - :PROMPT_C => nil, - :RETURN => " ==>%s\n" - } - } - - @CONF[:PROMPT_MODE] = (STDIN.tty? ? :DEFAULT : :NULL) - @CONF[:AUTO_INDENT] = true - - @CONF[:CONTEXT_MODE] = 4 # use a copy of TOPLEVEL_BINDING - @CONF[:SINGLE_IRB] = false - - @CONF[:MEASURE] = false - @CONF[:MEASURE_PROC] = {} - @CONF[:MEASURE_PROC][:TIME] = proc { |context, code, line_no, &block| - time = Time.now - result = block.() - now = Time.now - puts 'processing time: %fs' % (now - time) if IRB.conf[:MEASURE] - result - } - # arg can be either a symbol for the mode (:cpu, :wall, ..) or a hash for - # a more complete configuration. - # See https://github.com/tmm1/stackprof#all-options. - @CONF[:MEASURE_PROC][:STACKPROF] = proc { |context, code, line_no, arg, &block| - return block.() unless IRB.conf[:MEASURE] - success = false - begin - require 'stackprof' - success = true - rescue LoadError - puts 'Please run "gem install stackprof" before measuring by StackProf.' - end - if success - result = nil - arg = { mode: arg || :cpu } unless arg.is_a?(Hash) - stackprof_result = StackProf.run(**arg) do - result = block.() - end - case stackprof_result - when File - puts "StackProf report saved to #{stackprof_result.path}" - when Hash - StackProf::Report.new(stackprof_result).print_text - else - puts "Stackprof ran with #{arg.inspect}" - end - result - else - block.() - end - } - @CONF[:MEASURE_CALLBACKS] = [] - - @CONF[:LC_MESSAGES] = Locale.new - - @CONF[:AT_EXIT] = [] - - @CONF[:COMMAND_ALIASES] = { - # Symbol aliases - :'$' => :show_source, - :'@' => :whereami, - } - - @CONF[:COPY_COMMAND] = ENV.fetch("IRB_COPY_COMMAND", nil) - end - - def IRB.set_measure_callback(type = nil, arg = nil, &block) - added = nil - if type - type_sym = type.upcase.to_sym - if IRB.conf[:MEASURE_PROC][type_sym] - added = [type_sym, IRB.conf[:MEASURE_PROC][type_sym], arg] - end - elsif IRB.conf[:MEASURE_PROC][:CUSTOM] - added = [:CUSTOM, IRB.conf[:MEASURE_PROC][:CUSTOM], arg] - elsif block_given? - added = [:BLOCK, block, arg] - found = IRB.conf[:MEASURE_CALLBACKS].find{ |m| m[0] == added[0] && m[2] == added[2] } - if found - found[1] = block - return added - else - IRB.conf[:MEASURE_CALLBACKS] << added - return added - end - else - added = [:TIME, IRB.conf[:MEASURE_PROC][:TIME], arg] - end - if added - IRB.conf[:MEASURE] = true - found = IRB.conf[:MEASURE_CALLBACKS].find{ |m| m[0] == added[0] && m[2] == added[2] } - if found - # already added - nil - else - IRB.conf[:MEASURE_CALLBACKS] << added if added - added - end - else - nil - end - end - - def IRB.unset_measure_callback(type = nil) - if type.nil? - IRB.conf[:MEASURE_CALLBACKS].clear - else - type_sym = type.upcase.to_sym - IRB.conf[:MEASURE_CALLBACKS].reject!{ |t, | t == type_sym } - end - IRB.conf[:MEASURE] = nil if IRB.conf[:MEASURE_CALLBACKS].empty? - end - - def IRB.init_error - @CONF[:LC_MESSAGES].load("irb/error.rb") - end - - # option analyzing - def IRB.parse_opts(argv: ::ARGV) - load_path = [] - while opt = argv.shift - case opt - when "-f" - @CONF[:RC] = false - when "-d" - $DEBUG = true - $VERBOSE = true - when "-w" - Warning[:deprecated] = $VERBOSE = true - when /^-W(.+)?/ - opt = $1 || argv.shift - case opt - when "0" - $VERBOSE = nil - when "1" - $VERBOSE = false - else - Warning[:deprecated] = $VERBOSE = true - end - when /^-r(.+)?/ - opt = $1 || argv.shift - @CONF[:LOAD_MODULES].push opt if opt - when /^-I(.+)?/ - opt = $1 || argv.shift - load_path.concat(opt.split(File::PATH_SEPARATOR)) if opt - when '-U' - set_encoding("UTF-8", "UTF-8") - when /^-E(.+)?/, /^--encoding(?:=(.+))?/ - opt = $1 || argv.shift - set_encoding(*opt.split(':', 2)) - when "--inspect" - if /^-/ !~ argv.first - @CONF[:INSPECT_MODE] = argv.shift - else - @CONF[:INSPECT_MODE] = true - end - when "--noinspect" - @CONF[:INSPECT_MODE] = false - when "--no-pager" - @CONF[:USE_PAGER] = false - when "--singleline", "--readline", "--legacy" - @CONF[:USE_SINGLELINE] = true - when "--nosingleline", "--noreadline" - @CONF[:USE_SINGLELINE] = false - when "--multiline", "--reidline" - if opt == "--reidline" - warn <<~MSG.strip - --reidline is deprecated, please use --multiline instead. - MSG - end - - @CONF[:USE_MULTILINE] = true - when "--nomultiline", "--noreidline" - if opt == "--noreidline" - warn <<~MSG.strip - --noreidline is deprecated, please use --nomultiline instead. - MSG - end - - @CONF[:USE_MULTILINE] = false - when /^--extra-doc-dir(?:=(.+))?/ - opt = $1 || argv.shift - @CONF[:EXTRA_DOC_DIRS] << opt - when "--echo" - @CONF[:ECHO] = true - when "--noecho" - @CONF[:ECHO] = false - when "--echo-on-assignment" - @CONF[:ECHO_ON_ASSIGNMENT] = true - when "--noecho-on-assignment" - @CONF[:ECHO_ON_ASSIGNMENT] = false - when "--truncate-echo-on-assignment" - @CONF[:ECHO_ON_ASSIGNMENT] = :truncate - when "--verbose" - @CONF[:VERBOSE] = true - when "--noverbose" - @CONF[:VERBOSE] = false - when "--colorize" - @CONF[:USE_COLORIZE] = true - when "--nocolorize" - @CONF[:USE_COLORIZE] = false - when "--autocomplete" - @CONF[:USE_AUTOCOMPLETE] = true - when "--noautocomplete" - @CONF[:USE_AUTOCOMPLETE] = false - when "--regexp-completor" - @CONF[:COMPLETOR] = :regexp - when "--type-completor" - @CONF[:COMPLETOR] = :type - when /^--prompt-mode(?:=(.+))?/, /^--prompt(?:=(.+))?/ - opt = $1 || argv.shift - prompt_mode = opt.upcase.tr("-", "_").intern - @CONF[:PROMPT_MODE] = prompt_mode - when "--noprompt" - @CONF[:PROMPT_MODE] = :NULL - when "--script" - noscript = false - when "--noscript" - noscript = true - when "--inf-ruby-mode" - @CONF[:PROMPT_MODE] = :INF_RUBY - when "--sample-book-mode", "--simple-prompt" - @CONF[:PROMPT_MODE] = :SIMPLE - when "--tracer" - @CONF[:USE_TRACER] = true - when /^--back-trace-limit(?:=(.+))?/ - @CONF[:BACK_TRACE_LIMIT] = ($1 || argv.shift).to_i - when /^--context-mode(?:=(.+))?/ - @CONF[:CONTEXT_MODE] = ($1 || argv.shift).to_i - when "--single-irb" - @CONF[:SINGLE_IRB] = true - when "-v", "--version" - print IRB.version, "\n" - exit 0 - when "-h", "--help" - require_relative "help" - IRB.print_usage - exit 0 - when "--" - if !noscript && (opt = argv.shift) - @CONF[:SCRIPT] = opt - $0 = opt - end - break - when /^-./ - fail UnrecognizedSwitch, opt - else - if noscript - argv.unshift(opt) - else - @CONF[:SCRIPT] = opt - $0 = opt - end - break - end - end - - load_path.collect! do |path| - /\A\.\// =~ path ? path : File.expand_path(path) - end - $LOAD_PATH.unshift(*load_path) - end - - # Run the config file - def IRB.run_config - if @CONF[:RC] - irbrc_files.each do |rc| - load rc - rescue StandardError, ScriptError => e - warn "Error loading RC file '#{rc}':\n#{e.full_message(highlight: false)}" - end - end - end - - IRBRC_EXT = "rc" - - 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 - - # 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 - - def IRB.irbrc_files - prepare_irbrc_name_generators - @irbrc_files - end - - def IRB.validate_config - conf[:IRB_NAME] = conf[:IRB_NAME].to_s - - irb_rc = conf[:IRB_RC] - unless irb_rc.nil? || irb_rc.respond_to?(:call) - raise_validation_error "IRB.conf[:IRB_RC] should be a callable object. Got #{irb_rc.inspect}." - end - - back_trace_limit = conf[:BACK_TRACE_LIMIT] - unless back_trace_limit.is_a?(Integer) - raise_validation_error "IRB.conf[:BACK_TRACE_LIMIT] should be an integer. Got #{back_trace_limit.inspect}." - end - - prompt = conf[:PROMPT] - unless prompt.is_a?(Hash) - msg = "IRB.conf[:PROMPT] should be a Hash. Got #{prompt.inspect}." - - if prompt.is_a?(Symbol) - msg += " Did you mean to set `IRB.conf[:PROMPT_MODE]`?" - end - - raise_validation_error msg - end - - eval_history = conf[:EVAL_HISTORY] - unless eval_history.nil? || eval_history.is_a?(Integer) - raise_validation_error "IRB.conf[:EVAL_HISTORY] should be an integer. Got #{eval_history.inspect}." - end - end - - def IRB.raise_validation_error(msg) - raise TypeError, msg, @irbrc_files - end - - # loading modules - def IRB.load_modules - for m in @CONF[:LOAD_MODULES] - begin - require m - rescue LoadError => err - warn "#{err.class}: #{err}", uplevel: 0 - end - end - end - - 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? - Encoding.default_internal = intern unless intern.nil? || intern.empty? - [$stdin, $stdout, $stderr].each do |io| - io.set_encoding(extern, intern) - end - if override - @CONF[:LC_MESSAGES].instance_variable_set(:@override_encoding, extern) - else - @CONF[:LC_MESSAGES].instance_variable_set(:@encoding, extern) - end - ensure - $VERBOSE = verbose - end - end -end diff --git a/lib/irb/input-method.rb b/lib/irb/input-method.rb deleted file mode 100644 index 260d9a1cbf..0000000000 --- a/lib/irb/input-method.rb +++ /dev/null @@ -1,515 +0,0 @@ -# frozen_string_literal: true -# -# irb/input-method.rb - input methods used irb -# by Keiju ISHITSUKA([email protected]) -# - -require_relative 'completion' -require_relative "history" -require 'io/console' -require 'reline' - -module IRB - class InputMethod - BASIC_WORD_BREAK_CHARACTERS = " \t\n`><=;|&{(" - - # The irb prompt associated with this input method - attr_accessor :prompt - - # Reads the next line from this input method. - # - # See IO#gets for more information. - def gets - fail NotImplementedError - end - public :gets - - def winsize - if instance_variable_defined?(:@stdout) && @stdout.tty? - @stdout.winsize - else - [24, 80] - end - end - - # Whether this input method is still readable when there is no more data to - # read. - # - # See IO#eof for more information. - def readable_after_eof? - false - end - - def support_history_saving? - false - end - - def prompting? - false - end - - # For debug message - def inspect - 'Abstract InputMethod' - end - end - - class StdioInputMethod < InputMethod - # Creates a new input method object - def initialize - @line_no = 0 - @line = [] - @stdin = IO.open(STDIN.to_i, :external_encoding => IRB.conf[:LC_MESSAGES].encoding, :internal_encoding => "-") - @stdout = IO.open(STDOUT.to_i, 'w', :external_encoding => IRB.conf[:LC_MESSAGES].encoding, :internal_encoding => "-") - end - - # Reads the next line from this input method. - # - # See IO#gets for more information. - def gets - # Workaround for debug compatibility test https://github.com/ruby/debug/pull/1100 - puts if ENV['RUBY_DEBUG_TEST_UI'] - - print @prompt - line = @stdin.gets - @line[@line_no += 1] = line - end - - # Whether the end of this input method has been reached, returns +true+ if - # there is no more data to read. - # - # See IO#eof? for more information. - def eof? - if @stdin.wait_readable(0.00001) - c = @stdin.getc - result = c.nil? ? true : false - @stdin.ungetc(c) unless c.nil? - result - else # buffer is empty - false - end - end - - # Whether this input method is still readable when there is no more data to - # read. - # - # See IO#eof for more information. - def readable_after_eof? - true - end - - def prompting? - STDIN.tty? - end - - # Returns the current line number for #io. - # - # #line counts the number of times #gets is called. - # - # See IO#lineno for more information. - def line(line_no) - @line[line_no] - end - - # The external encoding for standard input. - def encoding - @stdin.external_encoding - end - - # For debug message - def inspect - 'StdioInputMethod' - end - end - - # Use a File for IO with irb, see InputMethod - class FileInputMethod < InputMethod - class << self - def open(file, &block) - begin - io = new(file) - block.call(io) - ensure - io&.close - end - end - end - - # Creates a new input method object - def initialize(file) - @io = file.is_a?(IO) ? file : File.open(file) - @external_encoding = @io.external_encoding - end - - # Whether the end of this input method has been reached, returns +true+ if - # there is no more data to read. - # - # See IO#eof? for more information. - def eof? - @io.closed? || @io.eof? - end - - # Reads the next line from this input method. - # - # See IO#gets for more information. - def gets - print @prompt - @io.gets - end - - # The external encoding for standard input. - def encoding - @external_encoding - end - - # For debug message - def inspect - 'FileInputMethod' - end - - def close - @io.close - end - end - - class ReadlineInputMethod < StdioInputMethod - class << self - def initialize_readline - require "readline" - rescue LoadError - else - include ::Readline - end - end - - include HistorySavingAbility - - # Creates a new input method object using Readline - def initialize - self.class.initialize_readline - if Readline.respond_to?(:encoding_system_needs) - IRB.__send__(:set_encoding, Readline.encoding_system_needs.name, override: false) - end - - super - - @eof = false - @completor = RegexpCompletor.new - - if Readline.respond_to?("basic_word_break_characters=") - Readline.basic_word_break_characters = BASIC_WORD_BREAK_CHARACTERS - end - Readline.completion_append_character = nil - Readline.completion_proc = ->(target) { - bind = IRB.conf[:MAIN_CONTEXT].workspace.binding - @completor.completion_candidates('', target, '', bind: bind) - } - end - - def completion_info - 'RegexpCompletor' - end - - # Reads the next line from this input method. - # - # See IO#gets for more information. - def gets - Readline.input = @stdin - Readline.output = @stdout - if l = readline(@prompt, false) - HISTORY.push(l) if !l.empty? - @line[@line_no += 1] = l + "\n" - else - @eof = true - l - end - end - - # Whether the end of this input method has been reached, returns +true+ - # if there is no more data to read. - # - # See IO#eof? for more information. - def eof? - @eof - end - - def prompting? - true - end - - # For debug message - def inspect - readline_impl = (defined?(Reline) && Readline == Reline) ? 'Reline' : 'ext/readline' - str = "ReadlineInputMethod with #{readline_impl} #{Readline::VERSION}" - inputrc_path = File.expand_path(ENV['INPUTRC'] || '~/.inputrc') - str += " and #{inputrc_path}" if File.exist?(inputrc_path) - str - end - end - - class RelineInputMethod < StdioInputMethod - HISTORY = Reline::HISTORY - include HistorySavingAbility - # Creates a new input method object using Reline - def initialize(completor) - IRB.__send__(:set_encoding, Reline.encoding_system_needs.name, override: false) - - super() - - @eof = false - @completor = completor - - Reline.basic_word_break_characters = BASIC_WORD_BREAK_CHARACTERS - Reline.completion_append_character = nil - Reline.completer_quote_characters = '' - Reline.completion_proc = ->(target, preposing, postposing) { - bind = IRB.conf[:MAIN_CONTEXT].workspace.binding - @completion_params = [preposing, target, postposing, bind] - @completor.completion_candidates(preposing, target, postposing, bind: bind) - } - Reline.output_modifier_proc = proc do |input, complete:| - IRB.CurrentContext.colorize_input(input, complete: complete) - end - Reline.dig_perfect_match_proc = ->(matched) { display_document(matched) } - Reline.autocompletion = IRB.conf[:USE_AUTOCOMPLETE] - - if IRB.conf[:USE_AUTOCOMPLETE] - begin - require 'rdoc' - Reline.add_dialog_proc(:show_doc, show_doc_dialog_proc, Reline::DEFAULT_DIALOG_CONTEXT) - rescue LoadError - end - end - end - - def completion_info - autocomplete_message = Reline.autocompletion ? 'Autocomplete' : 'Tab Complete' - "#{autocomplete_message}, #{@completor.inspect}" - end - - def check_termination(&block) - @check_termination_proc = block - end - - def dynamic_prompt(&block) - @prompt_proc = block - end - - def auto_indent(&block) - @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 - input_method = self # self is changed in the lambda below. - ->() { - dialog.trap_key = nil - alt_d = [ - [27, 100], # Normal Alt+d when convert-meta isn't used. - # When option/alt is not configured as a meta key in terminal emulator, - # option/alt + d will send a unicode character depend on OS keyboard setting. - [195, 164], # "ä" in somewhere (FIXME: environment information is unknown). - [226, 136, 130] # "∂" Alt+d on Mac keyboard. - ] - - if just_cursor_moving and completion_journey_data.nil? - return nil - end - cursor_pos_to_render, result, pointer, autocomplete_dialog = context.pop(4) - return nil if result.nil? or pointer.nil? or pointer < 0 - - 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'] - - driver = input_method.rdoc_ri_driver - - if key.match?(dialog.name) - if show_easter_egg - IRB.__send__(:easter_egg) - else - # RDoc::RI::Driver#display_names uses pager command internally. - # Some pager command like `more` doesn't use alternate screen - # so we need to turn on and off alternate screen manually. - begin - print "\e[?1049h" - driver.display_names([name]) - rescue RDoc::RI::Driver::NotFoundError - ensure - print "\e[?1049l" - end - end - end - - begin - name = driver.expand_name(name) - rescue RDoc::RI::Driver::NotFoundError - return nil - rescue - return nil # unknown error - end - doc = nil - used_for_class = false - if not name =~ /#|\./ - found, klasses, includes, extends = driver.classes_and_includes_and_extends_for(name) - if not found.empty? - doc = driver.class_document(name, found, klasses, includes, extends) - used_for_class = true - end - end - unless used_for_class - doc = RDoc::Markup::Document.new - begin - driver.add_method(doc, name) - rescue RDoc::RI::Driver::NotFoundError - doc = nil - rescue - return nil # unknown error - end - end - return nil if doc.nil? - width = 40 - - right_x = cursor_pos_to_render.x + autocomplete_dialog.width - if right_x + width > screen_width - right_width = screen_width - (right_x + 1) - left_x = autocomplete_dialog.column - width - left_x = 0 if left_x < 0 - left_width = width > autocomplete_dialog.column ? autocomplete_dialog.column : width - if right_width.positive? and left_width.positive? - if right_width >= left_width - width = right_width - x = right_x - else - width = left_width - x = left_x - end - elsif right_width.positive? and left_width <= 0 - width = right_width - x = right_x - elsif right_width <= 0 and left_width.positive? - width = left_width - x = left_x - else # Both are negative width. - return nil - end - else - x = right_x - end - formatter = RDoc::Markup::ToAnsi.new - formatter.width = width - dialog.trap_key = alt_d - mod_key = RUBY_PLATFORM.match?(/darwin/) ? "Option" : "Alt" - if show_easter_egg - type = STDOUT.external_encoding == Encoding::UTF_8 ? :unicode : :ascii - contents = IRB.send(:easter_egg_logo, type).split("\n") - message = "Press #{mod_key}+d to see more" - contents[0][0, message.size] = message - else - message = "Press #{mod_key}+d to read the full document" - contents = [message] + doc.accept(formatter).split("\n") - end - contents = contents.take(preferred_dialog_height) - - y = cursor_pos_to_render.y - Reline::DialogRenderInfo.new(pos: Reline::CursorPos.new(x, y), contents: contents, width: width, bg_color: '49') - } - 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 - - namespace = retrieve_doc_namespace(matched) - return unless namespace - - if namespace.is_a?(Array) - out = RDoc::Markup::Document.new - namespace.each do |m| - begin - driver.add_method(out, m) - rescue RDoc::RI::Driver::NotFoundError - end - end - driver.display(out) - else - begin - driver.display_names([namespace]) - rescue RDoc::RI::Driver::NotFoundError - end - end - end - - # Reads the next line from this input method. - # - # See IO#gets for more information. - def gets - Reline.input = @stdin - Reline.output = @stdout - Reline.prompt_proc = @prompt_proc - Reline.auto_indent_proc = @auto_indent_proc if @auto_indent_proc - if l = Reline.readmultiline(@prompt, false, &@check_termination_proc) - Reline::HISTORY.push(l) if !l.empty? - @line[@line_no += 1] = l + "\n" - else - @eof = true - l - end - end - - # Whether the end of this input method has been reached, returns +true+ - # if there is no more data to read. - # - # See IO#eof? for more information. - def eof? - @eof - end - - def prompting? - true - end - - # For debug message - def inspect - config = Reline::Config.new - str = "RelineInputMethod with Reline #{Reline::VERSION}" - inputrc_path = File.expand_path(config.inputrc_path) - str += " and #{inputrc_path}" if File.exist?(inputrc_path) - str - end - end - - class ReidlineInputMethod < RelineInputMethod - def initialize - warn <<~MSG.strip - IRB::ReidlineInputMethod is deprecated, please use IRB::RelineInputMethod instead. - MSG - super - end - end -end diff --git a/lib/irb/inspector.rb b/lib/irb/inspector.rb deleted file mode 100644 index 75a257b4ba..0000000000 --- a/lib/irb/inspector.rb +++ /dev/null @@ -1,136 +0,0 @@ -# frozen_string_literal: true -# -# irb/inspector.rb - inspect methods -# by Keiju ISHITSUKA([email protected]) -# - -module IRB # :nodoc: - - # Convenience method to create a new Inspector, using the given +inspect+ - # proc, and optional +init+ proc and passes them to Inspector.new - # - # irb(main):001:0> ins = IRB::Inspector(proc{ |v| "omg! #{v}" }) - # irb(main):001:0> IRB.CurrentContext.inspect_mode = ins # => omg! #<IRB::Inspector:0x007f46f7ba7d28> - # irb(main):001:0> "what?" #=> omg! what? - # - def IRB::Inspector(inspect, init = nil) - Inspector.new(inspect, init) - end - - # An irb inspector - # - # In order to create your own custom inspector there are two things you - # should be aware of: - # - # Inspector uses #inspect_value, or +inspect_proc+, for output of return values. - # - # This also allows for an optional #init+, or +init_proc+, which is called - # when the inspector is activated. - # - # Knowing this, you can create a rudimentary inspector as follows: - # - # irb(main):001:0> ins = IRB::Inspector.new(proc{ |v| "omg! #{v}" }) - # irb(main):001:0> IRB.CurrentContext.inspect_mode = ins # => omg! #<IRB::Inspector:0x007f46f7ba7d28> - # irb(main):001:0> "what?" #=> omg! what? - # - class Inspector - KERNEL_INSPECT = Object.instance_method(:inspect) - # Default inspectors available to irb, this includes: - # - # +:pp+:: Using Kernel#pretty_inspect - # +:yaml+:: Using YAML.dump - # +:marshal+:: Using Marshal.dump - INSPECTORS = {} - - class << self - # Determines the inspector to use where +inspector+ is one of the keys passed - # during inspector definition. - def keys_with_inspector(inspector) - INSPECTORS.select{|k, v| v == inspector}.collect{|k, v| k} - end - - # Example - # - # Inspector.def_inspector(key, init_p=nil){|v| v.inspect} - # Inspector.def_inspector([key1,..], init_p=nil){|v| v.inspect} - # Inspector.def_inspector(key, inspector) - # Inspector.def_inspector([key1,...], inspector) - def def_inspector(key, arg=nil, &block) - if block_given? - inspector = IRB::Inspector(block, arg) - else - inspector = arg - end - - case key - when Array - for k in key - def_inspector(k, inspector) - end - when Symbol - INSPECTORS[key] = inspector - INSPECTORS[key.to_s] = inspector - when String - INSPECTORS[key] = inspector - INSPECTORS[key.intern] = inspector - else - INSPECTORS[key] = inspector - end - end - end - - # Creates a new inspector object, using the given +inspect_proc+ when - # output return values in irb. - def initialize(inspect_proc, init_proc = nil) - @init = init_proc - @inspect = inspect_proc - end - - # Proc to call when the inspector is activated, good for requiring - # dependent libraries. - def init - @init.call if @init - end - - def support_stream_output? - second_parameter_type = @inspect.parameters[1]&.first - second_parameter_type == :req || second_parameter_type == :opt - end - - # Proc to call when the input is evaluated and output in irb. - def inspect_value(v, output, colorize: true) - support_stream_output? ? @inspect.call(v, output, colorize: colorize) : output << @inspect.call(v, colorize: colorize) - rescue => e - puts "An error occurred when inspecting the object: #{e.inspect}" - - begin - puts "Result of Kernel#inspect: #{KERNEL_INSPECT.bind_call(v)}" - '' - rescue => e - puts "An error occurred when running Kernel#inspect: #{e.inspect}" - puts e.backtrace.join("\n") - '' - end - end - end - - Inspector.def_inspector([false, :to_s, :raw]){|v| v.to_s} - Inspector.def_inspector([:p, :inspect]){|v, colorize: true| - Color.colorize_code(v.inspect, colorable: colorize && Color.colorable? && Color.inspect_colorable?(v)) - } - Inspector.def_inspector([true, :pp, :pretty_inspect], proc{require_relative "color_printer"}){|v, output, colorize: true| - IRB::ColorPrinter.pp(v, output, colorize: colorize) - } - Inspector.def_inspector([:yaml, :YAML], proc{require "yaml"}){|v| - begin - YAML.dump(v) - rescue - puts "(can't dump yaml. use inspect)" - v.inspect - end - } - - Inspector.def_inspector([:marshal, :Marshal, :MARSHAL, Marshal]){|v| - Marshal.dump(v) - } -end diff --git a/lib/irb/irb.gemspec b/lib/irb/irb.gemspec deleted file mode 100644 index 9a93382b7a..0000000000 --- a/lib/irb/irb.gemspec +++ /dev/null @@ -1,46 +0,0 @@ -begin - require_relative "lib/irb/version" -rescue LoadError - # for Ruby core repository - require_relative "version" -end - -Gem::Specification.new do |spec| - spec.name = "irb" - spec.version = IRB::VERSION - spec.authors = ["aycabta", "Keiju ISHITSUKA"] - spec.email = ["[email protected]", "[email protected]"] - - spec.summary = %q{Interactive Ruby command-line tool for REPL (Read Eval Print Loop).} - spec.description = %q{Interactive Ruby command-line tool for REPL (Read Eval Print Loop).} - spec.homepage = "https://github.com/ruby/irb" - spec.licenses = ["Ruby", "BSD-2-Clause"] - - spec.metadata["homepage_uri"] = spec.homepage - spec.metadata["source_code_uri"] = spec.homepage - spec.metadata["documentation_uri"] = "https://ruby.github.io/irb/" - spec.metadata["changelog_uri"] = "#{spec.homepage}/releases" - - spec.files = [ - "Gemfile", - "LICENSE.txt", - "README.md", - "Rakefile", - "bin/console", - "bin/setup", - "doc/irb/irb-tools.rd.ja", - "doc/irb/irb.rd.ja", - "exe/irb", - "irb.gemspec", - "man/irb.1", - ] + Dir.glob("lib/**/*") - spec.bindir = "exe" - spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } - spec.require_paths = ["lib"] - - spec.required_ruby_version = Gem::Requirement.new(">= 2.7") - - spec.add_dependency "reline", ">= 0.4.2" - spec.add_dependency "rdoc", ">= 4.0.0" - spec.add_dependency "pp", ">= 0.6.0" -end diff --git a/lib/irb/lc/error.rb b/lib/irb/lc/error.rb deleted file mode 100644 index ee0f047822..0000000000 --- a/lib/irb/lc/error.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true -# -# irb/lc/error.rb - -# by Keiju ISHITSUKA([email protected]) -# - -module IRB - # :stopdoc: - - class UnrecognizedSwitch < StandardError - def initialize(val) - super("Unrecognized switch: #{val}") - end - end - class CantReturnToNormalMode < StandardError - def initialize - super("Can't return to normal mode.") - end - end - class IllegalParameter < StandardError - def initialize(val) - super("Invalid parameter(#{val}).") - end - end - class IrbAlreadyDead < StandardError - def initialize - super("Irb is already dead.") - end - end - class IrbSwitchedToCurrentThread < StandardError - def initialize - super("Switched to current thread.") - end - end - class NoSuchJob < StandardError - def initialize(val) - super("No such job(#{val}).") - end - end - class CantChangeBinding < StandardError - def initialize(val) - super("Can't change binding to (#{val}).") - end - end - class UndefinedPromptMode < StandardError - def initialize(val) - super("Undefined prompt mode(#{val}).") - end - end - - # :startdoc: -end diff --git a/lib/irb/lc/help-message b/lib/irb/lc/help-message deleted file mode 100644 index 37347306e8..0000000000 --- a/lib/irb/lc/help-message +++ /dev/null @@ -1,55 +0,0 @@ -Usage: irb.rb [options] [programfile] [arguments] - -f Don't initialize from configuration file. - -d Set $DEBUG and $VERBOSE to true (same as 'ruby -d'). - -r load-module Require load-module (same as 'ruby -r'). - -I path Specify $LOAD_PATH directory (same as 'ruby -I'). - -U Set external and internal encodings to UTF-8. - -E ex[:in] Set default external (ex) and internal (in) encodings - (same as 'ruby -E'). - -w Suppress warnings (same as 'ruby -w'). - -W[level=2] Set warning level: 0=silence, 1=medium, 2=verbose - (same as 'ruby -W'). - --context-mode n Set n[0-4] to method to create Binding Object, - when new workspace was created. - --extra-doc-dir Add an extra doc dir for the doc dialog. - --echo Show result (default). - --noecho Don't show result. - --echo-on-assignment - Show result on assignment. - --noecho-on-assignment - Don't show result on assignment. - --truncate-echo-on-assignment - Show truncated result on assignment (default). - --inspect Use 'inspect' for output. - --noinspect Don't use 'inspect' for output. - --no-pager Don't use pager. - --multiline Use multiline editor module (default). - --nomultiline Don't use multiline editor module. - --singleline Use single line editor module. - --nosingleline Don't use single line editor module (default). - --colorize Use color-highlighting (default). - --nocolorize Don't use color-highlighting. - --autocomplete Use auto-completion (default). - --noautocomplete Don't use auto-completion. - --regexp-completor - Use regexp based completion (default). - --type-completor Use type based completion. - --prompt prompt-mode, --prompt-mode prompt-mode - Set prompt mode. Pre-defined prompt modes are: - 'default', 'classic', 'simple', 'inf-ruby', 'xmp', 'null'. - --inf-ruby-mode Use prompt appropriate for inf-ruby-mode on emacs. - Suppresses --multiline and --singleline. - --sample-book-mode, --simple-prompt - Set prompt mode to 'simple'. - --noprompt Don't output prompt. - --script Script mode (default, treat first argument as script) - --noscript No script mode (leave arguments in argv) - --single-irb Share self with sub-irb. - --tracer Show stack trace for each command. - --back-trace-limit n[=16] - Display backtrace top n and bottom n. - --verbose Show details. - --noverbose Don't show details. - -v, --version Print the version of irb. - -h, --help Print help. - -- Separate options of irb from the list of command-line args. diff --git a/lib/irb/lc/ja/error.rb b/lib/irb/lc/ja/error.rb deleted file mode 100644 index 9e2e5b8870..0000000000 --- a/lib/irb/lc/ja/error.rb +++ /dev/null @@ -1,53 +0,0 @@ -# frozen_string_literal: true -# -# irb/lc/ja/error.rb - -# by Keiju ISHITSUKA([email protected]) -# - -module IRB - # :stopdoc: - - class UnrecognizedSwitch < StandardError - def initialize(val) - super("スイッチ(#{val})が分りません") - end - end - class CantReturnToNormalMode < StandardError - def initialize - super("Normalモードに戻れません.") - end - end - class IllegalParameter < StandardError - def initialize(val) - super("パラメータ(#{val})が間違っています.") - end - end - class IrbAlreadyDead < StandardError - def initialize - super("Irbは既に死んでいます.") - end - end - class IrbSwitchedToCurrentThread < StandardError - def initialize - super("カレントスレッドに切り替わりました.") - end - end - class NoSuchJob < StandardError - def initialize(val) - super("そのようなジョブ(#{val})はありません.") - end - end - class CantChangeBinding < StandardError - def initialize(val) - super("バインディング(#{val})に変更できません.") - end - end - class UndefinedPromptMode < StandardError - def initialize(val) - super("プロンプトモード(#{val})は定義されていません.") - end - end - - # :startdoc: -end -# vim:fileencoding=utf-8 diff --git a/lib/irb/lc/ja/help-message b/lib/irb/lc/ja/help-message deleted file mode 100644 index 844c67bbba..0000000000 --- a/lib/irb/lc/ja/help-message +++ /dev/null @@ -1,58 +0,0 @@ -Usage: irb.rb [options] [programfile] [arguments] - -f ~/.irbrc を読み込まない. - -d $DEBUG をtrueにする(ruby -d と同じ) - -r load-module ruby -r と同じ. - -I path $LOAD_PATH に path を追加する. - -U ruby -U と同じ. - -E enc ruby -E と同じ. - -w ruby -w と同じ. - -W[level=2] ruby -W と同じ. - --context-mode n 新しいワークスペースを作成した時に関連する Binding - オブジェクトの作成方法を 0 から 4 のいずれかに設定する. - --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 シングルラインエディタを利用する. - --nosingleline シングルラインエディタを利用しない. - --colorize 色付けを利用する. - --nocolorize 色付けを利用しない. - --autocomplete オートコンプリートを利用する. - --noautocomplete オートコンプリートを利用しない. - --regexp-completor - 補完に正規表現を利用する. - --type-completor 補完に型情報を利用する. - --prompt prompt-mode/--prompt-mode prompt-mode - プロンプトモードを切り替える. - 現在定義されているプロンプトモードは, - default, classic, simple, inf-ruby, xmp, null. - --inf-ruby-mode emacsのinf-ruby-mode用のプロンプト表示を行なう. 特 - に指定がない限り, シングルラインエディタとマルチラ - インエディタは使わなくなる. - --sample-book-mode/--simple-prompt - 非常にシンプルなプロンプトを用いるモードです. - --noprompt プロンプト表示を行なわない. - --script スクリプトモード(最初の引数をスクリプトファイルとして扱う、デフォルト) - --noscript 引数をargvとして扱う. - --single-irb irb 中で self を実行して得られるオブジェクトをサ - ブ irb と共有する. - --tracer コマンド実行時にトレースを行なう. - --back-trace-limit n - バックトレース表示をバックトレースの頭から n, 後ろ - からnだけ行なう. デフォルトは16 - - --verbose 詳細なメッセージを出力する. - --noverbose 詳細なメッセージを出力しない(デフォルト). - -v, --version irbのバージョンを表示する. - -h, --help irb のヘルプを表示する. - -- 以降のコマンドライン引数をオプションとして扱わない. diff --git a/lib/irb/locale.rb b/lib/irb/locale.rb deleted file mode 100644 index 2abcc7354b..0000000000 --- a/lib/irb/locale.rb +++ /dev/null @@ -1,153 +0,0 @@ -# frozen_string_literal: true -# -# irb/locale.rb - internationalization module -# by Keiju ISHITSUKA([email protected]) -# - -module IRB # :nodoc: - class Locale - - LOCALE_NAME_RE = %r[ - (?<language>[[:alpha:]]{2,3}) - (?:_ (?<territory>[[:alpha:]]{2,3}) )? - (?:\. (?<codeset>[^@]+) )? - (?:@ (?<modifier>.*) )? - ]x - LOCALE_DIR = "/lc/" - - LEGACY_ENCODING_ALIAS_MAP = { - 'ujis' => Encoding::EUC_JP, - 'euc' => Encoding::EUC_JP - } - - @@loaded = [] - - def initialize(locale = nil) - @override_encoding = nil - @lang = @territory = @encoding_name = @modifier = nil - @locale = locale || ENV["IRB_LANG"] || ENV["LC_MESSAGES"] || ENV["LC_ALL"] || ENV["LANG"] || "C" - if m = LOCALE_NAME_RE.match(@locale) - @lang, @territory, @encoding_name, @modifier = m[:language], m[:territory], m[:codeset], m[:modifier] - - if @encoding_name - if @encoding = LEGACY_ENCODING_ALIAS_MAP[@encoding_name] - warn(("%s is obsolete. use %s" % ["#{@lang}_#{@territory}.#{@encoding_name}", "#{@lang}_#{@territory}.#{@encoding.name}"]), uplevel: 1) - else - @encoding = Encoding.find(@encoding_name) rescue nil - end - end - end - @encoding ||= (Encoding.find('locale') rescue Encoding::ASCII_8BIT) - end - - attr_reader :lang, :territory, :modifier - - def encoding - @override_encoding || @encoding - end - - def String(mes) - mes = super(mes) - if encoding - mes.encode(encoding, undef: :replace) - else - mes - end - end - - def format(*opts) - String(super(*opts)) - end - - def gets(*rs) - String(super(*rs)) - end - - def readline(*rs) - String(super(*rs)) - end - - def print(*opts) - ary = opts.collect{|opt| String(opt)} - super(*ary) - end - - def printf(*opts) - s = format(*opts) - print s - end - - def puts(*opts) - ary = opts.collect{|opt| String(opt)} - super(*ary) - end - - def load(file) - found = find(file) - if found - unless @@loaded.include?(found) - @@loaded << found # cache - Kernel.load(found) - end - else - raise LoadError, "No such file to load -- #{file}" - end - end - - def find(file, paths = $:) - dir = File.dirname(file) - dir = "" if dir == "." - base = File.basename(file) - - if dir.start_with?('/') - return each_localized_path(dir, base).find{|full_path| File.readable? full_path} - else - return search_file(paths, dir, base) - end - end - - # @param paths load paths in which IRB find a localized file. - # @param dir directory - # @param file basename to be localized - # - # typically, for the parameters and a <path> in paths, it searches - # <path>/<dir>/<locale>/<file> - def search_file(lib_paths, dir, file) - each_localized_path(dir, file) do |lc_path| - lib_paths.each do |libpath| - full_path = File.join(libpath, lc_path) - return full_path if File.readable?(full_path) - end - redo if defined?(Gem) and Gem.try_activate(lc_path) - end - nil - end - - def each_localized_path(dir, file) - return enum_for(:each_localized_path) unless block_given? - each_sublocale do |lc| - yield lc.nil? ? File.join(dir, LOCALE_DIR, file) : File.join(dir, LOCALE_DIR, lc, file) - end - end - - def each_sublocale - if @lang - if @territory - if @encoding_name - yield "#{@lang}_#{@territory}.#{@encoding_name}@#{@modifier}" if @modifier - yield "#{@lang}_#{@territory}.#{@encoding_name}" - end - yield "#{@lang}_#{@territory}@#{@modifier}" if @modifier - yield "#{@lang}_#{@territory}" - end - if @encoding_name - yield "#{@lang}.#{@encoding_name}@#{@modifier}" if @modifier - yield "#{@lang}.#{@encoding_name}" - end - yield "#{@lang}@#{@modifier}" if @modifier - yield "#{@lang}" - end - yield nil - end - end -end diff --git a/lib/irb/nesting_parser.rb b/lib/irb/nesting_parser.rb deleted file mode 100644 index c1c9a5cc76..0000000000 --- a/lib/irb/nesting_parser.rb +++ /dev/null @@ -1,239 +0,0 @@ -# frozen_string_literal: true -module IRB - module NestingParser - IGNORE_TOKENS = %i[on_sp on_ignored_nl on_comment on_embdoc_beg on_embdoc on_embdoc_end] - - class << self - # Scan each token and call the given block with array of token and other information for parsing - def scan_opens(tokens) - opens = [] - pending_heredocs = [] - first_token_on_line = true - tokens.each do |t| - 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 - skip = true - end - when :in_lambda_head - opens.pop if t.event == :on_tlambeg || (t.event == :on_kw && t.tok == 'do') - when :in_method_head - unless IGNORE_TOKENS.include?(t.event) - next_args = [] - body = nil - if args.include?(:receiver) - case t.event - when :on_lparen, :on_ivar, :on_gvar, :on_cvar - # def (receiver). | def @ivar. | def $gvar. | def @@cvar. - next_args << :dot - when :on_kw - case t.tok - when 'self', 'true', 'false', 'nil' - # def self(arg) | def self. - next_args.push(:arg, :dot) - else - # def if(arg) - skip = true - next_args << :arg - end - when :on_op, :on_backtick - # def +(arg) - skip = true - next_args << :arg - when :on_ident, :on_const - # def a(arg) | def a. - next_args.push(:arg, :dot) - end - end - if args.include?(:dot) - # def receiver.name - next_args << :name if t.event == :on_period || (t.event == :on_op && t.tok == '::') - end - if args.include?(:name) - if %i[on_ident on_const on_op on_kw on_backtick].include?(t.event) - # def name(arg) | def receiver.name(arg) - next_args << :arg - skip = true - end - end - if args.include?(:arg) - case t.event - when :on_nl, :on_semicolon - # def receiver.f; - body = :normal - when :on_lparen - # def receiver.f() - next_args << :eq - else - if t.event == :on_op && t.tok == '=' - # def receiver.f = - body = :oneliner - else - # def receiver.f arg - next_args << :arg_without_paren - end - end - end - if args.include?(:eq) - if t.event == :on_op && t.tok == '=' - body = :oneliner - else - body = :normal - end - end - if args.include?(:arg_without_paren) - if %i[on_semicolon on_nl].include?(t.event) - # def f a; - body = :normal - else - # def f a, b - next_args << :arg_without_paren - end - end - if body == :oneliner - opens.pop - elsif body - opens[-1] = [last_tok, nil] - else - opens[-1] = [last_tok, :in_method_head, next_args] - end - end - when :in_for_while_until_condition - if t.event == :on_semicolon || t.event == :on_nl || (t.event == :on_kw && t.tok == 'do') - skip = true if t.event == :on_kw && t.tok == 'do' - opens[-1] = [last_tok, nil] - end - end - - unless skip - case t.event - when :on_kw - case t.tok - when 'begin', 'class', 'module', 'do', 'case' - opens << [t, nil] - when 'end' - opens.pop - when 'def' - opens << [t, :in_method_head, [:receiver, :name]] - when 'if', 'unless' - unless t.state.allbits?(Ripper::EXPR_LABEL) - opens << [t, nil] - end - when 'while', 'until' - unless t.state.allbits?(Ripper::EXPR_LABEL) - opens << [t, :in_for_while_until_condition] - end - when 'ensure', 'rescue' - unless t.state.allbits?(Ripper::EXPR_LABEL) - 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] - when 'for' - opens << [t, :in_for_while_until_condition] - when 'in' - if last_tok&.event == :on_kw && %w[case in].include?(last_tok.tok) && first_token_on_line - opens.pop - opens << [t, nil] - end - end - when :on_tlambda - opens << [t, :in_lambda_head] - when :on_lparen, :on_lbracket, :on_lbrace, :on_tlambeg, :on_embexpr_beg, :on_embdoc_beg - opens << [t, nil] - when :on_rparen, :on_rbracket, :on_rbrace, :on_embexpr_end, :on_embdoc_end - opens.pop - when :on_heredoc_beg - pending_heredocs << t - when :on_heredoc_end - opens.pop - when :on_backtick - opens << [t, nil] unless t.state == Ripper::EXPR_ARG - when :on_tstring_beg, :on_words_beg, :on_qwords_beg, :on_symbols_beg, :on_qsymbols_beg, :on_regexp_beg - opens << [t, nil] - when :on_tstring_end, :on_regexp_end, :on_label_end - opens.pop - when :on_symbeg - if t.tok == ':' - opens << [t, :in_unquoted_symbol] - else - opens << [t, nil] - end - end - end - if t.event == :on_nl || t.event == :on_semicolon - first_token_on_line = true - elsif t.event != :on_sp - first_token_on_line = false - end - if pending_heredocs.any? && t.tok.include?("\n") - 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 - end - - def open_tokens(tokens) - # scan_opens without block will return a list of open tokens at last token position - scan_opens(tokens) - end - - # Calculates token information [line_tokens, prev_opens, next_opens, min_depth] for each line. - # Example code - # ["hello - # world"+( - # First line - # line_tokens: [[lbracket, '['], [tstring_beg, '"'], [tstring_content("hello\nworld"), "hello\n"]] - # prev_opens: [] - # next_tokens: [lbracket, tstring_beg] - # min_depth: 0 (minimum at beginning of line) - # Second line - # line_tokens: [[tstring_content("hello\nworld"), "world"], [tstring_end, '"'], [op, '+'], [lparen, '(']] - # prev_opens: [lbracket, tstring_beg] - # next_tokens: [lbracket, lparen] - # min_depth: 1 (minimum just after tstring_end) - def parse_by_line(tokens) - line_tokens = [] - prev_opens = [] - min_depth = 0 - output = [] - last_opens = scan_opens(tokens) do |t, opens| - depth = t == opens.last&.first ? opens.size - 1 : opens.size - min_depth = depth if depth < min_depth - if t.tok.include?("\n") - t.tok.each_line do |line| - line_tokens << [t, line] - next if line[-1] != "\n" - next_opens = opens.map(&:first) - output << [line_tokens, prev_opens, next_opens, min_depth] - prev_opens = next_opens - min_depth = prev_opens.size - line_tokens = [] - end - else - line_tokens << [t, t.tok] - end - end - output << [line_tokens, prev_opens, last_opens, min_depth] if line_tokens.any? - output - end - end - end -end diff --git a/lib/irb/notifier.rb b/lib/irb/notifier.rb deleted file mode 100644 index dc1b9ef14b..0000000000 --- a/lib/irb/notifier.rb +++ /dev/null @@ -1,230 +0,0 @@ -# frozen_string_literal: true -# -# notifier.rb - output methods used by irb -# by Keiju ISHITSUKA([email protected]) -# - -require_relative "output-method" - -module IRB - # An output formatter used internally by the lexer. - module Notifier - class ErrUndefinedNotifier < StandardError - def initialize(val) - super("undefined notifier level: #{val} is specified") - end - end - class ErrUnrecognizedLevel < StandardError - def initialize(val) - super("unrecognized notifier level: #{val} is specified") - end - end - - # Define a new Notifier output source, returning a new CompositeNotifier - # with the given +prefix+ and +output_method+. - # - # The optional +prefix+ will be appended to all objects being inspected - # during output, using the given +output_method+ as the output source. If - # no +output_method+ is given, StdioOutputMethod will be used, and all - # expressions will be sent directly to STDOUT without any additional - # formatting. - def def_notifier(prefix = "", output_method = StdioOutputMethod.new) - CompositeNotifier.new(prefix, output_method) - end - module_function :def_notifier - - # An abstract class, or superclass, for CompositeNotifier and - # LeveledNotifier to inherit. It provides several wrapper methods for the - # OutputMethod object used by the Notifier. - class AbstractNotifier - # Creates a new Notifier object - def initialize(prefix, base_notifier) - @prefix = prefix - @base_notifier = base_notifier - end - - # The +prefix+ for this Notifier, which is appended to all objects being - # inspected during output. - attr_reader :prefix - - # A wrapper method used to determine whether notifications are enabled. - # - # Defaults to +true+. - def notify? - true - end - - # See OutputMethod#print for more detail. - def print(*opts) - @base_notifier.print prefix, *opts if notify? - end - - # See OutputMethod#printn for more detail. - def printn(*opts) - @base_notifier.printn prefix, *opts if notify? - end - - # See OutputMethod#printf for more detail. - def printf(format, *opts) - @base_notifier.printf(prefix + format, *opts) if notify? - end - - # See OutputMethod#puts for more detail. - def puts(*objs) - if notify? - @base_notifier.puts(*objs.collect{|obj| prefix + obj.to_s}) - end - end - - # Same as #ppx, except it uses the #prefix given during object - # initialization. - # See OutputMethod#ppx for more detail. - def pp(*objs) - if notify? - @base_notifier.ppx @prefix, *objs - end - end - - # Same as #pp, except it concatenates the given +prefix+ with the #prefix - # given during object initialization. - # - # See OutputMethod#ppx for more detail. - def ppx(prefix, *objs) - if notify? - @base_notifier.ppx @prefix+prefix, *objs - end - end - - # Execute the given block if notifications are enabled. - def exec_if - yield(@base_notifier) if notify? - end - end - - # A class that can be used to create a group of notifier objects with the - # intent of representing a leveled notification system for irb. - # - # This class will allow you to generate other notifiers, and assign them - # the appropriate level for output. - # - # The Notifier class provides a class-method Notifier.def_notifier to - # create a new composite notifier. Using the first composite notifier - # object you create, sibling notifiers can be initialized with - # #def_notifier. - class CompositeNotifier < AbstractNotifier - # Create a new composite notifier object with the given +prefix+, and - # +base_notifier+ to use for output. - def initialize(prefix, base_notifier) - super - - @notifiers = [D_NOMSG] - @level_notifier = D_NOMSG - end - - # List of notifiers in the group - attr_reader :notifiers - - # Creates a new LeveledNotifier in the composite #notifiers group. - # - # The given +prefix+ will be assigned to the notifier, and +level+ will - # be used as the index of the #notifiers Array. - # - # This method returns the newly created instance. - def def_notifier(level, prefix = "") - notifier = LeveledNotifier.new(self, level, prefix) - @notifiers[level] = notifier - notifier - end - - # Returns the leveled notifier for this object - attr_reader :level_notifier - alias level level_notifier - - # Sets the leveled notifier for this object. - # - # When the given +value+ is an instance of AbstractNotifier, - # #level_notifier is set to the given object. - # - # When an Integer is given, #level_notifier is set to the notifier at the - # index +value+ in the #notifiers Array. - # - # If no notifier exists at the index +value+ in the #notifiers Array, an - # ErrUndefinedNotifier exception is raised. - # - # An ErrUnrecognizedLevel exception is raised if the given +value+ is not - # found in the existing #notifiers Array, or an instance of - # AbstractNotifier - def level_notifier=(value) - case value - when AbstractNotifier - @level_notifier = value - when Integer - l = @notifiers[value] - raise ErrUndefinedNotifier, value unless l - @level_notifier = l - else - raise ErrUnrecognizedLevel, value unless l - end - end - - alias level= level_notifier= - end - - # A leveled notifier is comparable to the composite group from - # CompositeNotifier#notifiers. - class LeveledNotifier < AbstractNotifier - include Comparable - - # Create a new leveled notifier with the given +base+, and +prefix+ to - # send to AbstractNotifier.new - # - # The given +level+ is used to compare other leveled notifiers in the - # CompositeNotifier group to determine whether or not to output - # notifications. - def initialize(base, level, prefix) - super(prefix, base) - - @level = level - end - - # The current level of this notifier object - attr_reader :level - - # Compares the level of this notifier object with the given +other+ - # notifier. - # - # See the Comparable module for more information. - def <=>(other) - @level <=> other.level - end - - # Whether to output messages to the output method, depending on the level - # of this notifier object. - def notify? - @base_notifier.level >= self - end - end - - # NoMsgNotifier is a LeveledNotifier that's used as the default notifier - # when creating a new CompositeNotifier. - # - # This notifier is used as the +zero+ index, or level +0+, for - # CompositeNotifier#notifiers, and will not output messages of any sort. - class NoMsgNotifier < LeveledNotifier - # Creates a new notifier that should not be used to output messages. - def initialize - @base_notifier = nil - @level = 0 - @prefix = "" - end - - # Ensures notifications are ignored, see AbstractNotifier#notify? for - # more information. - def notify? - false - end - end - - D_NOMSG = NoMsgNotifier.new # :nodoc: - end -end diff --git a/lib/irb/output-method.rb b/lib/irb/output-method.rb deleted file mode 100644 index 69942f47a2..0000000000 --- a/lib/irb/output-method.rb +++ /dev/null @@ -1,80 +0,0 @@ -# frozen_string_literal: true -# -# output-method.rb - output methods used by irb -# by Keiju ISHITSUKA([email protected]) -# - -module IRB - # An abstract output class for IO in irb. This is mainly used internally by - # IRB::Notifier. You can define your own output method to use with Irb.new, - # or Context.new - class OutputMethod - # 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 - end - - # Prints the given +opts+, with a newline delimiter. - def printn(*opts) - print opts.join(" "), "\n" - end - - # Extends IO#printf to format the given +opts+ for Kernel#sprintf using - # #parse_printf_format - def printf(format, *opts) - if /(%*)%I/ =~ format - format, opts = parse_printf_format(format, opts) - end - print sprintf(format, *opts) - end - - # Returns an array of the given +format+ and +opts+ to be used by - # Kernel#sprintf, if there was a successful Regexp match in the given - # +format+ from #printf - # - # % - # <flag> [#0- +] - # <minimum field width> (\*|\*[1-9][0-9]*\$|[1-9][0-9]*) - # <precision>.(\*|\*[1-9][0-9]*\$|[1-9][0-9]*|)? - # #<length modifier>(hh|h|l|ll|L|q|j|z|t) - # <conversion specifier>[diouxXeEfgGcsb%] - def parse_printf_format(format, opts) - return format, opts if $1.size % 2 == 1 - end - - # Calls #print on each element in the given +objs+, followed by a newline - # character. - def puts(*objs) - for obj in objs - print(*obj) - print "\n" - end - end - - # Prints the given +objs+ calling Object#inspect on each. - # - # See #puts for more detail. - def pp(*objs) - puts(*objs.collect{|obj| obj.inspect}) - end - - # Prints the given +objs+ calling Object#inspect on each and appending the - # given +prefix+. - # - # See #puts for more detail. - def ppx(prefix, *objs) - puts(*objs.collect{|obj| prefix+obj.inspect}) - end - - end - - # A standard output printer - class StdioOutputMethod < OutputMethod - # Prints the given +opts+ to standard output, see IO#print for more - # information. - def print(*opts) - STDOUT.print(*opts) - end - end -end diff --git a/lib/irb/pager.rb b/lib/irb/pager.rb deleted file mode 100644 index 16ff30cf89..0000000000 --- a/lib/irb/pager.rb +++ /dev/null @@ -1,213 +0,0 @@ -# frozen_string_literal: true - -require 'reline' - -module IRB - # The implementation of this class is borrowed from RDoc's lib/rdoc/ri/driver.rb. - # Please do NOT use this class directly outside of IRB. - class Pager - PAGE_COMMANDS = [ENV['RI_PAGER'], ENV['PAGER'], 'less', 'more'].compact.uniq - - class << self - def page_content(content, **options) - if content_exceeds_screen_height?(content) - page(**options) do |io| - io.puts content - end - else - $stdout.puts content - end - end - - def page(retain_content: false) - if should_page? && pager = setup_pager(retain_content: retain_content) - begin - pid = pager.pid - yield pager - ensure - pager.close - end - else - yield $stdout - end - # When user presses Ctrl-C, IRB would raise `IRB::Abort` - # But since Pager is implemented by running paging commands like `less` in another process with `IO.popen`, - # the `IRB::Abort` exception only interrupts IRB's execution but doesn't affect the pager - # So to properly terminate the pager with Ctrl-C, we need to catch `IRB::Abort` and kill the pager process - rescue IRB::Abort - begin - begin - Process.kill("TERM", pid) if pid - rescue Errno::EINVAL - # SIGTERM not supported (windows) - Process.kill("KILL", pid) - end - rescue Errno::ESRCH - # Pager process already terminated - end - nil - rescue Errno::EPIPE - end - - def should_page? - IRB.conf[:USE_PAGER] && STDIN.tty? && (ENV.key?("TERM") && ENV["TERM"] != "dumb") - end - - def page_with_preview(width, height, formatter_proc) - overflow_callback = ->(lines) do - modified_output = formatter_proc.call(lines.join, true) - content, = take_first_page(width, [height - 2, 0].max) {|o| o.write modified_output } - content = content.chomp - content = "#{content}\e[0m" if Color.colorable? - $stdout.puts content - $stdout.puts 'Preparing full inspection value...' - end - out = PageOverflowIO.new(width, height, overflow_callback, delay: 0.1) - yield out - content = formatter_proc.call(out.string, out.multipage?) - if out.multipage? - page(retain_content: true) do |io| - io.puts content - end - else - $stdout.puts content - end - end - - def take_first_page(width, height) - overflow_callback = proc do |lines| - return lines.join, true - end - out = Pager::PageOverflowIO.new(width, height, overflow_callback) - yield out - [out.string, false] - end - - private - - def content_exceeds_screen_height?(content) - screen_height, screen_width = begin - Reline.get_screen_size - rescue Errno::EINVAL - [24, 80] - end - - pageable_height = screen_height - 3 # leave some space for previous and the current prompt - - return true if content.lines.size > pageable_height - - _, overflow = take_first_page(screen_width, pageable_height) {|out| out.write content } - overflow - end - - def setup_pager(retain_content:) - require 'shellwords' - - PAGE_COMMANDS.each do |pager_cmd| - cmd = Shellwords.split(pager_cmd) - next if cmd.empty? - - if cmd.first == 'less' - cmd << '-R' unless cmd.include?('-R') - cmd << '-X' if retain_content && !cmd.include?('-X') - end - - begin - io = IO.popen(cmd, 'w') - rescue - next - end - - if $? && $?.pid == io.pid && $?.exited? # pager didn't work - next - end - - return io - end - - nil - end - end - - # Writable IO that has page overflow callback - class PageOverflowIO - attr_reader :string, :first_page_lines - - # Maximum size of a single cell in terminal - # Assumed worst case: "\e[1;3;4;9;38;2;255;128;128;48;2;128;128;255mA\e[0m" - # bold, italic, underline, crossed_out, RGB forgound, RGB background - MAX_CHAR_PER_CELL = 50 - - def initialize(width, height, overflow_callback, delay: nil) - @lines = [] - @first_page_lines = nil - @width = width - @height = height - @buffer = +'' - @overflow_callback = overflow_callback - @col = 0 - @string = +'' - @multipage = false - @delay_until = (Time.now + delay if delay) - end - - def puts(text = '') - text = text.to_s unless text.is_a?(String) - write(text) - write("\n") unless text.end_with?("\n") - end - - def write(text) - text = text.to_s unless text.is_a?(String) - @string << text - if @multipage - if @delay_until && Time.now > @delay_until - @overflow_callback.call(@first_page_lines) - @delay_until = nil - end - return - end - - overflow_size = (@width * (@height - @lines.size) + @width - @col) * MAX_CHAR_PER_CELL - if text.size >= overflow_size - text = text[0, overflow_size] - overflow = true - end - @buffer << text - @col += Reline::Unicode.calculate_width(text, true) - if text.include?("\n") || @col >= @width - @buffer.lines.each do |line| - wrapped_lines = Reline::Unicode.split_by_width(line.chomp, @width).first.compact - wrapped_lines.pop if wrapped_lines.last == '' - @lines.concat(wrapped_lines) - if line.end_with?("\n") - if @lines.empty? || @lines.last.end_with?("\n") - @lines << "\n" - else - @lines[-1] += "\n" - end - end - end - @buffer.clear - @buffer << @lines.pop unless @lines.last.end_with?("\n") - @col = Reline::Unicode.calculate_width(@buffer, true) - end - if overflow || @lines.size > @height || (@lines.size == @height && @col > 0) - @first_page_lines = @lines.take(@height) - if !@delay_until || Time.now > @delay_until - @overflow_callback.call(@first_page_lines) - @delay_until = nil - end - @multipage = true - end - end - - def multipage? - @multipage - end - - alias print write - alias << write - end - end -end diff --git a/lib/irb/ruby-lex.rb b/lib/irb/ruby-lex.rb deleted file mode 100644 index 3abb53b4ea..0000000000 --- a/lib/irb/ruby-lex.rb +++ /dev/null @@ -1,476 +0,0 @@ -# frozen_string_literal: true -# -# irb/ruby-lex.rb - ruby lexcal analyzer -# by Keiju ISHITSUKA([email protected]) -# - -require "ripper" -require "jruby" if RUBY_ENGINE == "jruby" -require_relative "nesting_parser" - -module IRB - # :stopdoc: - class RubyLex - ASSIGNMENT_NODE_TYPES = [ - # Local, instance, global, class, constant, instance, and index assignment: - # "foo = bar", - # "@foo = bar", - # "$foo = bar", - # "@@foo = bar", - # "::Foo = bar", - # "a::Foo = bar", - # "Foo = bar" - # "foo.bar = 1" - # "foo[1] = bar" - :assign, - - # Operation assignment: - # "foo += bar" - # "foo -= bar" - # "foo ||= bar" - # "foo &&= bar" - :opassign, - - # Multiple assignment: - # "foo, bar = 1, 2 - :massign, - ] - - ERROR_TOKENS = [ - :on_parse_error, - :compile_error, - :on_assign_error, - :on_alias_error, - :on_class_name_error, - :on_param_error - ] - - LTYPE_TOKENS = %i[ - on_heredoc_beg on_tstring_beg - on_regexp_beg on_symbeg on_backtick - on_symbols_beg on_qsymbols_beg - on_words_beg on_qwords_beg - ] - - class TerminateLineInput < StandardError - def initialize - super("Terminate Line Input") - end - end - - class << self - def compile_with_errors_suppressed(code, line_no: 1) - begin - result = yield code, line_no - rescue ArgumentError - # Ruby can issue an error for the code if there is an - # incomplete magic comment for encoding in it. Force an - # expression with a new line before the code in this - # case to prevent magic comment handling. To make sure - # line numbers in the lexed code remain the same, - # decrease the line number by one. - code = ";\n#{code}" - line_no -= 1 - result = yield code, line_no - end - result - end - - def generate_local_variables_assign_code(local_variables) - "#{local_variables.join('=')}=nil;" unless local_variables.empty? - end - - # Some part of the code is not included in Ripper's token. - # Example: DATA part, token after heredoc_beg when heredoc has unclosed embexpr. - # With interpolated tokens, tokens.map(&:tok).join will be equal to code. - def interpolate_ripper_ignored_tokens(code, tokens) - line_positions = [0] - code.lines.each do |line| - line_positions << line_positions.last + line.bytesize - end - prev_byte_pos = 0 - interpolated = [] - prev_line = 1 - tokens.each do |t| - line, col = t.pos - byte_pos = line_positions[line - 1] + col - if prev_byte_pos < byte_pos - tok = code.byteslice(prev_byte_pos...byte_pos) - pos = [prev_line, prev_byte_pos - line_positions[prev_line - 1]] - interpolated << Ripper::Lexer::Elem.new(pos, :on_ignored_by_ripper, tok, 0) - prev_line += tok.count("\n") - end - interpolated << t - prev_byte_pos = byte_pos + t.tok.bytesize - prev_line += t.tok.count("\n") - end - if prev_byte_pos < code.bytesize - tok = code.byteslice(prev_byte_pos..) - pos = [prev_line, prev_byte_pos - line_positions[prev_line - 1]] - interpolated << Ripper::Lexer::Elem.new(pos, :on_ignored_by_ripper, tok, 0) - end - interpolated - end - - def ripper_lex_without_warning(code, local_variables: []) - verbose, $VERBOSE = $VERBOSE, nil - lvars_code = generate_local_variables_assign_code(local_variables) - original_code = code - if lvars_code - code = "#{lvars_code}\n#{code}" - line_no = 0 - else - line_no = 1 - end - - compile_with_errors_suppressed(code, line_no: line_no) do |inner_code, line_no| - lexer = Ripper::Lexer.new(inner_code, '-', line_no) - tokens = [] - lexer.scan.each do |t| - next if t.pos.first == 0 - prev_tk = tokens.last - position_overlapped = prev_tk && t.pos[0] == prev_tk.pos[0] && t.pos[1] < prev_tk.pos[1] + prev_tk.tok.bytesize - if position_overlapped - tokens[-1] = t if ERROR_TOKENS.include?(prev_tk.event) && !ERROR_TOKENS.include?(t.event) - else - tokens << t - end - end - interpolate_ripper_ignored_tokens(original_code, tokens) - end - ensure - $VERBOSE = verbose - end - end - - def check_code_state(code, local_variables:) - tokens = self.class.ripper_lex_without_warning(code, local_variables: local_variables) - opens = NestingParser.open_tokens(tokens) - [tokens, opens, code_terminated?(code, tokens, opens, local_variables: local_variables)] - end - - def code_terminated?(code, tokens, opens, local_variables:) - case check_code_syntax(code, local_variables: local_variables) - when :unrecoverable_error - true - when :recoverable_error - false - when :other_error - opens.empty? && !should_continue?(tokens) - when :valid - !should_continue?(tokens) - end - end - - def assignment_expression?(code, local_variables:) - # Try to parse the code and check if the last of possibly multiple - # expressions is an assignment type. - - # If the expression is invalid, Ripper.sexp should return nil which will - # result in false being returned. Any valid expression should return an - # s-expression where the second element of the top level array is an - # array of parsed expressions. The first element of each expression is the - # expression's type. - verbose, $VERBOSE = $VERBOSE, nil - code = "#{RubyLex.generate_local_variables_assign_code(local_variables) || 'nil;'}\n#{code}" - # Get the last node_type of the line. drop(1) is to ignore the local_variables_assign_code part. - node_type = Ripper.sexp(code)&.dig(1)&.drop(1)&.dig(-1, 0) - ASSIGNMENT_NODE_TYPES.include?(node_type) - ensure - $VERBOSE = verbose - end - - def should_continue?(tokens) - # Look at the last token and check if IRB need to continue reading next line. - # Example code that should continue: `a\` `a +` `a.` - # Trailing spaces, newline, comments are skipped - return true if tokens.last&.event == :on_sp && tokens.last.tok == "\\\n" - - tokens.reverse_each do |token| - case token.event - when :on_sp, :on_nl, :on_ignored_nl, :on_comment, :on_embdoc_beg, :on_embdoc, :on_embdoc_end - # Skip - when :on_regexp_end, :on_heredoc_end, :on_semicolon - # State is EXPR_BEG but should not continue - return false - else - # Endless range should not continue - return false if token.event == :on_op && token.tok.match?(/\A\.\.\.?\z/) - - # EXPR_DOT and most of the EXPR_BEG should continue - return token.state.anybits?(Ripper::EXPR_BEG | Ripper::EXPR_DOT) - end - end - false - end - - def check_code_syntax(code, local_variables:) - lvars_code = RubyLex.generate_local_variables_assign_code(local_variables) - code = "#{lvars_code}\n#{code}" - - begin # check if parser error are available - verbose, $VERBOSE = $VERBOSE, nil - case RUBY_ENGINE - when 'ruby' - self.class.compile_with_errors_suppressed(code) do |inner_code, line_no| - RubyVM::InstructionSequence.compile(inner_code, nil, nil, line_no) - end - when 'jruby' - JRuby.compile_ir(code) - else - catch(:valid) do - eval("BEGIN { throw :valid, true }\n#{code}") - false - end - end - rescue EncodingError - # This is for a hash with invalid encoding symbol, {"\xAE": 1} - :unrecoverable_error - rescue SyntaxError => e - case e.message - when /unexpected keyword_end/ - # "syntax error, unexpected keyword_end" - # - # example: - # if ( - # end - # - # example: - # end - return :unrecoverable_error - when /unexpected '\.'/ - # "syntax error, unexpected '.'" - # - # example: - # . - return :unrecoverable_error - when /unexpected tREGEXP_BEG/ - # "syntax error, unexpected tREGEXP_BEG, expecting keyword_do or '{' or '('" - # - # example: - # method / f / - return :unrecoverable_error - when /unterminated (?:string|regexp) meets end of file/ - # "unterminated regexp meets end of file" - # - # example: - # / - # - # "unterminated string meets end of file" - # - # example: - # ' - return :recoverable_error - when /unexpected end-of-input/ - # "syntax error, unexpected end-of-input, expecting keyword_end" - # - # example: - # if true - # hoge - # if false - # fuga - # end - return :recoverable_error - else - return :other_error - end - ensure - $VERBOSE = verbose - end - :valid - end - - def calc_indent_level(opens) - indent_level = 0 - opens.each_with_index do |t, index| - case t.event - when :on_heredoc_beg - if opens[index + 1]&.event != :on_heredoc_beg - if t.tok.match?(/^<<[~-]/) - indent_level += 1 - else - indent_level = 0 - end - end - when :on_tstring_beg, :on_regexp_beg, :on_symbeg, :on_backtick - # No indent: "", //, :"", `` - # Indent: %(), %r(), %i(), %x() - indent_level += 1 if t.tok.start_with? '%' - when :on_embdoc_beg - indent_level = 0 - else - indent_level += 1 unless t.tok == 'alias' || t.tok == 'undef' - end - end - indent_level - end - - FREE_INDENT_TOKENS = %i[on_tstring_beg on_backtick on_regexp_beg on_symbeg] - - def free_indent_token?(token) - FREE_INDENT_TOKENS.include?(token&.event) - end - - # Calculates the difference of pasted code's indent and indent calculated from tokens - def indent_difference(lines, line_results, line_index) - loop do - _tokens, prev_opens, _next_opens, min_depth = line_results[line_index] - open_token = prev_opens.last - if !open_token || (open_token.event != :on_heredoc_beg && !free_indent_token?(open_token)) - # If the leading whitespace is an indent, return the difference - indent_level = calc_indent_level(prev_opens.take(min_depth)) - calculated_indent = 2 * indent_level - actual_indent = lines[line_index][/^ */].size - return actual_indent - calculated_indent - elsif open_token.event == :on_heredoc_beg && open_token.tok.match?(/^<<[^-~]/) - return 0 - end - # If the leading whitespace is not an indent but part of a multiline token - # Calculate base_indent of the multiline token's beginning line - line_index = open_token.pos[0] - 1 - end - end - - def process_indent_level(tokens, lines, line_index, is_newline) - line_results = NestingParser.parse_by_line(tokens) - result = line_results[line_index] - if result - _tokens, prev_opens, next_opens, min_depth = result - else - # When last line is empty - prev_opens = next_opens = line_results.last[2] - min_depth = next_opens.size - end - - # To correctly indent line like `end.map do`, we use shortest open tokens on each line for indent calculation. - # Shortest open tokens can be calculated by `opens.take(min_depth)` - indent = 2 * calc_indent_level(prev_opens.take(min_depth)) - - preserve_indent = lines[line_index - (is_newline ? 1 : 0)][/^ */].size - - prev_open_token = prev_opens.last - next_open_token = next_opens.last - - # Calculates base indent for pasted code on the line where prev_open_token is located - # irb(main):001:1* if a # base_indent is 2, indent calculated from tokens is 0 - # irb(main):002:1* if b # base_indent is 6, indent calculated from tokens is 2 - # irb(main):003:0> c # base_indent is 6, indent calculated from tokens is 4 - if prev_open_token - base_indent = [0, indent_difference(lines, line_results, prev_open_token.pos[0] - 1)].max - else - base_indent = 0 - end - - if free_indent_token?(prev_open_token) - if is_newline && prev_open_token.pos[0] == line_index - # First newline inside free-indent token - base_indent + indent - else - # Accept any number of indent inside free-indent token - preserve_indent - end - elsif prev_open_token&.event == :on_embdoc_beg || next_open_token&.event == :on_embdoc_beg - if prev_open_token&.event == next_open_token&.event - # Accept any number of indent inside embdoc content - preserve_indent - else - # =begin or =end - 0 - end - elsif prev_open_token&.event == :on_heredoc_beg - tok = prev_open_token.tok - if prev_opens.size <= next_opens.size - if is_newline && lines[line_index].empty? && line_results[line_index - 1][1].last != next_open_token - # First line in heredoc - tok.match?(/^<<[-~]/) ? base_indent + indent : indent - elsif tok.match?(/^<<~/) - # Accept extra indent spaces inside `<<~` heredoc - [base_indent + indent, preserve_indent].max - else - # Accept any number of indent inside other heredoc - preserve_indent - end - else - # Heredoc close - prev_line_indent_level = calc_indent_level(prev_opens) - tok.match?(/^<<[~-]/) ? base_indent + 2 * (prev_line_indent_level - 1) : 0 - end - else - base_indent + indent - end - end - - def ltype_from_open_tokens(opens) - start_token = opens.reverse_each.find do |tok| - LTYPE_TOKENS.include?(tok.event) - end - return nil unless start_token - - case start_token&.event - when :on_tstring_beg - case start_token&.tok - when ?" then ?" - when /^%.$/ then ?" - when /^%Q.$/ then ?" - when ?' then ?' - when /^%q.$/ then ?' - end - when :on_regexp_beg then ?/ - when :on_symbeg then ?: - when :on_backtick then ?` - when :on_qwords_beg then ?] - when :on_words_beg then ?] - when :on_qsymbols_beg then ?] - when :on_symbols_beg then ?] - when :on_heredoc_beg - start_token&.tok =~ /<<[-~]?(['"`])\w+\1/ - $1 || ?" - else - nil - end - end - - def check_termination_in_prev_line(code, local_variables:) - tokens = self.class.ripper_lex_without_warning(code, local_variables: local_variables) - past_first_newline = false - index = tokens.rindex do |t| - # traverse first token before last line - if past_first_newline - if t.tok.include?("\n") - true - end - elsif t.tok.include?("\n") - past_first_newline = true - false - else - false - end - end - - if index - first_token = nil - last_line_tokens = tokens[(index + 1)..(tokens.size - 1)] - last_line_tokens.each do |t| - unless [:on_sp, :on_ignored_sp, :on_comment].include?(t.event) - first_token = t - break - end - end - - if first_token && first_token.state != Ripper::EXPR_DOT - tokens_without_last_line = tokens[0..index] - code_without_last_line = tokens_without_last_line.map(&:tok).join - opens_without_last_line = NestingParser.open_tokens(tokens_without_last_line) - if code_terminated?(code_without_last_line, tokens_without_last_line, opens_without_last_line, local_variables: local_variables) - return last_line_tokens.map(&:tok).join - end - end - end - false - end - end - # :startdoc: -end - -RubyLex = IRB::RubyLex -Object.deprecate_constant(:RubyLex) diff --git a/lib/irb/ruby_logo.aa b/lib/irb/ruby_logo.aa deleted file mode 100644 index d0143a448b..0000000000 --- a/lib/irb/ruby_logo.aa +++ /dev/null @@ -1,118 +0,0 @@ -TYPE: ASCII_LARGE - - ,,,;;;;;;;;;;;;;;;;;;;;;;,, - ,,,;;;;;;;;;,, ,;;;' ''';;, - ,,;;;''' ';;;, ,,;;'' '';, - ,;;'' ;;;;;;;;,,,,,, ';; - ,;;'' ;;;;';;;'''';;;;;;;;;,,,;; - ,,;'' ;;;; ';;, ''''';;, - ,;;' ;;;' ';;, ;; - ,;;' ,;;; '';,, ;; - ,;;' ;;; ';;, ,;; - ;;' ;;;' '';,, ;;; - ,;' ;;;; ';;, ;;' - ,;;' ,;;;;' ,,,,,,,,,,,,;;;;; - ,;' ,;;;;;;;;;;;;;;;;;;;;'''''''';;; - ;;' ,;;;;;;;;;,, ;;;; - ;;' ,;;;'' ;;, ';;,, ,;;;; - ;;' ,;;;' ;; '';;, ,;';;; - ;;' ,;;;' ;;, '';;,, ,;',;;; - ,;;; ,;;;' ;; '';;,, ,;' ;;;' - ;;;; ,,;;;' ;;, ';;;' ;;; -,;;; ,;;;;' ;; ,;;; ;;; -;;;;; ,,;;;;;' ;;, ,;';; ;;; -;;;;;, ,,;;;;;;;' ;; ,;;' ;;; ;;; -;;;;;;;,,,,,,,;;;;;;;;;;;;;;,,, ;;, ,;' ;; ;;; -;;' ;;;;;;;;;;'''' ,;';; ''';;;;,,, ;; ,;; ;; ;;; -;; ;;;'' ;; ';; ''';;;;,,,, ;;, ,;;' ;;, ;; -;; ;;;;, ;;' ';; ''';;;;,,;;;;' ';; ;; -;;;;;;';, ,;; ;; '';;;;, ;;,;; -;;; ;; ;;, ;; ;; ,;;' ';;, ;;;;; -;; ;;; ;;, ;;' ;; ,,;'' ';;, ;;;;; -;; ;; ;; ;; ;; ,;;' '';, ;;;; -;;,;; ;; ;;' ;; ,;;'' ';,, ;;;' - ;;;; ';; ,;; ;;,,;;'' ';;, ;;; - ';;; ';; ;; ,;;;;;;;;;;;;;,,,,,,,,,,,, ';;;;; - ';, ';,;;' ,,,;;'' '''''''';;;;;;;;;;;;;;;;;;; - ';;,,, ;;;; ,,,,;;;;;;,,,,,;;;;;;;;;;;;;;;;;;;'''''''''''''' - ''';;;;;;;;;;;;;;''''''''''''''' -TYPE: ASCII - ,,,;;;;''''';;;'';, - ,,;'' ';;,;;; ', - ,,'' ;;'';'''';;;;;; - ,;' ;; ',, ; - ,;' ,;' ';, ; - ;' ,;; ',,,; - ,' ,;;,,,,,,,,,,,;;;; - ;' ;;';;;; ,;; - ;' ,;' ;; '',, ,;;; - ;; ,;' ; '';, ,; ;' -;; ,;;' ;; ;; ;; -;;, ,,;;' ; ;'; ;; -;';;,,,,;;;;;;;,,, ;; ,' ; ;; -; ;;''' ,;'; ''';,,, ; ,;' ;;;; -;;;;, ; '; ''';;;' ';;; -;'; ;, ;' '; ,;' ', ;;; -;;; ; ,; '; ,,' ',, ;; -;;; '; ;' ';,,'' ';,;; - '; ';,; ,,;''''''''';;;;;;,,;;; - ';,,;;,,;;;;;;;;;;'''''''''''''' -TYPE: UNICODE_LARGE - - ⣀⣤⣴⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣤⣄⡀ - ⢀⣀⣤⣴⣾⣿⣿⣿⠿⣿⣿⣿⣿⣦⣀ ⢀⣤⣶⣿⠿⠛⠁⠈⠉⠙⠻⢿⣷⣦⡀ - ⢀⣠⣴⣾⡿⠿⠛⠉⠉ ⠈⠙⢿⣿⣷⣤⡀ ⣠⣴⣾⡿⠟⠉ ⠉⠻⣿⣦ - ⢀⣤⣶⣿⠟⠋⠁ ⢿⣿⣿⣿⣿⣿⣿⣧⣤⣤⣤⣀⣀⣀⡀ ⠘⢿⣷⡀ - ⢀⣠⣾⡿⠟⠉ ⢸⣿⣿⣿⠟⢿⣿⣯⡙⠛⠛⠛⠿⠿⠿⢿⣿⣿⣶⣶⣶⣦⣤⣬⣿⣧ - ⣠⣴⣿⠟⠋ ⢸⣿⣿⡿ ⠈⠻⣿⣶⣄ ⠉⠉⠉⠙⠛⢻⣿⡆ - ⣠⣾⡿⠛⠁ ⣼⣿⣿⠃ ⠈⠙⢿⣷⣤⡀ ⠈⣿⡇ - ⣠⣾⡿⠋ ⢠⣿⣿⡏ ⠙⠻⣿⣦⣀ ⣿⡇ - ⣠⣾⡿⠋ ⢀⣿⣿⣿ ⠈⠛⢿⣷⣄⡀ ⢠⣿⡇ - ⢀⣾⡿⠋ ⢀⣾⣿⣿⠇ ⠙⠻⣿⣦⣀ ⢸⣿⡇ - ⢀⣴⣿⠟⠁ ⢀⣾⣿⣿⡟ ⠈⠻⢿⣷⣄ ⣾⣿⠇ - ⢠⣾⡿⠃ ⣠⣿⣿⣿⣿⠃ ⣀⣀⣀⣀⣀⣀⣀⣀⣤⣤⣤⣤⣽⣿⣿⣿⣿ - ⣰⣿⠟ ⣴⣿⣿⣿⣿⣿⣶⣶⣿⣿⣿⣿⣿⠿⠿⠿⠿⠿⠿⠿⠿⠛⠛⠛⠛⠛⠛⠛⠛⣿⣿⣿ - ⣼⣿⠏ ⢠⣾⣿⣿⣿⡿⣿⣿⢿⣷⣦⣄ ⣼⣿⣿⣿ - ⣼⣿⠃ ⢀⣴⣿⣿⣿⠟⠋ ⢸⣿⡆⠈⠛⠿⣿⣦⣄⡀ ⣰⣿⣿⣿⡇ - ⢀⣾⣿⠃ ⢀⣴⣿⣿⣿⠟⠁ ⣿⣷ ⠈⠙⠻⣿⣶⣄⡀ ⣰⣿⠟⣿⣿⡇ - ⢀⣾⣿⠇ ⢀⣴⣿⣿⣿⠟⠁ ⢸⣿⡆ ⠙⠻⢿⣷⣤⣀ ⣰⣿⠏⢠⣿⣿⡇ - ⢠⣿⣿⡟ ⢀⣴⣿⣿⡿⠛⠁ ⣿⣷ ⠉⠻⢿⣷⣦⣀ ⣴⣿⠏ ⢸⣿⣿⠃ - ⣿⣿⣿⡇ ⣠⣴⣿⣿⡿⠋ ⢸⣿⡆ ⠈⠛⢿⣿⣿⠃ ⢸⣿⣿ -⢠⣿⣿⣿ ⢀⣴⣾⣿⣿⡿⠋ ⠈⣿⣧ ⢠⣾⣿⣿ ⢸⣿⣿ -⢸⣿⣿⣿⡇ ⣀⣴⣾⣿⣿⣿⡿⠋ ⢹⣿⡆ ⣴⣿⠟⢹⣿⡀ ⢸⣿⡿ -⢸⣿⡟⣿⣿⣄ ⣀⣤⣶⣿⣿⣿⣿⣿⡟⠉ ⠈⣿⣷ ⢠⣾⡿⠋ ⢸⣿⡇ ⣼⣿⡇ -⢸⣿⡇⢹⣿⣿⣷⣦⣤⣤⣤⣤⣤⣴⣶⣾⣿⣿⣿⣿⡿⠿⣿⣿⣿⣿⣷⣶⣤⣤⣀⡀ ⢹⣿⡆ ⢀⣴⣿⠟ ⣿⣧ ⣿⣿⡇ -⢸⣿⠃ ⢿⣿⣿⣿⣿⣿⣿⡿⠿⠿⠛⠛⠉⠉⠁ ⢰⣿⠟⣿⣷⡀⠉⠙⠛⠿⢿⣿⣶⣦⣤⣀⡀ ⠈⣿⣷ ⣠⣿⡿⠁ ⢿⣿ ⣿⣿⡇ -⢸⣿ ⢀⣾⣿⣿⠋⠉⠁ ⢀⣿⡿ ⠘⣿⣷⡀ ⠉⠙⠛⠿⠿⣿⣶⣦⣤⣄⣀ ⢹⣿⡄ ⣠⣾⡿⠋ ⢸⣿⡆ ⣿⣿ -⣸⣿⢀⣾⣿⣿⣿⣆ ⣸⣿⠃ ⠘⢿⣷⡀ ⠈⠉⠛⠻⠿⣿⣷⣶⣤⣌⣿⣷⣾⡿⠋ ⠘⣿⡇ ⣿⣿ -⣿⣿⣾⡿⣿⡿⠹⣿⡆ ⢠⣿⡏ ⠈⢿⣷⡀ ⠈⠉⠙⣻⣿⣿⣿⣀ ⣿⣷⢰⣿⣿ -⣿⣿⡿⢁⣿⡇ ⢻⣿⡄ ⣾⣿ ⠈⢿⣷⡀ ⢀⣤⣾⡿⠋⠈⠻⢿⣷⣄ ⢻⣿⢸⣿⡟ -⣿⣿⠁⢸⣿⡇ ⢻⣿⡄ ⢸⣿⠇ ⠈⢿⣷⡀ ⣀⣴⣿⠟⠋ ⠙⢿⣷⣤⡀ ⢸⣿⣿⣿⡇ -⣿⣿ ⢸⣿⠁ ⠈⢿⣷⡀ ⢀⣿⡟ ⠈⢿⣷⡀ ⢀⣤⣾⡿⠛⠁ ⠙⠻⣿⣦⡀ ⠈⣿⣿⣿⡇ -⢸⣿⡄⣿⣿ ⠈⣿⣷⡀ ⣼⣿⠃ ⠈⢿⣷⡀ ⢀⣠⣶⣿⠟⠋ ⠈⠻⣿⣦⣄ ⣿⣿⣿⠇ -⠈⣿⣷⣿⡿ ⠘⣿⣧ ⢠⣿⡏ ⠈⢿⣷⣄⣤⣶⣿⠟⠋ ⠈⠛⢿⣷⣄ ⢸⣿⣿ - ⠘⣿⣿⡇ ⠘⣿⣧ ⣾⣿ ⢀⣠⣼⣿⣿⣿⣿⣿⣷⣶⣶⣶⣶⣶⣶⣤⣤⣤⣤⣤⣤⣀⣀⣀⣀⣀⣀⡀ ⠙⢿⣷⣼⣿⣿ - ⠈⠻⣿⣦⡀ ⠹⣿⣆⢸⣿⠇ ⣀⣠⣴⣾⡿⠟⠋⠁ ⠉⠉⠉⠉⠉⠉⠛⠛⣛⣛⣛⣻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣿⣿⣿⡿ - ⠈⠻⢿⣷⣦⣄⣀⡀ ⢹⣿⣿⡟ ⢀⣀⣀⣤⣤⣶⣾⣿⣿⣿⣯⣥⣤⣤⣤⣤⣶⣶⣶⣶⣶⣶⣶⣾⣿⣿⣿⣿⠿⠿⠿⠿⠿⠿⠿⠟⠛⠛⠛⠛⠛⠛⠛⠉⠉⠉⠉⠉⠉ - ⠉⠙⠛⠿⠿⠿⣿⣿⣿⣿⠿⠿⠿⠿⠿⠿⠿⠛⠛⠛⠛⠛⠛⠛⠋⠉⠉⠉⠉⠉⠉⠉ -TYPE: UNICODE - ⣀⣤⣴⣾⣿⣿⣿⡛⠛⠛⠛⠛⣻⣿⠿⠛⠛⠶⣤⡀ - ⣀⣴⠾⠛⠉⠁ ⠙⣿⣶⣤⣶⣟⣉ ⠈⠻⣦ - ⣀⣴⠟⠋ ⢸⣿⠟⠻⣯⡙⠛⠛⠛⠶⠶⠶⢶⣽⣇ - ⣠⡾⠋⠁ ⣾⡿ ⠈⠛⢦⣄ ⣿ - ⣠⡾⠋ ⣰⣿⠃ ⠙⠷⣤⡀ ⣿ - ⢀⡾⠋ ⣰⣿⡏ ⠈⠻⣦⣄⢠⣿ - ⣰⠟⠁ ⣴⣿⣿⣁⣀⣠⣤⣤⣤⣤⣤⣤⣤⣴⠶⠿⣿⡏ - ⣼⠏ ⢀⣾⣿⠟⣿⠿⣯⣍⠁ ⣰⣿⡇ - ⢀⣼⠋ ⢀⣴⣿⠟⠁ ⢸⡇ ⠙⠻⢦⣄⡀ ⢠⡿⣿⡇ -⢀⣾⡏ ⢀⣴⣿⠟⠁ ⣿ ⠉⠻⢶⣄⡀⣰⡟ ⣿⠃ -⣾⣿⠁ ⣠⣶⡿⠋⠁ ⢹⡇ ⠈⣿⡏ ⢸⣿ -⣿⣿⡆ ⢀⣠⣴⣿⡿⠋ ⠈⣿ ⢀⡾⠋⣿ ⢸⣿ -⣿⠸⣿⣶⣤⣤⣤⣤⣶⣾⠿⠿⣿⣿⠶⣤⣤⣀⡀ ⢹⡇ ⣴⠟⠁ ⣿⡀⢸⣿ -⣿⢀⣿⣟⠛⠋⠉⠁ ⢰⡟⠹⣧ ⠈⠉⠛⠻⠶⢦⣤⣀⡀ ⠈⣿ ⣠⡾⠃ ⢸⡇⢸⡇ -⣿⣾⣿⢿⡄ ⣿⠁ ⠘⣧ ⠉⠙⠛⠷⣿⣿⡋ ⠸⣇⣸⡇ -⣿⠃⣿⠈⢿⡄ ⣸⠇ ⠘⣧ ⢀⣤⠾⠋⠈⠻⣦⡀ ⣿⣿⡇ -⣿⢸⡏ ⠈⣷⡀ ⢠⡿ ⠘⣧⡀ ⣠⡴⠟⠁ ⠈⠻⣦⣀ ⢿⣿⠁ -⢻⣾⡇ ⠘⣷ ⣼⠃ ⠘⣷⣠⣴⠟⠋ ⠙⢷⣄⢸⣿ - ⠻⣧⡀ ⠘⣧⣰⡏ ⢀⣠⣤⠶⠛⠉⠛⠛⠛⠛⠛⠛⠻⢶⣶⣶⣶⣶⣶⣤⣤⣽⣿⣿ - ⠈⠛⠷⢦⣤⣽⣿⣥⣤⣶⣶⡿⠿⠿⠶⠶⠶⠶⠾⠛⠛⠛⠛⠛⠛⠛⠋⠉⠉⠉⠉⠉⠉⠁ diff --git a/lib/irb/source_finder.rb b/lib/irb/source_finder.rb deleted file mode 100644 index 6e1e58069f..0000000000 --- a/lib/irb/source_finder.rb +++ /dev/null @@ -1,138 +0,0 @@ -# frozen_string_literal: true - -require_relative "ruby-lex" - -module IRB - class SourceFinder - 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) - @irb_context = irb_context - end - - def find_source(signature, super_level = 0) - case 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_receiver_or_owner(Regexp.last_match[:owner]) - method = Regexp.last_match[:method] - return unless owner.respond_to?(:instance_method) - 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_receiver_or_owner(Regexp.last_match[:receiver] || 'self') - method = Regexp.last_match[:method] - return unless receiver.respond_to?(method, true) - method = method_target(receiver, super_level, method, "receiver") - file, line = method&.source_location - end - return unless file && line - - if File.exist?(file) - Source.new(file, line) - elsif method - # Method defined with eval, probably in IRB session - source = RubyVM::InstructionSequence.of(method)&.script_lines&.join rescue nil - Source.new(file, line, source) - end - rescue EvaluationError - nil - end - - private - - def method_target(owner_receiver, super_level, method, type) - case type - when "owner" - target_method = owner_receiver.instance_method(method) - when "receiver" - target_method = owner_receiver.method(method) - end - super_level.times do |s| - target_method = target_method.super_method if target_method - end - target_method - rescue NameError - nil - end - - def eval_receiver_or_owner(code) - @irb_context.workspace.binding.eval(code) - rescue Exception - 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 deleted file mode 100644 index 6a959995d1..0000000000 --- a/lib/irb/statement.rb +++ /dev/null @@ -1,101 +0,0 @@ -# frozen_string_literal: true - -module IRB - class Statement - attr_reader :code - - def is_assignment? - raise NotImplementedError - end - - def suppresses_echo? - raise NotImplementedError - end - - def should_be_handled_by_debugger? - raise NotImplementedError - end - - 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 - def initialize(code, is_assignment) - @code = code - @is_assignment = is_assignment - end - - def suppresses_echo? - @code.match?(/;\s*\z/) - end - - def should_be_handled_by_debugger? - true - end - - def is_assignment? - @is_assignment - end - end - - class IncorrectAlias < Statement - attr_reader :message - - def initialize(message) - @code = "" - @message = message - end - - def should_be_handled_by_debugger? - false - end - - def is_assignment? - false - end - - def suppresses_echo? - true - end - end - - class Command < Statement - 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? - false - end - - def suppresses_echo? - true - end - - def should_be_handled_by_debugger? - require_relative 'command/debug' - IRB::Command::DebugCommand > @command_class - end - end - end -end diff --git a/lib/irb/version.rb b/lib/irb/version.rb deleted file mode 100644 index b195956fe0..0000000000 --- a/lib/irb/version.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true -# -# irb/version.rb - irb version definition file -# by Keiju ISHITSUKA([email protected]) -# - -module IRB # :nodoc: - VERSION = "1.15.1" - @RELEASE_VERSION = VERSION - @LAST_UPDATE_DATE = "2025-01-22" -end diff --git a/lib/irb/workspace.rb b/lib/irb/workspace.rb deleted file mode 100644 index ced9d78663..0000000000 --- a/lib/irb/workspace.rb +++ /dev/null @@ -1,171 +0,0 @@ -# frozen_string_literal: true -# -# irb/workspace-binding.rb - -# by Keiju ISHITSUKA([email protected]) -# - -require_relative "helper_method" - -IRB::TOPLEVEL_BINDING = binding -module IRB # :nodoc: - class WorkSpace - # Creates a new workspace. - # - # set self to main if specified, otherwise - # inherit main from TOPLEVEL_BINDING. - def initialize(*main) - if Binding === main[0] - @binding = main.shift - elsif IRB.conf[:SINGLE_IRB] - @binding = TOPLEVEL_BINDING - else - case IRB.conf[:CONTEXT_MODE] - when 0 # binding in proc on TOPLEVEL_BINDING - @binding = eval("proc{binding}.call", - TOPLEVEL_BINDING, - __FILE__, - __LINE__) - when 1 # binding in loaded file - require "tempfile" - f = Tempfile.open("irb-binding") - f.print <<EOF - $binding = binding -EOF - f.close - load f.path - @binding = $binding - - when 2 # binding in loaded file(thread use) - unless defined? BINDING_QUEUE - IRB.const_set(:BINDING_QUEUE, Thread::SizedQueue.new(1)) - Thread.abort_on_exception = true - Thread.start do - eval "require \"irb/ws-for-case-2\"", TOPLEVEL_BINDING, __FILE__, __LINE__ - end - Thread.pass - end - @binding = BINDING_QUEUE.pop - - when 3 # binding in function on TOPLEVEL_BINDING - @binding = eval("self.class.remove_method(:irb_binding) if defined?(irb_binding); private; def irb_binding; binding; end; irb_binding", - TOPLEVEL_BINDING, - __FILE__, - __LINE__ - 3) - when 4 # binding is a copy of TOPLEVEL_BINDING (default) - # Note that this will typically be IRB::TOPLEVEL_BINDING - # This is to avoid RubyGems' local variables (see issue #17623) - @binding = TOPLEVEL_BINDING.dup - end - end - - if main.empty? - @main = eval("self", @binding) - else - @main = main[0] - end - IRB.conf[:__MAIN__] = @main - - unless main.empty? - case @main - when Module - @binding = eval("::IRB.conf[:__MAIN__].module_eval('::Kernel.binding', __FILE__, __LINE__)", @binding, __FILE__, __LINE__) - else - begin - @binding = eval("::IRB.conf[:__MAIN__].instance_eval('::Kernel.binding', __FILE__, __LINE__)", @binding, __FILE__, __LINE__) - rescue TypeError - fail CantChangeBinding, @main.inspect - end - end - end - - @binding.local_variable_set(:_, nil) - end - - # The Binding of this workspace - attr_reader :binding - # The top-level workspace of this context, also available as - # <code>IRB.conf[:__MAIN__]</code> - attr_reader :main - - def load_helper_methods_to_main - # Do not load helper methods to frozen objects and BasicObject - return unless Object === @main && [email protected]? - - 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. - def evaluate(statements, file = __FILE__, line = __LINE__) - eval(statements, @binding, file, line) - end - - def local_variable_set(name, value) - @binding.local_variable_set(name, value) - end - - def local_variable_get(name) - @binding.local_variable_get(name) - end - - # error message manipulator - # WARN: Rails patches this method to filter its own backtrace. Be cautious when changing it. - # See: https://github.com/rails/rails/blob/main/railties/lib/rails/commands/console/console_command.rb#L8:~:text=def,filter_backtrace - def filter_backtrace(bt) - return nil if bt =~ /\/irb\/.*\.rb/ - return nil if bt =~ /\/irb\.rb/ - return nil if bt =~ /tool\/lib\/.*\.rb|runner\.rb/ # for tests in Ruby repository - case IRB.conf[:CONTEXT_MODE] - when 1 - return nil if bt =~ %r!/tmp/irb-binding! - when 3 - bt = bt.sub(/:\s*in `irb_binding'/, '') - end - bt - end - - def code_around_binding - file, pos = @binding.source_location - - if defined?(::SCRIPT_LINES__[file]) && lines = ::SCRIPT_LINES__[file] - code = ::SCRIPT_LINES__[file].join('') - else - begin - code = File.read(file) - rescue SystemCallError - return - end - end - - lines = Color.colorize_code(code).lines - pos -= 1 - - start_pos = [pos - 5, 0].max - end_pos = [pos + 5, lines.size - 1].min - - line_number_fmt = Color.colorize("%#{end_pos.to_s.length}d", [:BLUE, :BOLD]) - fmt = " %2s #{line_number_fmt}: %s" - - body = (start_pos..end_pos).map do |current_pos| - sprintf(fmt, pos == current_pos ? '=>' : '', current_pos + 1, lines[current_pos]) - end.join("") - - "\nFrom: #{file} @ line #{pos + 1} :\n\n#{body}#{Color.clear}\n" - end - end - - module HelpersContainer - class << self - def 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 - end - - install_helper_methods - end -end diff --git a/lib/irb/ws-for-case-2.rb b/lib/irb/ws-for-case-2.rb deleted file mode 100644 index 03f42d73d9..0000000000 --- a/lib/irb/ws-for-case-2.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true -# -# irb/ws-for-case-2.rb - -# by Keiju ISHITSUKA([email protected]) -# - -while true - IRB::BINDING_QUEUE.push _ = binding -end diff --git a/lib/irb/xmp.rb b/lib/irb/xmp.rb deleted file mode 100644 index b1bc53283e..0000000000 --- a/lib/irb/xmp.rb +++ /dev/null @@ -1,164 +0,0 @@ -# frozen_string_literal: true -# -# xmp.rb - irb version of gotoken xmp -# by Keiju ISHITSUKA(Nippon Rational Inc.) -# - -require_relative "../irb" -require_relative "frame" - -# An example printer for irb. -# -# It's much like the standard library PrettyPrint, that shows the value of each -# expression as it runs. -# -# In order to use this library, you must first require it: -# -# require 'irb/xmp' -# -# Now, you can take advantage of the Object#xmp convenience method. -# -# xmp <<END -# foo = "bar" -# baz = 42 -# END -# #=> foo = "bar" -# #==>"bar" -# #=> baz = 42 -# #==>42 -# -# You can also create an XMP object, with an optional binding to print -# expressions in the given binding: -# -# ctx = binding -# x = XMP.new ctx -# x.puts -# #=> today = "a good day" -# #==>"a good day" -# ctx.eval 'today # is what?' -# #=> "a good day" -class XMP - - # Creates a new XMP object. - # - # The top-level binding or, optional +bind+ parameter will be used when - # creating the workspace. See WorkSpace.new for more information. - # - # This uses the +:XMP+ prompt mode. - # See {Custom Prompts}[rdoc-ref:IRB@Custom+Prompts] for more information. - def initialize(bind = nil) - IRB.init_config(nil) - - IRB.conf[:PROMPT_MODE] = :XMP - - bind = IRB::Frame.top(1) unless bind - ws = IRB::WorkSpace.new(bind) - @io = StringInputMethod.new - @irb = IRB::Irb.new(ws, @io) - @irb.context.ignore_sigint = false - - IRB.conf[:MAIN_CONTEXT] = @irb.context - end - - # Evaluates the given +exps+, for example: - # - # require 'irb/xmp' - # x = XMP.new - # - # x.puts '{:a => 1, :b => 2, :c => 3}' - # #=> {:a => 1, :b => 2, :c => 3} - # # ==>{:a=>1, :b=>2, :c=>3} - # x.puts 'foo = "bar"' - # # => foo = "bar" - # # ==>"bar" - def puts(exps) - @io.puts exps - - if @irb.context.ignore_sigint - begin - trap_proc_b = trap("SIGINT"){@irb.signal_handle} - catch(:IRB_EXIT) do - @irb.eval_input - end - ensure - trap("SIGINT", trap_proc_b) - end - else - catch(:IRB_EXIT) do - @irb.eval_input - end - end - end - - # A custom InputMethod class used by XMP for evaluating string io. - class StringInputMethod < IRB::InputMethod - # Creates a new StringInputMethod object - def initialize - super - @exps = [] - end - - # Whether there are any expressions left in this printer. - def eof? - @exps.empty? - end - - # Reads the next expression from this printer. - # - # See IO#gets for more information. - def gets - while l = @exps.shift - next if /^\s+$/ =~ l - l.concat "\n" - print @prompt, l - break - end - l - end - - # Concatenates all expressions in this printer, separated by newlines. - # - # An Encoding::CompatibilityError is raised of the given +exps+'s encoding - # doesn't match the previous expression evaluated. - def puts(exps) - if @encoding and exps.encoding != @encoding - enc = Encoding.compatible?(@exps.join("\n"), exps) - if enc.nil? - raise Encoding::CompatibilityError, "Encoding in which the passed expression is encoded is not compatible to the preceding's one" - else - @encoding = enc - end - else - @encoding = exps.encoding - end - @exps.concat exps.split(/\n/) - end - - # Returns the encoding of last expression printed by #puts. - attr_reader :encoding - end -end - -# A convenience method that's only available when the you require the IRB::XMP standard library. -# -# Creates a new XMP object, using the given expressions as the +exps+ -# parameter, and optional binding as +bind+ or uses the top-level binding. Then -# evaluates the given expressions using the +:XMP+ prompt mode. -# -# For example: -# -# require 'irb/xmp' -# ctx = binding -# xmp 'foo = "bar"', ctx -# #=> foo = "bar" -# #==>"bar" -# ctx.eval 'foo' -# #=> "bar" -# -# See XMP.new for more information. -def xmp(exps, bind = nil) - bind = IRB::Frame.top(1) unless bind - xmp = XMP.new(bind) - xmp.puts exps - xmp -end diff --git a/lib/readline.gemspec b/lib/readline.gemspec deleted file mode 100644 index 9221c29263..0000000000 --- a/lib/readline.gemspec +++ /dev/null @@ -1,33 +0,0 @@ -Gem::Specification.new do |spec| - spec.name = 'readline' - spec.version = '0.0.4' - spec.authors = ['aycabta'] - spec.email = ['[email protected]'] - - spec.summary = %q{Loader for "readline".} - spec.description = <<~EOD - This is just a loader for "readline". If Ruby has the "readline-ext" gem - that is a native extension, this gem will load it. If Ruby does not have - the "readline-ext" gem this gem will load "reline", a library that is - compatible with the "readline-ext" gem and implemented in pure Ruby. - EOD - spec.homepage = 'https://github.com/ruby/readline' - spec.license = 'Ruby' - - spec.files = Dir['BSDL', 'COPYING', 'README.md', 'lib/readline.rb'] - spec.require_paths = ['lib'] - - spec.post_install_message = <<~EOM - +---------------------------------------------------------------------------+ - | This is just a loader for "readline". If Ruby has the "readline-ext" gem | - | that is a native extension, this gem will load it. If Ruby does not have | - | the "readline-ext" gem this gem will load "reline", a library that is | - | compatible with the "readline-ext" gem and implemented in pure Ruby. | - | | - | If you intend to use GNU Readline by `require 'readline'`, please install | - | the "readline-ext" gem. | - +---------------------------------------------------------------------------+ - EOM - - spec.add_runtime_dependency 'reline' -end diff --git a/lib/readline.rb b/lib/readline.rb deleted file mode 100644 index d1c9d3a955..0000000000 --- a/lib/readline.rb +++ /dev/null @@ -1,7 +0,0 @@ -begin - require "readline.#{RbConfig::CONFIG["DLEXT"]}" -rescue LoadError - require 'reline' unless defined? Reline - Object.send(:remove_const, :Readline) if Object.const_defined?(:Readline) - Readline = Reline -end diff --git a/lib/reline.rb b/lib/reline.rb deleted file mode 100644 index 5d1fcbf8e8..0000000000 --- a/lib/reline.rb +++ /dev/null @@ -1,518 +0,0 @@ -require 'io/console' -require 'forwardable' -require 'reline/version' -require 'reline/config' -require 'reline/key_actor' -require 'reline/key_stroke' -require 'reline/line_editor' -require 'reline/history' -require 'reline/io' -require 'reline/face' -require 'rbconfig' - -module Reline - # NOTE: For making compatible with the rb-readline gem - FILENAME_COMPLETION_PROC = nil - USERNAME_COMPLETION_PROC = nil - - class ConfigEncodingConversionError < StandardError; end - - # EOF key: { char: nil, method_symbol: nil } - # Other key: { char: String, method_symbol: Symbol } - Key = Struct.new(:char, :method_symbol, :unused_boolean) do - # For dialog_proc `key.match?(dialog.name)` - def match?(sym) - method_symbol && method_symbol == sym - end - end - CursorPos = Struct.new(:x, :y) - DialogRenderInfo = Struct.new( - :pos, - :contents, - :face, - :bg_color, # For the time being, this line should stay here for the compatibility with IRB. - :width, - :height, - :scrollbar, - keyword_init: true - ) - - class Core - ATTR_READER_NAMES = %i( - completion_append_character - basic_word_break_characters - completer_word_break_characters - basic_quote_characters - completer_quote_characters - filename_quote_characters - special_prefixes - completion_proc - output_modifier_proc - prompt_proc - auto_indent_proc - pre_input_hook - dig_perfect_match_proc - ).each(&method(:attr_reader)) - - attr_accessor :config - attr_accessor :key_stroke - attr_accessor :line_editor - attr_accessor :last_incremental_search - attr_reader :output - - extend Forwardable - def_delegators :config, - :autocompletion, - :autocompletion= - - def initialize - self.output = STDOUT - @mutex = Mutex.new - @dialog_proc_list = {} - yield self - @completion_quote_character = nil - end - - def io_gate - Reline::IOGate - end - - def encoding - io_gate.encoding - end - - def completion_append_character=(val) - if val.nil? - @completion_append_character = nil - elsif val.size == 1 - @completion_append_character = val.encode(encoding) - elsif val.size > 1 - @completion_append_character = val[0].encode(encoding) - else - @completion_append_character = nil - end - end - - def basic_word_break_characters=(v) - @basic_word_break_characters = v.encode(encoding) - end - - def completer_word_break_characters=(v) - @completer_word_break_characters = v.encode(encoding) - end - - def basic_quote_characters=(v) - @basic_quote_characters = v.encode(encoding) - end - - def completer_quote_characters=(v) - @completer_quote_characters = v.encode(encoding) - end - - def filename_quote_characters=(v) - @filename_quote_characters = v.encode(encoding) - end - - def special_prefixes=(v) - @special_prefixes = v.encode(encoding) - end - - def completion_case_fold=(v) - @config.completion_ignore_case = v - end - - def completion_case_fold - @config.completion_ignore_case - end - - def completion_quote_character - @completion_quote_character - end - - def completion_proc=(p) - raise ArgumentError unless p.respond_to?(:call) or p.nil? - @completion_proc = p - end - - def output_modifier_proc=(p) - raise ArgumentError unless p.respond_to?(:call) or p.nil? - @output_modifier_proc = p - end - - def prompt_proc=(p) - raise ArgumentError unless p.respond_to?(:call) or p.nil? - @prompt_proc = p - end - - def auto_indent_proc=(p) - raise ArgumentError unless p.respond_to?(:call) or p.nil? - @auto_indent_proc = p - end - - def pre_input_hook=(p) - @pre_input_hook = p - end - - def dig_perfect_match_proc=(p) - raise ArgumentError unless p.respond_to?(:call) or p.nil? - @dig_perfect_match_proc = p - end - - DialogProc = Struct.new(:dialog_proc, :context) - def add_dialog_proc(name_sym, p, context = nil) - raise ArgumentError unless name_sym.instance_of?(Symbol) - if p.nil? - @dialog_proc_list.delete(name_sym) - else - raise ArgumentError unless p.respond_to?(:call) - @dialog_proc_list[name_sym] = DialogProc.new(p, context) - end - end - - def dialog_proc(name_sym) - @dialog_proc_list[name_sym] - end - - def input=(val) - raise TypeError unless val.respond_to?(:getc) or val.nil? - if val.respond_to?(:getc) && io_gate.respond_to?(:input=) - io_gate.input = val - end - end - - def output=(val) - raise TypeError unless val.respond_to?(:write) or val.nil? - @output = val - io_gate.output = val - end - - def vi_editing_mode - config.editing_mode = :vi_insert - nil - end - - def emacs_editing_mode - config.editing_mode = :emacs - nil - end - - def vi_editing_mode? - config.editing_mode_is?(:vi_insert, :vi_command) - end - - def emacs_editing_mode? - config.editing_mode_is?(:emacs) - end - - def get_screen_size - io_gate.get_screen_size - end - - Reline::DEFAULT_DIALOG_PROC_AUTOCOMPLETE = ->() { - # autocomplete - 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) - 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) - if context and context.is_a?(Array) - context.clear - context.push(cursor_pos_to_render, result, pointer, dialog) - end - dialog.pointer = pointer - DialogRenderInfo.new( - pos: cursor_pos_to_render, - contents: result, - scrollbar: true, - height: [15, preferred_dialog_height].min, - face: :completion_dialog - ) - } - Reline::DEFAULT_DIALOG_CONTEXT = Array.new - - def readmultiline(prompt = '', add_hist = false, &confirm_multiline_termination) - @mutex.synchronize do - unless confirm_multiline_termination - raise ArgumentError.new('#readmultiline needs block to confirm multiline termination') - end - - 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' - if add_hist and whole_buffer and whole_buffer.chomp("\n").size > 0 - Reline::HISTORY << whole_buffer - end - - 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) - @mutex.synchronize do - 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_editor.reset_line if line_editor.line.nil? - line - end - end - - private def inner_readline(prompt, add_hist, multiline, &confirm_multiline_termination) - if ENV['RELINE_STDERR_TTY'] - if io_gate.win? - $stderr = File.open(ENV['RELINE_STDERR_TTY'], 'a') - else - $stderr.reopen(ENV['RELINE_STDERR_TTY'], 'w') - end - $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 - key_stroke.encoding = encoding - line_editor.reset(prompt) - if multiline - line_editor.multiline_on - if block_given? - line_editor.confirm_multiline_termination_proc = confirm_multiline_termination - end - else - line_editor.multiline_off - end - line_editor.completion_proc = completion_proc - line_editor.completion_append_character = completion_append_character - line_editor.output_modifier_proc = output_modifier_proc - line_editor.prompt_proc = prompt_proc - line_editor.auto_indent_proc = auto_indent_proc - line_editor.dig_perfect_match_proc = dig_perfect_match_proc - - # Readline calls pre_input_hook just after printing the first prompt. - line_editor.print_nomultiline_prompt - pre_input_hook&.call - - unless Reline::IOGate.dumb? - @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.update_dialogs - line_editor.rerender - - begin - line_editor.set_signal_handlers - loop do - read_io(config.keyseq_timeout) { |inputs| - inputs.each do |key| - case key.method_symbol - when :bracketed_paste_start - # io_gate is Reline::ANSI because the key :bracketed_paste_start is only assigned in Reline::ANSI - key = Reline::Key.new(io_gate.read_bracketed_paste, :insert_multiline_text) - when :quoted_insert, :ed_quoted_insert - key = Reline::Key.new(io_gate.read_single_char(config.keyseq_timeout), :insert_raw_char) - end - line_editor.set_pasting_state(io_gate.in_pasting?) - line_editor.update(key) - end - } - if line_editor.finished? - line_editor.render_finished - break - else - line_editor.rerender - end - end - io_gate.move_cursor_column(0) - rescue Errno::EIO - # Maybe the I/O has been closed. - ensure - line_editor.finalize - io_gate.deprep(otio) - end - end - - # GNU Readline watis for "keyseq-timeout" milliseconds when the input is - # ambiguous whether it is matching or matched. - # If the next character does not arrive within the specified timeout, input - # is considered as matched. - # `ESC` is ambiguous because it can be a standalone ESC (matched) or part of - # `ESC char` or part of CSI sequence (matching). - private def read_io(keyseq_timeout, &block) - buffer = [] - status = KeyStroke::MATCHING - loop do - timeout = status == KeyStroke::MATCHING_MATCHED ? keyseq_timeout.fdiv(1000) : Float::INFINITY - c = io_gate.getc(timeout) - if c.nil? || c == -1 - if status == KeyStroke::MATCHING_MATCHED - status = KeyStroke::MATCHED - elsif buffer.empty? - # io_gate is closed and reached EOF - block.call([Key.new(nil, nil, false)]) - return - else - status = KeyStroke::UNMATCHED - end - else - buffer << c - status = key_stroke.match_status(buffer) - end - - if status == KeyStroke::MATCHED || status == KeyStroke::UNMATCHED - expanded, rest_bytes = key_stroke.expand(buffer) - rest_bytes.reverse_each { |c| io_gate.ungetc(c) } - block.call(expanded) - return - end - end - end - - def ambiguous_width - may_req_ambiguous_char_width unless defined? @ambiguous_width - @ambiguous_width - end - - private def may_req_ambiguous_char_width - @ambiguous_width = 1 if io_gate.dumb? || !STDIN.tty? || !STDOUT.tty? - return if defined? @ambiguous_width - io_gate.move_cursor_column(0) - begin - output.write "\u{25bd}" - rescue Encoding::UndefinedConversionError - # LANG=C - @ambiguous_width = 1 - else - @ambiguous_width = io_gate.cursor_pos.x == 2 ? 2 : 1 - end - io_gate.move_cursor_column(0) - io_gate.erase_after_cursor - end - end - - extend Forwardable - extend SingleForwardable - - #-------------------------------------------------------- - # Documented API - #-------------------------------------------------------- - - (Core::ATTR_READER_NAMES).each { |name| - def_single_delegators :core, :"#{name}", :"#{name}=" - } - def_single_delegators :core, :input=, :output= - def_single_delegators :core, :vi_editing_mode, :emacs_editing_mode - def_single_delegators :core, :readline - def_single_delegators :core, :completion_case_fold, :completion_case_fold= - def_single_delegators :core, :completion_quote_character - def_instance_delegators self, :readline - private :readline - - - #-------------------------------------------------------- - # Undocumented API - #-------------------------------------------------------- - - # Testable in original - def_single_delegators :core, :get_screen_size - def_single_delegators :line_editor, :eof? - def_instance_delegators self, :eof? - def_single_delegators :line_editor, :delete_text - def_single_delegator :line_editor, :line, :line_buffer - def_single_delegator :line_editor, :byte_pointer, :point - def_single_delegator :line_editor, :byte_pointer=, :point= - - def self.insert_text(text) - line_editor.insert_multiline_text(text) - self - end - - # Untestable in original - def_single_delegator :line_editor, :rerender, :redisplay - def_single_delegators :core, :vi_editing_mode?, :emacs_editing_mode? - def_single_delegators :core, :ambiguous_width - def_single_delegators :core, :last_incremental_search - def_single_delegators :core, :last_incremental_search= - def_single_delegators :core, :add_dialog_proc - def_single_delegators :core, :dialog_proc - def_single_delegators :core, :autocompletion, :autocompletion= - - def_single_delegators :core, :readmultiline - def_instance_delegators self, :readmultiline - private :readmultiline - - def self.encoding_system_needs - self.core.encoding - end - - def self.core - @core ||= Core.new { |core| - core.config = Reline::Config.new - core.key_stroke = Reline::KeyStroke.new(core.config, core.encoding) - core.line_editor = Reline::LineEditor.new(core.config) - - core.basic_word_break_characters = " \t\n`><=;|&{(" - core.completer_word_break_characters = " \t\n`><=;|&{(" - core.basic_quote_characters = '"\'' - core.completer_quote_characters = '"\'' - core.filename_quote_characters = "" - core.special_prefixes = "" - core.add_dialog_proc(:autocomplete, Reline::DEFAULT_DIALOG_PROC_AUTOCOMPLETE, Reline::DEFAULT_DIALOG_CONTEXT) - } - end - - def self.ungetc(c) - core.io_gate.ungetc(c) - end - - def self.line_editor - core.line_editor - end -end - - -Reline::IOGate = Reline::IO.decide_io_gate - -# Deprecated -Reline::GeneralIO = Reline::Dumb.new - -Reline::Face.load_initial_configs - -Reline::HISTORY = Reline::History.new(Reline.core.config) diff --git a/lib/reline/config.rb b/lib/reline/config.rb deleted file mode 100644 index e0fc37fc68..0000000000 --- a/lib/reline/config.rb +++ /dev/null @@ -1,373 +0,0 @@ -class Reline::Config - attr_reader :test_mode - - KEYSEQ_PATTERN = /\\(?:C|Control)-[A-Za-z_]|\\(?:M|Meta)-[0-9A-Za-z_]|\\(?:C|Control)-\\(?:M|Meta)-[A-Za-z_]|\\(?:M|Meta)-\\(?:C|Control)-[A-Za-z_]|\\e|\\[\\\"\'abdfnrtv]|\\\d{1,3}|\\x\h{1,2}|./ - - class InvalidInputrc < RuntimeError - attr_accessor :file, :lineno - end - - VARIABLE_NAMES = %w{ - completion-ignore-case - convert-meta - disable-completion - history-size - keyseq-timeout - show-all-if-ambiguous - show-mode-in-prompt - vi-cmd-mode-string - vi-ins-mode-string - emacs-mode-string - enable-bracketed-paste - isearch-terminators - } - VARIABLE_NAME_SYMBOLS = VARIABLE_NAMES.map { |v| :"#{v.tr(?-, ?_)}" } - VARIABLE_NAME_SYMBOLS.each do |v| - attr_accessor v - end - - attr_accessor :autocompletion - - def initialize - reset_variables - end - - def reset - if editing_mode_is?(:vi_command) - @editing_mode_label = :vi_insert - end - @oneshot_key_bindings.clear - end - - def reset_variables - @additional_key_bindings = { # from inputrc - emacs: Reline::KeyActor::Base.new, - vi_insert: Reline::KeyActor::Base.new, - vi_command: Reline::KeyActor::Base.new - } - @oneshot_key_bindings = Reline::KeyActor::Base.new - @editing_mode_label = :emacs - @keymap_label = :emacs - @keymap_prefix = [] - @default_key_bindings = { - emacs: Reline::KeyActor::Base.new(Reline::KeyActor::EMACS_MAPPING), - vi_insert: Reline::KeyActor::Base.new(Reline::KeyActor::VI_INSERT_MAPPING), - vi_command: Reline::KeyActor::Base.new(Reline::KeyActor::VI_COMMAND_MAPPING) - } - @vi_cmd_mode_string = '(cmd)' - @vi_ins_mode_string = '(ins)' - @emacs_mode_string = '@' - # https://tiswww.case.edu/php/chet/readline/readline.html#IDX25 - @history_size = -1 # unlimited - @keyseq_timeout = 500 - @test_mode = false - @autocompletion = false - @convert_meta = seven_bit_encoding?(Reline::IOGate.encoding) - @loaded = false - @enable_bracketed_paste = true - @show_mode_in_prompt = false - @default_inputrc_path = nil - end - - def editing_mode - @default_key_bindings[@editing_mode_label] - end - - def editing_mode=(val) - @editing_mode_label = val - end - - def editing_mode_is?(*val) - val.any?(@editing_mode_label) - end - - def keymap - @default_key_bindings[@keymap_label] - end - - def loaded? - @loaded - end - - def inputrc_path - case ENV['INPUTRC'] - when nil, '' - else - return File.expand_path(ENV['INPUTRC']) - end - - # In the XDG Specification, if ~/.config/readline/inputrc exists, then - # ~/.inputrc should not be read, but for compatibility with GNU Readline, - # if ~/.inputrc exists, then it is given priority. - home_rc_path = File.expand_path('~/.inputrc') - return home_rc_path if File.exist?(home_rc_path) - - case path = ENV['XDG_CONFIG_HOME'] - when nil, '' - else - path = File.join(path, 'readline/inputrc') - return path if File.exist?(path) and path == File.expand_path(path) - end - - path = File.expand_path('~/.config/readline/inputrc') - return path if File.exist?(path) - - return home_rc_path - end - - private def default_inputrc_path - @default_inputrc_path ||= inputrc_path - end - - def read(file = nil) - @loaded = true - file ||= default_inputrc_path - begin - if file.respond_to?(:readlines) - lines = file.readlines - else - lines = File.readlines(file) - end - rescue Errno::ENOENT - return nil - end - - read_lines(lines, file) - self - rescue InvalidInputrc => e - warn e.message - nil - end - - def key_bindings - # The key bindings for each editing mode will be overwritten by the user-defined ones. - Reline::KeyActor::Composite.new([@oneshot_key_bindings, @additional_key_bindings[@editing_mode_label], @default_key_bindings[@editing_mode_label]]) - end - - def add_oneshot_key_binding(keystroke, target) - # IRB sets invalid keystroke [Reline::Key]. We should ignore it. - return unless keystroke.all? { |c| c.is_a?(Integer) } - - @oneshot_key_bindings.add(keystroke, target) - end - - def reset_oneshot_key_bindings - @oneshot_key_bindings.clear - end - - def add_default_key_binding_by_keymap(keymap, keystroke, target) - @default_key_bindings[keymap].add(keystroke, target) - end - - def add_default_key_binding(keystroke, target) - add_default_key_binding_by_keymap(@keymap_label, keystroke, target) - end - - def read_lines(lines, file = nil) - if not lines.empty? and lines.first.encoding != Reline.encoding_system_needs - begin - lines = lines.map do |l| - l.encode(Reline.encoding_system_needs) - rescue Encoding::UndefinedConversionError - mes = "The inputrc encoded in #{lines.first.encoding.name} can't be converted to the locale #{Reline.encoding_system_needs.name}." - raise Reline::ConfigEncodingConversionError.new(mes) - end - end - end - if_stack = [] - - lines.each_with_index do |line, no| - next if line.match(/\A\s*#/) - - no += 1 - - line = line.chomp.lstrip - if line.start_with?('$') - handle_directive(line[1..-1], file, no, if_stack) - next - end - - next if if_stack.any? { |_no, skip| skip } - - case line - when /^set +([^ ]+) +(.+)/i - # value ignores everything after a space, raw_value does not. - var, value, raw_value = $1.downcase, $2.partition(' ').first, $2 - bind_variable(var, value, raw_value) - when /^\s*(?:M|Meta)-([a-zA-Z_])\s*:\s*(.*)\s*$/o - bind_key("\"\\M-#$1\"", $2) - when /^\s*(?:C|Control)-([a-zA-Z_])\s*:\s*(.*)\s*$/o - bind_key("\"\\C-#$1\"", $2) - when /^\s*(?:(?:C|Control)-(?:M|Meta)|(?:M|Meta)-(?:C|Control))-([a-zA-Z_])\s*:\s*(.*)\s*$/o - bind_key("\"\\M-\\C-#$1\"", $2) - when /^\s*("#{KEYSEQ_PATTERN}+")\s*:\s*(.*)\s*$/o - bind_key($1, $2) - end - end - unless if_stack.empty? - raise InvalidInputrc, "#{file}:#{if_stack.last[0]}: unclosed if" - end - end - - def handle_directive(directive, file, no, if_stack) - directive, args = directive.split(' ') - case directive - when 'if' - condition = false - case args - 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 << [no, !condition] - when 'else' - if if_stack.empty? - raise InvalidInputrc, "#{file}:#{no}: unmatched else" - end - if_stack.last[1] = !if_stack.last[1] - when 'endif' - if if_stack.empty? - raise InvalidInputrc, "#{file}:#{no}: unmatched endif" - end - if_stack.pop - when 'include' - read(File.expand_path(args)) - end - end - - def bind_variable(name, value, raw_value) - case name - when 'history-size' - begin - @history_size = Integer(value) - rescue ArgumentError - @history_size = 500 - end - when 'isearch-terminators' - @isearch_terminators = retrieve_string(raw_value) - when 'editing-mode' - case value - when 'emacs' - @editing_mode_label = :emacs - @keymap_label = :emacs - @keymap_prefix = [] - when 'vi' - @editing_mode_label = :vi_insert - @keymap_label = :vi_insert - @keymap_prefix = [] - end - when 'keymap' - case value - when 'emacs', 'emacs-standard' - @keymap_label = :emacs - @keymap_prefix = [] - when 'emacs-ctlx' - @keymap_label = :emacs - @keymap_prefix = [?\C-x.ord] - when 'emacs-meta' - @keymap_label = :emacs - @keymap_prefix = [?\e.ord] - when 'vi', 'vi-move', 'vi-command' - @keymap_label = :vi_command - @keymap_prefix = [] - when 'vi-insert' - @keymap_label = :vi_insert - @keymap_prefix = [] - end - when 'keyseq-timeout' - @keyseq_timeout = value.to_i - when 'show-mode-in-prompt' - case value - when 'off' - @show_mode_in_prompt = false - when 'on' - @show_mode_in_prompt = true - else - @show_mode_in_prompt = false - end - when 'vi-cmd-mode-string' - @vi_cmd_mode_string = retrieve_string(raw_value) - when 'vi-ins-mode-string' - @vi_ins_mode_string = retrieve_string(raw_value) - when 'emacs-mode-string' - @emacs_mode_string = retrieve_string(raw_value) - when *VARIABLE_NAMES then - variable_name = :"@#{name.tr(?-, ?_)}" - instance_variable_set(variable_name, value.nil? || value == '1' || value == 'on') - end - end - - def retrieve_string(str) - str = $1 if str =~ /\A"(.*)"\z/ - parse_keyseq(str).map { |c| c.chr(Reline.encoding_system_needs) }.join - end - - def bind_key(key, value) - keystroke, func = parse_key_binding(key, value) - @additional_key_bindings[@keymap_label].add(@keymap_prefix + keystroke, func) if keystroke - end - - def parse_key_binding(key, func_name) - if key =~ /\A"(.*)"\z/ - keyseq = parse_keyseq($1) - else - keyseq = nil - end - if func_name =~ /"(.*)"/ - func = parse_keyseq($1) - else - func = func_name.split.first.tr(?-, ?_).to_sym # It must be macro. - end - [keyseq, func] - end - - def key_notation_to_code(notation) - case notation - when /(?:\\(?:C|Control)-\\(?:M|Meta)|\\(?:M|Meta)-\\(?:C|Control))-([A-Za-z_])/ - [?\e.ord, $1.ord % 32] - when /\\(?:C|Control)-([A-Za-z_])/ - ($1.upcase.ord % 32) - when /\\(?:M|Meta)-([0-9A-Za-z_])/ - [?\e.ord, $1.ord] - when /\\(\d{1,3})/ then $1.to_i(8) # octal - when /\\x(\h{1,2})/ then $1.to_i(16) # hexadecimal - when "\\e" then ?\e.ord - when "\\\\" then ?\\.ord - when "\\\"" then ?".ord - when "\\'" then ?'.ord - when "\\a" then ?\a.ord - when "\\b" then ?\b.ord - when "\\d" then ?\d.ord - when "\\f" then ?\f.ord - when "\\n" then ?\n.ord - when "\\r" then ?\r.ord - when "\\t" then ?\t.ord - when "\\v" then ?\v.ord - else notation.ord - end - end - - def parse_keyseq(str) - str.scan(KEYSEQ_PATTERN).flat_map do |notation| - key_notation_to_code(notation) - end - end - - def reload - reset_variables - read - end - - private def seven_bit_encoding?(encoding) - encoding == Encoding::US_ASCII - end -end diff --git a/lib/reline/face.rb b/lib/reline/face.rb deleted file mode 100644 index 5b4464a623..0000000000 --- a/lib/reline/face.rb +++ /dev/null @@ -1,199 +0,0 @@ -# frozen_string_literal: true - -class Reline::Face - SGR_PARAMETERS = { - foreground: { - black: 30, - red: 31, - green: 32, - yellow: 33, - blue: 34, - magenta: 35, - cyan: 36, - white: 37, - bright_black: 90, - gray: 90, - bright_red: 91, - bright_green: 92, - bright_yellow: 93, - bright_blue: 94, - bright_magenta: 95, - bright_cyan: 96, - bright_white: 97 - }, - background: { - black: 40, - red: 41, - green: 42, - yellow: 43, - blue: 44, - magenta: 45, - cyan: 46, - white: 47, - bright_black: 100, - gray: 100, - bright_red: 101, - bright_green: 102, - bright_yellow: 103, - bright_blue: 104, - bright_magenta: 105, - bright_cyan: 106, - bright_white: 107, - }, - style: { - reset: 0, - bold: 1, - faint: 2, - italicized: 3, - underlined: 4, - slowly_blinking: 5, - blinking: 5, - rapidly_blinking: 6, - negative: 7, - concealed: 8, - crossed_out: 9 - } - }.freeze - - class Config - ESSENTIAL_DEFINE_NAMES = %i(default enhanced scrollbar).freeze - RESET_SGR = "\e[0m".freeze - - def initialize(name, &block) - @definition = {} - block.call(self) - ESSENTIAL_DEFINE_NAMES.each do |name| - @definition[name] ||= { style: :reset, escape_sequence: RESET_SGR } - end - end - - attr_reader :definition - - def define(name, **values) - values[:escape_sequence] = format_to_sgr(values.to_a).freeze - @definition[name] = values - end - - def reconfigure - @definition.each_value do |values| - values.delete(:escape_sequence) - values[:escape_sequence] = format_to_sgr(values.to_a).freeze - end - end - - def [](name) - @definition.dig(name, :escape_sequence) or raise ArgumentError, "unknown face: #{name}" - end - - private - - def sgr_rgb(key, value) - return nil unless rgb_expression?(value) - if Reline::Face.truecolor? - sgr_rgb_truecolor(key, value) - else - sgr_rgb_256color(key, value) - end - end - - def sgr_rgb_truecolor(key, value) - case key - when :foreground - "38;2;" - when :background - "48;2;" - end + value[1, 6].scan(/../).map(&:hex).join(";") - end - - def sgr_rgb_256color(key, value) - # 256 colors are - # 0..15: standard colors, high intensity colors - # 16..232: 216 colors (R, G, B each 6 steps) - # 233..255: grayscale colors (24 steps) - # This methods converts rgb_expression to 216 colors - rgb = value[1, 6].scan(/../).map(&:hex) - # Color steps are [0, 95, 135, 175, 215, 255] - r, g, b = rgb.map { |v| v <= 95 ? v / 48 : (v - 35) / 40 } - color = (16 + 36 * r + 6 * g + b) - case key - when :foreground - "38;5;#{color}" - when :background - "48;5;#{color}" - end - end - - def format_to_sgr(ordered_values) - sgr = "\e[" + ordered_values.map do |key_value| - key, value = key_value - case key - when :foreground, :background - case value - when Symbol - SGR_PARAMETERS[key][value] - when String - sgr_rgb(key, value) - end - when :style - [ value ].flatten.map do |style_name| - SGR_PARAMETERS[:style][style_name] - end.then do |sgr_parameters| - sgr_parameters.include?(nil) ? nil : sgr_parameters - end - end.then do |rendition_expression| - unless rendition_expression - raise ArgumentError, "invalid SGR parameter: #{value.inspect}" - end - rendition_expression - end - end.join(';') + "m" - sgr == RESET_SGR ? RESET_SGR : RESET_SGR + sgr - end - - def rgb_expression?(color) - color.respond_to?(:match?) and color.match?(/\A#[0-9a-fA-F]{6}\z/) - end - end - - private_constant :SGR_PARAMETERS, :Config - - def self.truecolor? - @force_truecolor || %w[truecolor 24bit].include?(ENV['COLORTERM']) - end - - def self.force_truecolor - @force_truecolor = true - @configs&.each_value(&:reconfigure) - end - - def self.[](name) - @configs[name] - end - - def self.config(name, &block) - @configs ||= {} - @configs[name] = Config.new(name, &block) - end - - def self.configs - @configs.transform_values(&:definition) - end - - def self.load_initial_configs - config(:default) do |conf| - conf.define :default, style: :reset - conf.define :enhanced, style: :reset - conf.define :scrollbar, style: :reset - end - config(:completion_dialog) do |conf| - conf.define :default, foreground: :bright_white, background: :gray - conf.define :enhanced, foreground: :black, background: :white - conf.define :scrollbar, foreground: :white, background: :gray - end - end - - def self.reset_to_initial_configs - @configs = {} - load_initial_configs - end -end diff --git a/lib/reline/history.rb b/lib/reline/history.rb deleted file mode 100644 index 47c68ba774..0000000000 --- a/lib/reline/history.rb +++ /dev/null @@ -1,76 +0,0 @@ -class Reline::History < Array - def initialize(config) - @config = config - end - - def to_s - 'HISTORY' - end - - def delete_at(index) - index = check_index(index) - super(index) - end - - def [](index) - index = check_index(index) unless index.is_a?(Range) - super(index) - end - - def []=(index, val) - index = check_index(index) - super(index, Reline::Unicode.safe_encode(val, Reline.encoding_system_needs)) - end - - def concat(*val) - val.each do |v| - push(*v) - end - end - - def push(*val) - # If history_size is zero, all histories are dropped. - return self if @config.history_size.zero? - # If history_size is negative, history size is unlimited. - if @config.history_size.positive? - diff = size + val.size - @config.history_size - if diff > 0 - if diff <= size - shift(diff) - else - diff -= size - clear - val.shift(diff) - end - end - end - super(*(val.map{ |v| - Reline::Unicode.safe_encode(v, Reline.encoding_system_needs) - })) - end - - def <<(val) - # If history_size is zero, all histories are dropped. - return self if @config.history_size.zero? - # If history_size is negative, history size is unlimited. - if @config.history_size.positive? - shift if size + 1 > @config.history_size - end - super(Reline::Unicode.safe_encode(val, Reline.encoding_system_needs)) - end - - 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'") - end - # If history_size is negative, history size is unlimited. - if @config.history_size.positive? - if index < [email protected]_size or @config.history_size < index - raise RangeError.new("index=<#{index}>") - end - end - raise IndexError.new("index=<#{index}>") if index < 0 or size <= index - index - end -end diff --git a/lib/reline/io.rb b/lib/reline/io.rb deleted file mode 100644 index 0f48f53b82..0000000000 --- a/lib/reline/io.rb +++ /dev/null @@ -1,55 +0,0 @@ - -module Reline - class IO - RESET_COLOR = "\e[0m" - - def self.decide_io_gate - if ENV['TERM'] == 'dumb' - Reline::Dumb.new - else - require 'reline/io/ansi' - - case RbConfig::CONFIG['host_os'] - when /mswin|msys|mingw|cygwin|bccwin|wince|emc/ - require 'reline/io/windows' - io = Reline::Windows.new - if io.msys_tty? - Reline::ANSI.new - else - io - end - else - Reline::ANSI.new - end - end - end - - def dumb? - false - end - - def win? - false - end - - def reset_color_sequence - self.class::RESET_COLOR - end - - # Read a single encoding valid character from the input. - def read_single_char(keyseq_timeout) - buffer = String.new(encoding: Encoding::ASCII_8BIT) - loop do - timeout = buffer.empty? ? Float::INFINITY : keyseq_timeout - c = getc(timeout) - return unless c - - buffer << c - encoded = buffer.dup.force_encoding(encoding) - return encoded if encoded.valid_encoding? - end - end - end -end - -require 'reline/io/dumb' diff --git a/lib/reline/io/ansi.rb b/lib/reline/io/ansi.rb deleted file mode 100644 index 1d20719779..0000000000 --- a/lib/reline/io/ansi.rb +++ /dev/null @@ -1,332 +0,0 @@ -require 'io/console' -require 'io/wait' - -class Reline::ANSI < Reline::IO - CAPNAME_KEY_BINDINGS = { - 'khome' => :ed_move_to_beg, - 'kend' => :ed_move_to_end, - 'kdch1' => :key_delete, - 'kpp' => :ed_search_prev_history, - 'knp' => :ed_search_next_history, - 'kcuu1' => :ed_prev_history, - 'kcud1' => :ed_next_history, - 'kcuf1' => :ed_next_char, - 'kcub1' => :ed_prev_char, - } - - ANSI_CURSOR_KEY_BINDINGS = { - # Up - 'A' => [:ed_prev_history, {}], - # Down - 'B' => [:ed_next_history, {}], - # Right - 'C' => [:ed_next_char, { ctrl: :em_next_word, meta: :em_next_word }], - # Left - 'D' => [:ed_prev_char, { ctrl: :ed_prev_word, meta: :ed_prev_word }], - # End - 'F' => [:ed_move_to_end, {}], - # Home - 'H' => [:ed_move_to_beg, {}], - } - - attr_writer :input, :output - - def initialize - @input = STDIN - @output = STDOUT - @buf = [] - @output_buffer = nil - @old_winch_handler = nil - end - - def encoding - @input.external_encoding || Encoding.default_external - rescue IOError - # STDIN.external_encoding raises IOError in Ruby <= 3.0 when STDIN is closed - Encoding.default_external - end - - def set_default_key_bindings(config) - set_bracketed_paste_key_bindings(config) - set_default_key_bindings_ansi_cursor(config) - set_default_key_bindings_comprehensive_list(config) - { - [27, 91, 90] => :completion_journey_up, # S-Tab - }.each_pair do |key, func| - config.add_default_key_binding_by_keymap(:emacs, key, func) - config.add_default_key_binding_by_keymap(:vi_insert, key, func) - end - { - # default bindings - [27, 32] => :em_set_mark, # M-<space> - [24, 24] => :em_exchange_mark, # C-x C-x - }.each_pair do |key, func| - config.add_default_key_binding_by_keymap(:emacs, key, func) - end - end - - def 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 set_default_key_bindings_ansi_cursor(config) - ANSI_CURSOR_KEY_BINDINGS.each do |char, (default_func, modifiers)| - bindings = [ - ["\e[#{char}", default_func], # CSI + char - ["\eO#{char}", default_func] # SS3 + char, application cursor key mode - ] - if modifiers[:ctrl] - # CSI + ctrl_key_modifier + char - bindings << ["\e[1;5#{char}", modifiers[:ctrl]] - end - if modifiers[:meta] - # CSI + meta_key_modifier + char - bindings << ["\e[1;3#{char}", modifiers[:meta]] - # Meta(ESC) + CSI + char - bindings << ["\e\e[#{char}", modifiers[:meta]] - end - bindings.each do |sequence, func| - key = sequence.bytes - config.add_default_key_binding_by_keymap(:emacs, key, func) - config.add_default_key_binding_by_keymap(:vi_insert, key, func) - config.add_default_key_binding_by_keymap(:vi_command, key, func) - end - end - end - - def set_default_key_bindings_comprehensive_list(config) - { - # xterm - [27, 91, 51, 126] => :key_delete, # kdch1 - [27, 91, 53, 126] => :ed_search_prev_history, # kpp - [27, 91, 54, 126] => :ed_search_next_history, # knp - - # Console (80x25) - [27, 91, 49, 126] => :ed_move_to_beg, # Home - [27, 91, 52, 126] => :ed_move_to_end, # End - - # urxvt / exoterm - [27, 91, 55, 126] => :ed_move_to_beg, # Home - [27, 91, 56, 126] => :ed_move_to_end, # End - }.each_pair do |key, func| - config.add_default_key_binding_by_keymap(:emacs, key, func) - config.add_default_key_binding_by_keymap(:vi_insert, key, func) - config.add_default_key_binding_by_keymap(:vi_command, key, func) - end - end - - def with_raw_input - if @input.tty? - @input.raw(intr: true) { yield } - else - yield - end - end - - def inner_getc(timeout_second) - unless @buf.empty? - return @buf.shift - end - until @input.wait_readable(0.01) - timeout_second -= 0.01 - return nil if timeout_second <= 0 - - Reline.core.line_editor.handle_signal - end - c = @input.getbyte - (c == 0x16 && @input.tty? && @input.raw(min: 0, time: 0, &:getbyte)) || c - rescue Errno::EIO - # Maybe the I/O has been closed. - nil - end - - START_BRACKETED_PASTE = String.new("\e[200~", encoding: Encoding::ASCII_8BIT) - END_BRACKETED_PASTE = String.new("\e[201~", encoding: Encoding::ASCII_8BIT) - def read_bracketed_paste - buffer = String.new(encoding: Encoding::ASCII_8BIT) - until buffer.end_with?(END_BRACKETED_PASTE) - c = inner_getc(Float::INFINITY) - break unless c - buffer << c - end - 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 getc(timeout_second) - inner_getc(timeout_second) - end - - def in_pasting? - not empty_buffer? - end - - def empty_buffer? - unless @buf.empty? - return false - end - [email protected]_readable(0) - end - - def ungetc(c) - @buf.unshift(c) - end - - def retrieve_keybuffer - begin - return unless @input.wait_readable(0.001) - str = @input.read_nonblock(1024) - str.bytes.each do |c| - @buf.push(c) - end - rescue EOFError - end - end - - def get_screen_size - s = @input.winsize - return s if s[0] > 0 && s[1] > 0 - s = [ENV["LINES"].to_i, ENV["COLUMNS"].to_i] - return s if s[0] > 0 && s[1] > 0 - [24, 80] - rescue Errno::ENOTTY, Errno::ENODEV - [24, 80] - end - - def set_screen_size(rows, columns) - @input.winsize = [rows, columns] - self - rescue Errno::ENOTTY, Errno::ENODEV - self - end - - private def cursor_pos_internal(timeout:) - match = nil - @input.raw do |stdin| - @output << "\e[6n" - @output.flush - timeout_at = Time.now + timeout - buf = +'' - while (wait = timeout_at - Time.now) > 0 && stdin.wait_readable(wait) - buf << stdin.readpartial(1024) - if (match = buf.match(/\e\[(?<row>\d+);(?<column>\d+)R/)) - buf = match.pre_match + match.post_match - break - end - end - buf.chars.reverse_each do |ch| - stdin.ungetc ch - end - end - [match[:column].to_i - 1, match[:row].to_i - 1] if match - end - - def cursor_pos - col, row = cursor_pos_internal(timeout: 0.5) if both_tty? - Reline::CursorPos.new(col || 0, row || 0) - end - - def both_tty? - @input.tty? && @output.tty? - end - - def write(string) - if @output_buffer - @output_buffer << string - else - @output.write(string) - end - end - - def buffered_output - @output_buffer = +'' - yield - @output.write(@output_buffer) - ensure - @output_buffer = nil - end - - def move_cursor_column(x) - write "\e[#{x + 1}G" - end - - def move_cursor_up(x) - if x > 0 - write "\e[#{x}A" - elsif x < 0 - move_cursor_down(-x) - end - end - - def move_cursor_down(x) - if x > 0 - write "\e[#{x}B" - elsif x < 0 - move_cursor_up(-x) - end - end - - def hide_cursor - write "\e[?25l" - end - - def show_cursor - write "\e[?25h" - end - - def erase_after_cursor - write "\e[K" - end - - # This only works when the cursor is at the bottom of the scroll range - # For more details, see https://github.com/ruby/reline/pull/577#issuecomment-1646679623 - def scroll_down(x) - return if x.zero? - # We use `\n` instead of CSI + S because CSI + S would cause https://github.com/ruby/reline/issues/576 - write "\n" * x - end - - def clear_screen - write "\e[2J" - write "\e[1;1H" - end - - def set_winch_handler(&handler) - @old_winch_handler = Signal.trap('WINCH') do |arg| - handler.call - @old_winch_handler.call(arg) if @old_winch_handler.respond_to?(:call) - end - @old_cont_handler = Signal.trap('CONT') do |arg| - @input.raw!(intr: true) if @input.tty? - # Rerender the screen. Note that screen size might be changed while suspended. - handler.call - @old_cont_handler.call(arg) if @old_cont_handler.respond_to?(:call) - end - rescue ArgumentError - # Signal.trap may raise an ArgumentError if the platform doesn't support the signal. - end - - def read_single_char(keyseq_timeout) - # Disable intr to read `C-c` `C-z` `C-\` for quoted insert - @input.raw(intr: false) do - super - end - end - - def prep - # Enable bracketed paste - write "\e[?2004h" if Reline.core.config.enable_bracketed_paste && both_tty? - retrieve_keybuffer - nil - end - - def deprep(otio) - # Disable bracketed paste - write "\e[?2004l" if Reline.core.config.enable_bracketed_paste && both_tty? - Signal.trap('WINCH', @old_winch_handler) if @old_winch_handler - Signal.trap('CONT', @old_cont_handler) if @old_cont_handler - end -end diff --git a/lib/reline/io/dumb.rb b/lib/reline/io/dumb.rb deleted file mode 100644 index 0c04c755d2..0000000000 --- a/lib/reline/io/dumb.rb +++ /dev/null @@ -1,120 +0,0 @@ -require 'io/wait' - -class Reline::Dumb < Reline::IO - RESET_COLOR = '' # Do not send color reset sequence - - attr_writer :output - - def initialize(encoding: nil) - @input = STDIN - @output = STDOUT - @buf = [] - @pasting = false - @encoding = encoding - @screen_size = [24, 80] - end - - def dumb? - true - end - - def encoding - if @encoding - @encoding - elsif RUBY_PLATFORM =~ /mswin|mingw/ - Encoding::UTF_8 - else - @input.external_encoding || Encoding.default_external - end - rescue IOError - # STDIN.external_encoding raises IOError in Ruby <= 3.0 when STDIN is closed - Encoding.default_external - end - - def set_default_key_bindings(_) - end - - def input=(val) - @input = val - end - - def with_raw_input - yield - end - - def write(string) - @output.write(string) - end - - def buffered_output - yield - end - - def getc(_timeout_second) - unless @buf.empty? - return @buf.shift - 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) - break - end - c&.ord - end - - def ungetc(c) - @buf.unshift(c) - end - - def get_screen_size - @screen_size - end - - def cursor_pos - Reline::CursorPos.new(0, 0) - end - - def hide_cursor - end - - def show_cursor - end - - def move_cursor_column(val) - end - - def move_cursor_up(val) - end - - def move_cursor_down(val) - end - - def erase_after_cursor - end - - def scroll_down(val) - end - - def clear_screen - end - - def set_screen_size(rows, columns) - @screen_size = [rows, columns] - end - - def set_winch_handler(&handler) - end - - def in_pasting? - @pasting - end - - def prep - end - - def deprep(otio) - end -end diff --git a/lib/reline/io/windows.rb b/lib/reline/io/windows.rb deleted file mode 100644 index 5c1ab6d080..0000000000 --- a/lib/reline/io/windows.rb +++ /dev/null @@ -1,530 +0,0 @@ -require 'fiddle/import' - -class Reline::Windows < Reline::IO - - attr_writer :output - - def initialize - @input_buf = [] - @output_buf = [] - - @output = STDOUT - @hsg = nil - @getwch = Win32API.new('msvcrt', '_getwch', [], 'I') - @kbhit = Win32API.new('msvcrt', '_kbhit', [], 'I') - @GetKeyState = Win32API.new('user32', 'GetKeyState', ['L'], 'L') - @GetConsoleScreenBufferInfo = Win32API.new('kernel32', 'GetConsoleScreenBufferInfo', ['L', 'P'], 'L') - @SetConsoleCursorPosition = Win32API.new('kernel32', 'SetConsoleCursorPosition', ['L', 'L'], 'L') - @GetStdHandle = Win32API.new('kernel32', 'GetStdHandle', ['L'], 'L') - @FillConsoleOutputCharacter = Win32API.new('kernel32', 'FillConsoleOutputCharacter', ['L', 'L', 'L', 'L', 'P'], 'L') - @ScrollConsoleScreenBuffer = Win32API.new('kernel32', 'ScrollConsoleScreenBuffer', ['L', 'P', 'P', 'L', 'P'], 'L') - @hConsoleHandle = @GetStdHandle.call(STD_OUTPUT_HANDLE) - @hConsoleInputHandle = @GetStdHandle.call(STD_INPUT_HANDLE) - @GetNumberOfConsoleInputEvents = Win32API.new('kernel32', 'GetNumberOfConsoleInputEvents', ['L', 'P'], 'L') - @ReadConsoleInputW = Win32API.new('kernel32', 'ReadConsoleInputW', ['L', 'P', 'L', 'P'], 'L') - @GetFileType = Win32API.new('kernel32', 'GetFileType', ['L'], 'L') - @GetFileInformationByHandleEx = Win32API.new('kernel32', 'GetFileInformationByHandleEx', ['L', 'I', 'P', 'L'], 'I') - @FillConsoleOutputAttribute = Win32API.new('kernel32', 'FillConsoleOutputAttribute', ['L', 'L', 'L', 'L', 'P'], 'L') - @SetConsoleCursorInfo = Win32API.new('kernel32', 'SetConsoleCursorInfo', ['L', 'P'], 'L') - - @GetConsoleMode = Win32API.new('kernel32', 'GetConsoleMode', ['L', 'P'], 'L') - @SetConsoleMode = Win32API.new('kernel32', 'SetConsoleMode', ['L', 'L'], 'L') - @WaitForSingleObject = Win32API.new('kernel32', 'WaitForSingleObject', ['L', 'L'], 'L') - - @legacy_console = getconsolemode & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0 - end - - def encoding - Encoding::UTF_8 - end - - def win? - true - end - - def win_legacy_console? - @legacy_console - end - - def set_default_key_bindings(config) - { - [224, 72] => :ed_prev_history, # ↑ - [224, 80] => :ed_next_history, # ↓ - [224, 77] => :ed_next_char, # → - [224, 75] => :ed_prev_char, # ← - [224, 83] => :key_delete, # Del - [224, 71] => :ed_move_to_beg, # Home - [224, 79] => :ed_move_to_end, # End - [ 0, 72] => :ed_prev_history, # ↑ - [ 0, 80] => :ed_next_history, # ↓ - [ 0, 77] => :ed_next_char, # → - [ 0, 75] => :ed_prev_char, # ← - [ 0, 83] => :key_delete, # Del - [ 0, 71] => :ed_move_to_beg, # Home - [ 0, 79] => :ed_move_to_end # End - }.each_pair do |key, func| - config.add_default_key_binding_by_keymap(:emacs, key, func) - config.add_default_key_binding_by_keymap(:vi_insert, key, func) - config.add_default_key_binding_by_keymap(:vi_command, key, func) - end - - { - [27, 32] => :em_set_mark, # M-<space> - [24, 24] => :em_exchange_mark, # C-x C-x - }.each_pair do |key, func| - config.add_default_key_binding_by_keymap(:emacs, key, func) - end - - # Emulate ANSI key sequence. - { - [27, 91, 90] => :completion_journey_up, # S-Tab - }.each_pair do |key, func| - config.add_default_key_binding_by_keymap(:emacs, key, func) - config.add_default_key_binding_by_keymap(:vi_insert, key, func) - end - end - - if defined? JRUBY_VERSION - require 'win32api' - else - class Win32API - DLL = {} - TYPEMAP = {"0" => Fiddle::TYPE_VOID, "S" => Fiddle::TYPE_VOIDP, "I" => Fiddle::TYPE_LONG} - POINTER_TYPE = Fiddle::SIZEOF_VOIDP == Fiddle::SIZEOF_LONG_LONG ? 'q*' : 'l!*' - - WIN32_TYPES = "VPpNnLlIi" - DL_TYPES = "0SSI" - - def initialize(dllname, func, import, export = "0", calltype = :stdcall) - @proto = [import].join.tr(WIN32_TYPES, DL_TYPES).sub(/^(.)0*$/, '\1') - import = @proto.chars.map {|win_type| TYPEMAP[win_type.tr(WIN32_TYPES, DL_TYPES)]} - export = TYPEMAP[export.tr(WIN32_TYPES, DL_TYPES)] - calltype = Fiddle::Importer.const_get(:CALL_TYPE_TO_ABI)[calltype] - - handle = DLL[dllname] ||= - begin - Fiddle.dlopen(dllname) - rescue Fiddle::DLError - raise unless File.extname(dllname).empty? - Fiddle.dlopen(dllname + ".dll") - end - - @func = Fiddle::Function.new(handle[func], import, export, calltype) - rescue Fiddle::DLError => e - raise LoadError, e.message, e.backtrace - end - - 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].pack("I").unpack("i") if import[i] == "I" - end - ret, = @func.call(*args) - return ret || 0 - end - end - end - - VK_RETURN = 0x0D - VK_MENU = 0x12 # ALT key - VK_LMENU = 0xA4 - VK_CONTROL = 0x11 - VK_SHIFT = 0x10 - VK_DIVIDE = 0x6F - - KEY_EVENT = 0x01 - WINDOW_BUFFER_SIZE_EVENT = 0x04 - - CAPSLOCK_ON = 0x0080 - ENHANCED_KEY = 0x0100 - LEFT_ALT_PRESSED = 0x0002 - LEFT_CTRL_PRESSED = 0x0008 - NUMLOCK_ON = 0x0020 - RIGHT_ALT_PRESSED = 0x0001 - RIGHT_CTRL_PRESSED = 0x0004 - SCROLLLOCK_ON = 0x0040 - SHIFT_PRESSED = 0x0010 - - VK_TAB = 0x09 - VK_END = 0x23 - VK_HOME = 0x24 - VK_LEFT = 0x25 - VK_UP = 0x26 - VK_RIGHT = 0x27 - VK_DOWN = 0x28 - VK_DELETE = 0x2E - - STD_INPUT_HANDLE = -10 - STD_OUTPUT_HANDLE = -11 - FILE_TYPE_PIPE = 0x0003 - FILE_NAME_INFO = 2 - ENABLE_WRAP_AT_EOL_OUTPUT = 2 - ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4 - - # Calling Win32API with console handle is reported to fail after executing some external command. - # We need to refresh console handle and retry the call again. - private def call_with_console_handle(win32func, *args) - val = win32func.call(@hConsoleHandle, *args) - return val if val != 0 - - @hConsoleHandle = @GetStdHandle.call(STD_OUTPUT_HANDLE) - win32func.call(@hConsoleHandle, *args) - end - - private def getconsolemode - mode = +"\0\0\0\0" - call_with_console_handle(@GetConsoleMode, mode) - mode.unpack1('L') - end - - private def setconsolemode(mode) - call_with_console_handle(@SetConsoleMode, mode) - end - - #if @legacy_console - # setconsolemode(getconsolemode() | ENABLE_VIRTUAL_TERMINAL_PROCESSING) - # @legacy_console = (getconsolemode() & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0) - #end - - def msys_tty?(io = @hConsoleInputHandle) - # check if fd is a pipe - if @GetFileType.call(io) != FILE_TYPE_PIPE - return false - end - - bufsize = 1024 - p_buffer = "\0" * bufsize - res = @GetFileInformationByHandleEx.call(io, FILE_NAME_INFO, p_buffer, bufsize - 2) - return false if res == 0 - - # get pipe name: p_buffer layout is: - # struct _FILE_NAME_INFO { - # DWORD FileNameLength; - # WCHAR FileName[1]; - # } FILE_NAME_INFO - len = p_buffer[0, 4].unpack1("L") - name = p_buffer[4, len].encode(Encoding::UTF_8, Encoding::UTF_16LE, invalid: :replace) - - # Check if this could be a MSYS2 pty pipe ('\msys-XXXX-ptyN-XX') - # or a cygwin pty pipe ('\cygwin-XXXX-ptyN-XX') - name =~ /(msys-|cygwin-).*-pty/ ? true : false - end - - KEY_MAP = [ - # It's treated as Meta+Enter on Windows. - [ { control_keys: :CTRL, virtual_key_code: 0x0D }, "\e\r".bytes ], - [ { control_keys: :SHIFT, virtual_key_code: 0x0D }, "\e\r".bytes ], - - # It's treated as Meta+Space on Windows. - [ { control_keys: :CTRL, char_code: 0x20 }, "\e ".bytes ], - - # Emulate getwch() key sequences. - [ { control_keys: [], virtual_key_code: VK_UP }, [0, 72] ], - [ { control_keys: [], virtual_key_code: VK_DOWN }, [0, 80] ], - [ { control_keys: [], virtual_key_code: VK_RIGHT }, [0, 77] ], - [ { control_keys: [], virtual_key_code: VK_LEFT }, [0, 75] ], - [ { control_keys: [], virtual_key_code: VK_DELETE }, [0, 83] ], - [ { control_keys: [], virtual_key_code: VK_HOME }, [0, 71] ], - [ { control_keys: [], virtual_key_code: VK_END }, [0, 79] ], - - # Emulate ANSI key sequence. - [ { control_keys: :SHIFT, virtual_key_code: VK_TAB }, [27, 91, 90] ], - ] - - def process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state) - - # high-surrogate - if 0xD800 <= char_code and char_code <= 0xDBFF - @hsg = char_code - return - end - # low-surrogate - if 0xDC00 <= char_code and char_code <= 0xDFFF - if @hsg - char_code = 0x10000 + (@hsg - 0xD800) * 0x400 + char_code - 0xDC00 - @hsg = nil - else - # no high-surrogate. ignored. - return - end - else - # ignore high-surrogate without low-surrogate if there - @hsg = nil - end - - key = KeyEventRecord.new(virtual_key_code, char_code, control_key_state) - - match = KEY_MAP.find { |args,| key.match?(**args) } - unless match.nil? - @output_buf.concat(match.last) - return - end - - # no char, only control keys - return if key.char_code == 0 and key.control_keys.any? - - @output_buf.push("\e".ord) if key.control_keys.include?(:ALT) and !key.control_keys.include?(:CTRL) - - @output_buf.concat(key.char.bytes) - end - - def check_input_event - num_of_events = 0.chr * 8 - while @output_buf.empty? - 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 - next - end - next if @GetNumberOfConsoleInputEvents.(@hConsoleInputHandle, num_of_events) == 0 or num_of_events.unpack1('L') == 0 - input_records = 0.chr * 20 * 80 - read_event = 0.chr * 4 - if @ReadConsoleInputW.(@hConsoleInputHandle, input_records, 80, read_event) != 0 - read_events = read_event.unpack1('L') - 0.upto(read_events) do |idx| - input_record = input_records[idx * 20, 20] - event = input_record[0, 2].unpack1('s*') - case event - when WINDOW_BUFFER_SIZE_EVENT - @winch_handler.() - when KEY_EVENT - key_down = input_record[4, 4].unpack1('l*') - repeat_count = input_record[8, 2].unpack1('s*') - virtual_key_code = input_record[10, 2].unpack1('s*') - virtual_scan_code = input_record[12, 2].unpack1('s*') - char_code = input_record[14, 2].unpack1('S*') - control_key_state = input_record[16, 2].unpack1('S*') - is_key_down = key_down.zero? ? false : true - if is_key_down - process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state) - end - end - end - end - end - end - - def with_raw_input - yield - end - - def write(string) - @output.write(string) - end - - def buffered_output - yield - end - - def getc(_timeout_second) - check_input_event - @output_buf.shift - end - - def ungetc(c) - @output_buf.unshift(c) - end - - def in_pasting? - not empty_buffer? - end - - def empty_buffer? - if not @output_buf.empty? - false - elsif @kbhit.call == 0 - true - else - false - end - end - - def get_console_screen_buffer_info - # CONSOLE_SCREEN_BUFFER_INFO - # [ 0,2] dwSize.X - # [ 2,2] dwSize.Y - # [ 4,2] dwCursorPositions.X - # [ 6,2] dwCursorPositions.Y - # [ 8,2] wAttributes - # [10,2] srWindow.Left - # [12,2] srWindow.Top - # [14,2] srWindow.Right - # [16,2] srWindow.Bottom - # [18,2] dwMaximumWindowSize.X - # [20,2] dwMaximumWindowSize.Y - csbi = 0.chr * 22 - if call_with_console_handle(@GetConsoleScreenBufferInfo, csbi) != 0 - # returns [width, height, x, y, attributes, left, top, right, bottom] - csbi.unpack("s9") - else - return nil - end - end - - ALTERNATIVE_CSBI = [80, 24, 0, 0, 7, 0, 0, 79, 23].freeze - - def get_screen_size - width, _, _, _, _, _, top, _, bottom = get_console_screen_buffer_info || ALTERNATIVE_CSBI - [bottom - top + 1, width] - end - - def cursor_pos - _, _, x, y, _, _, top, = get_console_screen_buffer_info || ALTERNATIVE_CSBI - Reline::CursorPos.new(x, y - top) - end - - def move_cursor_column(val) - _, _, _, y, = get_console_screen_buffer_info - call_with_console_handle(@SetConsoleCursorPosition, y * 65536 + val) if y - end - - def move_cursor_up(val) - if val > 0 - _, _, x, y, _, _, top, = get_console_screen_buffer_info - return unless y - y = (y - top) - val - y = 0 if y < 0 - call_with_console_handle(@SetConsoleCursorPosition, (y + top) * 65536 + x) - elsif val < 0 - move_cursor_down(-val) - end - end - - def move_cursor_down(val) - if val > 0 - _, _, x, y, _, _, top, _, bottom = get_console_screen_buffer_info - return unless y - screen_height = bottom - top - y = (y - top) + val - y = screen_height if y > screen_height - call_with_console_handle(@SetConsoleCursorPosition, (y + top) * 65536 + x) - elsif val < 0 - move_cursor_up(-val) - end - end - - def erase_after_cursor - width, _, x, y, attributes, = get_console_screen_buffer_info - return unless x - written = 0.chr * 4 - call_with_console_handle(@FillConsoleOutputCharacter, 0x20, width - x, y * 65536 + x, written) - call_with_console_handle(@FillConsoleOutputAttribute, attributes, width - x, y * 65536 + x, written) - end - - # This only works when the cursor is at the bottom of the scroll range - # For more details, see https://github.com/ruby/reline/pull/577#issuecomment-1646679623 - def scroll_down(x) - return if x.zero? - # We use `\n` instead of CSI + S because CSI + S would cause https://github.com/ruby/reline/issues/576 - @output.write "\n" * x - end - - def clear_screen - if @legacy_console - width, _, _, _, attributes, _, top, _, bottom = get_console_screen_buffer_info - return unless width - fill_length = width * (bottom - top + 1) - screen_topleft = top * 65536 - written = 0.chr * 4 - call_with_console_handle(@FillConsoleOutputCharacter, 0x20, fill_length, screen_topleft, written) - call_with_console_handle(@FillConsoleOutputAttribute, attributes, fill_length, screen_topleft, written) - call_with_console_handle(@SetConsoleCursorPosition, screen_topleft) - else - @output.write "\e[2J" "\e[H" - end - end - - def set_screen_size(rows, columns) - raise NotImplementedError - end - - def hide_cursor - size = 100 - visible = 0 # 0 means false - cursor_info = [size, visible].pack('Li') - call_with_console_handle(@SetConsoleCursorInfo, cursor_info) - end - - def show_cursor - size = 100 - visible = 1 # 1 means true - cursor_info = [size, visible].pack('Li') - call_with_console_handle(@SetConsoleCursorInfo, cursor_info) - end - - def set_winch_handler(&handler) - @winch_handler = handler - end - - def prep - # do nothing - nil - end - - def deprep(otio) - # do nothing - end - - def disable_auto_linewrap(setting = true, &block) - mode = getconsolemode - if 0 == (mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) - if block - begin - setconsolemode(mode & ~ENABLE_WRAP_AT_EOL_OUTPUT) - block.call - ensure - setconsolemode(mode | ENABLE_WRAP_AT_EOL_OUTPUT) - end - else - if setting - setconsolemode(mode & ~ENABLE_WRAP_AT_EOL_OUTPUT) - else - setconsolemode(mode | ENABLE_WRAP_AT_EOL_OUTPUT) - end - end - else - block.call if block - end - end - - class KeyEventRecord - - attr_reader :virtual_key_code, :char_code, :control_key_state, :control_keys - - def initialize(virtual_key_code, char_code, control_key_state) - @virtual_key_code = virtual_key_code - @char_code = char_code - @control_key_state = control_key_state - @enhanced = control_key_state & ENHANCED_KEY != 0 - - (@control_keys = []).tap do |control_keys| - # symbols must be sorted to make comparison is easier later on - control_keys << :ALT if control_key_state & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED) != 0 - control_keys << :CTRL if control_key_state & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) != 0 - control_keys << :SHIFT if control_key_state & SHIFT_PRESSED != 0 - end.freeze - end - - def char - @char_code.chr(Encoding::UTF_8) - end - - def enhanced? - @enhanced - end - - # Verifies if the arguments match with this key event. - # Nil arguments are ignored, but at least one must be passed as non-nil. - # To verify that no control keys were pressed, pass an empty array: `control_keys: []`. - def match?(control_keys: nil, virtual_key_code: nil, char_code: nil) - raise ArgumentError, 'No argument was passed to match key event' if control_keys.nil? && virtual_key_code.nil? && char_code.nil? - - (control_keys.nil? || [*control_keys].sort == @control_keys) && - (virtual_key_code.nil? || @virtual_key_code == virtual_key_code) && - (char_code.nil? || char_code == @char_code) - end - - end -end diff --git a/lib/reline/key_actor.rb b/lib/reline/key_actor.rb deleted file mode 100644 index 0ac7604556..0000000000 --- a/lib/reline/key_actor.rb +++ /dev/null @@ -1,8 +0,0 @@ -module Reline::KeyActor -end - -require 'reline/key_actor/base' -require 'reline/key_actor/composite' -require 'reline/key_actor/emacs' -require 'reline/key_actor/vi_command' -require 'reline/key_actor/vi_insert' diff --git a/lib/reline/key_actor/base.rb b/lib/reline/key_actor/base.rb deleted file mode 100644 index 5bd8609cdf..0000000000 --- a/lib/reline/key_actor/base.rb +++ /dev/null @@ -1,37 +0,0 @@ -class Reline::KeyActor::Base - def initialize(mappings = nil) - @matching_bytes = {} - @key_bindings = {} - add_mappings(mappings) if mappings - end - - def add_mappings(mappings) - add([27], :ed_ignore) - 128.times do |key| - func = mappings[key] - meta_func = mappings[key | 0b10000000] - add([key], func) if func - add([27, key], meta_func) if meta_func - end - end - - def add(key, func) - (1...key.size).each do |size| - @matching_bytes[key.take(size)] = true - end - @key_bindings[key] = func - end - - def matching?(key) - @matching_bytes[key] - end - - def get(key) - @key_bindings[key] - end - - def clear - @matching_bytes.clear - @key_bindings.clear - end -end diff --git a/lib/reline/key_actor/composite.rb b/lib/reline/key_actor/composite.rb deleted file mode 100644 index 37e94ce6cf..0000000000 --- a/lib/reline/key_actor/composite.rb +++ /dev/null @@ -1,17 +0,0 @@ -class Reline::KeyActor::Composite - def initialize(key_actors) - @key_actors = key_actors - end - - def matching?(key) - @key_actors.any? { |key_actor| key_actor.matching?(key) } - end - - def get(key) - @key_actors.each do |key_actor| - func = key_actor.get(key) - return func if func - end - nil - end -end diff --git a/lib/reline/key_actor/emacs.rb b/lib/reline/key_actor/emacs.rb deleted file mode 100644 index 1ed4af7684..0000000000 --- a/lib/reline/key_actor/emacs.rb +++ /dev/null @@ -1,517 +0,0 @@ -module Reline::KeyActor - EMACS_MAPPING = [ - # 0 ^@ - :em_set_mark, - # 1 ^A - :ed_move_to_beg, - # 2 ^B - :ed_prev_char, - # 3 ^C - :ed_ignore, - # 4 ^D - :em_delete, - # 5 ^E - :ed_move_to_end, - # 6 ^F - :ed_next_char, - # 7 ^G - nil, - # 8 ^H - :em_delete_prev_char, - # 9 ^I - :complete, - # 10 ^J - :ed_newline, - # 11 ^K - :ed_kill_line, - # 12 ^L - :ed_clear_screen, - # 13 ^M - :ed_newline, - # 14 ^N - :ed_next_history, - # 15 ^O - :ed_ignore, - # 16 ^P - :ed_prev_history, - # 17 ^Q - :ed_quoted_insert, - # 18 ^R - :vi_search_prev, - # 19 ^S - :vi_search_next, - # 20 ^T - :ed_transpose_chars, - # 21 ^U - :unix_line_discard, - # 22 ^V - :ed_quoted_insert, - # 23 ^W - :em_kill_region, - # 24 ^X - nil, - # 25 ^Y - :em_yank, - # 26 ^Z - :ed_ignore, - # 27 ^[ - nil, - # 28 ^\ - :ed_ignore, - # 29 ^] - :ed_ignore, - # 30 ^^ - nil, - # 31 ^_ - :undo, - # 32 SPACE - :ed_insert, - # 33 ! - :ed_insert, - # 34 " - :ed_insert, - # 35 # - :ed_insert, - # 36 $ - :ed_insert, - # 37 % - :ed_insert, - # 38 & - :ed_insert, - # 39 ' - :ed_insert, - # 40 ( - :ed_insert, - # 41 ) - :ed_insert, - # 42 * - :ed_insert, - # 43 + - :ed_insert, - # 44 , - :ed_insert, - # 45 - - :ed_insert, - # 46 . - :ed_insert, - # 47 / - :ed_insert, - # 48 0 - :ed_digit, - # 49 1 - :ed_digit, - # 50 2 - :ed_digit, - # 51 3 - :ed_digit, - # 52 4 - :ed_digit, - # 53 5 - :ed_digit, - # 54 6 - :ed_digit, - # 55 7 - :ed_digit, - # 56 8 - :ed_digit, - # 57 9 - :ed_digit, - # 58 : - :ed_insert, - # 59 ; - :ed_insert, - # 60 < - :ed_insert, - # 61 = - :ed_insert, - # 62 > - :ed_insert, - # 63 ? - :ed_insert, - # 64 @ - :ed_insert, - # 65 A - :ed_insert, - # 66 B - :ed_insert, - # 67 C - :ed_insert, - # 68 D - :ed_insert, - # 69 E - :ed_insert, - # 70 F - :ed_insert, - # 71 G - :ed_insert, - # 72 H - :ed_insert, - # 73 I - :ed_insert, - # 74 J - :ed_insert, - # 75 K - :ed_insert, - # 76 L - :ed_insert, - # 77 M - :ed_insert, - # 78 N - :ed_insert, - # 79 O - :ed_insert, - # 80 P - :ed_insert, - # 81 Q - :ed_insert, - # 82 R - :ed_insert, - # 83 S - :ed_insert, - # 84 T - :ed_insert, - # 85 U - :ed_insert, - # 86 V - :ed_insert, - # 87 W - :ed_insert, - # 88 X - :ed_insert, - # 89 Y - :ed_insert, - # 90 Z - :ed_insert, - # 91 [ - :ed_insert, - # 92 \ - :ed_insert, - # 93 ] - :ed_insert, - # 94 ^ - :ed_insert, - # 95 _ - :ed_insert, - # 96 ` - :ed_insert, - # 97 a - :ed_insert, - # 98 b - :ed_insert, - # 99 c - :ed_insert, - # 100 d - :ed_insert, - # 101 e - :ed_insert, - # 102 f - :ed_insert, - # 103 g - :ed_insert, - # 104 h - :ed_insert, - # 105 i - :ed_insert, - # 106 j - :ed_insert, - # 107 k - :ed_insert, - # 108 l - :ed_insert, - # 109 m - :ed_insert, - # 110 n - :ed_insert, - # 111 o - :ed_insert, - # 112 p - :ed_insert, - # 113 q - :ed_insert, - # 114 r - :ed_insert, - # 115 s - :ed_insert, - # 116 t - :ed_insert, - # 117 u - :ed_insert, - # 118 v - :ed_insert, - # 119 w - :ed_insert, - # 120 x - :ed_insert, - # 121 y - :ed_insert, - # 122 z - :ed_insert, - # 123 { - :ed_insert, - # 124 | - :ed_insert, - # 125 } - :ed_insert, - # 126 ~ - :ed_insert, - # 127 ^? - :em_delete_prev_char, - # 128 M-^@ - nil, - # 129 M-^A - nil, - # 130 M-^B - nil, - # 131 M-^C - nil, - # 132 M-^D - nil, - # 133 M-^E - nil, - # 134 M-^F - nil, - # 135 M-^G - nil, - # 136 M-^H - :ed_delete_prev_word, - # 137 M-^I - nil, - # 138 M-^J - :key_newline, - # 139 M-^K - nil, - # 140 M-^L - :ed_clear_screen, - # 141 M-^M - :key_newline, - # 142 M-^N - nil, - # 143 M-^O - nil, - # 144 M-^P - nil, - # 145 M-^Q - nil, - # 146 M-^R - nil, - # 147 M-^S - nil, - # 148 M-^T - nil, - # 149 M-^U - nil, - # 150 M-^V - nil, - # 151 M-^W - nil, - # 152 M-^X - nil, - # 153 M-^Y - :em_yank_pop, - # 154 M-^Z - nil, - # 155 M-^[ - nil, - # 156 M-^\ - nil, - # 157 M-^] - nil, - # 158 M-^^ - nil, - # 159 M-^_ - :redo, - # 160 M-SPACE - :em_set_mark, - # 161 M-! - nil, - # 162 M-" - nil, - # 163 M-# - nil, - # 164 M-$ - nil, - # 165 M-% - nil, - # 166 M-& - nil, - # 167 M-' - nil, - # 168 M-( - nil, - # 169 M-) - nil, - # 170 M-* - nil, - # 171 M-+ - nil, - # 172 M-, - nil, - # 173 M-- - nil, - # 174 M-. - nil, - # 175 M-/ - nil, - # 176 M-0 - :ed_argument_digit, - # 177 M-1 - :ed_argument_digit, - # 178 M-2 - :ed_argument_digit, - # 179 M-3 - :ed_argument_digit, - # 180 M-4 - :ed_argument_digit, - # 181 M-5 - :ed_argument_digit, - # 182 M-6 - :ed_argument_digit, - # 183 M-7 - :ed_argument_digit, - # 184 M-8 - :ed_argument_digit, - # 185 M-9 - :ed_argument_digit, - # 186 M-: - nil, - # 187 M-; - nil, - # 188 M-< - nil, - # 189 M-= - nil, - # 190 M-> - nil, - # 191 M-? - nil, - # 192 M-@ - nil, - # 193 M-A - nil, - # 194 M-B - :ed_prev_word, - # 195 M-C - :em_capitol_case, - # 196 M-D - :em_delete_next_word, - # 197 M-E - nil, - # 198 M-F - :em_next_word, - # 199 M-G - nil, - # 200 M-H - nil, - # 201 M-I - nil, - # 202 M-J - nil, - # 203 M-K - nil, - # 204 M-L - :em_lower_case, - # 205 M-M - nil, - # 206 M-N - :vi_search_next, - # 207 M-O - nil, - # 208 M-P - :vi_search_prev, - # 209 M-Q - nil, - # 210 M-R - nil, - # 211 M-S - nil, - # 212 M-T - nil, - # 213 M-U - :em_upper_case, - # 214 M-V - nil, - # 215 M-W - nil, - # 216 M-X - nil, - # 217 M-Y - :em_yank_pop, - # 218 M-Z - nil, - # 219 M-[ - nil, - # 220 M-\ - nil, - # 221 M-] - nil, - # 222 M-^ - nil, - # 223 M-_ - nil, - # 224 M-` - nil, - # 225 M-a - nil, - # 226 M-b - :ed_prev_word, - # 227 M-c - :em_capitol_case, - # 228 M-d - :em_delete_next_word, - # 229 M-e - nil, - # 230 M-f - :em_next_word, - # 231 M-g - nil, - # 232 M-h - nil, - # 233 M-i - nil, - # 234 M-j - nil, - # 235 M-k - nil, - # 236 M-l - :em_lower_case, - # 237 M-m - nil, - # 238 M-n - :vi_search_next, - # 239 M-o - nil, - # 240 M-p - :vi_search_prev, - # 241 M-q - nil, - # 242 M-r - nil, - # 243 M-s - nil, - # 244 M-t - :ed_transpose_words, - # 245 M-u - :em_upper_case, - # 246 M-v - nil, - # 247 M-w - nil, - # 248 M-x - nil, - # 249 M-y - nil, - # 250 M-z - nil, - # 251 M-{ - nil, - # 252 M-| - nil, - # 253 M-} - nil, - # 254 M-~ - nil, - # 255 M-^? - :ed_delete_prev_word - # EOF - ] -end diff --git a/lib/reline/key_actor/vi_command.rb b/lib/reline/key_actor/vi_command.rb deleted file mode 100644 index 400f88477a..0000000000 --- a/lib/reline/key_actor/vi_command.rb +++ /dev/null @@ -1,518 +0,0 @@ -module Reline::KeyActor - VI_COMMAND_MAPPING = [ - # 0 ^@ - nil, - # 1 ^A - :ed_move_to_beg, - # 2 ^B - nil, - # 3 ^C - :ed_ignore, - # 4 ^D - :vi_end_of_transmission, - # 5 ^E - :ed_move_to_end, - # 6 ^F - nil, - # 7 ^G - nil, - # 8 ^H - :ed_prev_char, - # 9 ^I - nil, - # 10 ^J - :ed_newline, - # 11 ^K - :ed_kill_line, - # 12 ^L - :ed_clear_screen, - # 13 ^M - :ed_newline, - # 14 ^N - :ed_next_history, - # 15 ^O - :ed_ignore, - # 16 ^P - :ed_prev_history, - # 17 ^Q - :ed_ignore, - # 18 ^R - :vi_search_prev, - # 19 ^S - :ed_ignore, - # 20 ^T - :ed_transpose_chars, - # 21 ^U - :vi_kill_line_prev, - # 22 ^V - :ed_quoted_insert, - # 23 ^W - :ed_delete_prev_word, - # 24 ^X - nil, - # 25 ^Y - :em_yank, - # 26 ^Z - nil, - # 27 ^[ - nil, - # 28 ^\ - :ed_ignore, - # 29 ^] - nil, - # 30 ^^ - nil, - # 31 ^_ - nil, - # 32 SPACE - :ed_next_char, - # 33 ! - nil, - # 34 " - nil, - # 35 # - :vi_comment_out, - # 36 $ - :ed_move_to_end, - # 37 % - nil, - # 38 & - nil, - # 39 ' - nil, - # 40 ( - nil, - # 41 ) - nil, - # 42 * - nil, - # 43 + - :ed_next_history, - # 44 , - nil, - # 45 - - :ed_prev_history, - # 46 . - nil, - # 47 / - :vi_search_prev, - # 48 0 - :vi_zero, - # 49 1 - :ed_argument_digit, - # 50 2 - :ed_argument_digit, - # 51 3 - :ed_argument_digit, - # 52 4 - :ed_argument_digit, - # 53 5 - :ed_argument_digit, - # 54 6 - :ed_argument_digit, - # 55 7 - :ed_argument_digit, - # 56 8 - :ed_argument_digit, - # 57 9 - :ed_argument_digit, - # 58 : - nil, - # 59 ; - nil, - # 60 < - nil, - # 61 = - nil, - # 62 > - nil, - # 63 ? - :vi_search_next, - # 64 @ - :vi_alias, - # 65 A - :vi_add_at_eol, - # 66 B - :vi_prev_big_word, - # 67 C - :vi_change_to_eol, - # 68 D - :ed_kill_line, - # 69 E - :vi_end_big_word, - # 70 F - :vi_prev_char, - # 71 G - :vi_to_history_line, - # 72 H - nil, - # 73 I - :vi_insert_at_bol, - # 74 J - :vi_join_lines, - # 75 K - :vi_search_prev, - # 76 L - nil, - # 77 M - nil, - # 78 N - nil, - # 79 O - nil, - # 80 P - :vi_paste_prev, - # 81 Q - nil, - # 82 R - nil, - # 83 S - nil, - # 84 T - :vi_to_prev_char, - # 85 U - nil, - # 86 V - nil, - # 87 W - :vi_next_big_word, - # 88 X - :ed_delete_prev_char, - # 89 Y - nil, - # 90 Z - nil, - # 91 [ - nil, - # 92 \ - nil, - # 93 ] - nil, - # 94 ^ - :vi_first_print, - # 95 _ - nil, - # 96 ` - nil, - # 97 a - :vi_add, - # 98 b - :vi_prev_word, - # 99 c - :vi_change_meta, - # 100 d - :vi_delete_meta, - # 101 e - :vi_end_word, - # 102 f - :vi_next_char, - # 103 g - nil, - # 104 h - :ed_prev_char, - # 105 i - :vi_insert, - # 106 j - :ed_next_history, - # 107 k - :ed_prev_history, - # 108 l - :ed_next_char, - # 109 m - nil, - # 110 n - nil, - # 111 o - nil, - # 112 p - :vi_paste_next, - # 113 q - nil, - # 114 r - :vi_replace_char, - # 115 s - nil, - # 116 t - :vi_to_next_char, - # 117 u - nil, - # 118 v - :vi_histedit, - # 119 w - :vi_next_word, - # 120 x - :ed_delete_next_char, - # 121 y - :vi_yank, - # 122 z - nil, - # 123 { - nil, - # 124 | - :vi_to_column, - # 125 } - nil, - # 126 ~ - nil, - # 127 ^? - :em_delete_prev_char, - # 128 M-^@ - nil, - # 129 M-^A - nil, - # 130 M-^B - nil, - # 131 M-^C - nil, - # 132 M-^D - nil, - # 133 M-^E - nil, - # 134 M-^F - nil, - # 135 M-^G - nil, - # 136 M-^H - nil, - # 137 M-^I - nil, - # 138 M-^J - nil, - # 139 M-^K - nil, - # 140 M-^L - nil, - # 141 M-^M - nil, - # 142 M-^N - nil, - # 143 M-^O - nil, - # 144 M-^P - nil, - # 145 M-^Q - nil, - # 146 M-^R - nil, - # 147 M-^S - nil, - # 148 M-^T - nil, - # 149 M-^U - nil, - # 150 M-^V - nil, - # 151 M-^W - nil, - # 152 M-^X - nil, - # 153 M-^Y - nil, - # 154 M-^Z - nil, - # 155 M-^[ - nil, - # 156 M-^\ - nil, - # 157 M-^] - nil, - # 158 M-^^ - nil, - # 159 M-^_ - nil, - # 160 M-SPACE - nil, - # 161 M-! - nil, - # 162 M-" - nil, - # 163 M-# - nil, - # 164 M-$ - nil, - # 165 M-% - nil, - # 166 M-& - nil, - # 167 M-' - nil, - # 168 M-( - nil, - # 169 M-) - nil, - # 170 M-* - nil, - # 171 M-+ - nil, - # 172 M-, - nil, - # 173 M-- - nil, - # 174 M-. - nil, - # 175 M-/ - nil, - # 176 M-0 - nil, - # 177 M-1 - nil, - # 178 M-2 - nil, - # 179 M-3 - nil, - # 180 M-4 - nil, - # 181 M-5 - nil, - # 182 M-6 - nil, - # 183 M-7 - nil, - # 184 M-8 - nil, - # 185 M-9 - nil, - # 186 M-: - nil, - # 187 M-; - nil, - # 188 M-< - nil, - # 189 M-= - nil, - # 190 M-> - nil, - # 191 M-? - nil, - # 192 M-@ - nil, - # 193 M-A - nil, - # 194 M-B - nil, - # 195 M-C - nil, - # 196 M-D - nil, - # 197 M-E - nil, - # 198 M-F - nil, - # 199 M-G - nil, - # 200 M-H - nil, - # 201 M-I - nil, - # 202 M-J - nil, - # 203 M-K - nil, - # 204 M-L - nil, - # 205 M-M - nil, - # 206 M-N - nil, - # 207 M-O - nil, - # 208 M-P - nil, - # 209 M-Q - nil, - # 210 M-R - nil, - # 211 M-S - nil, - # 212 M-T - nil, - # 213 M-U - nil, - # 214 M-V - nil, - # 215 M-W - nil, - # 216 M-X - nil, - # 217 M-Y - nil, - # 218 M-Z - nil, - # 219 M-[ - nil, - # 220 M-\ - nil, - # 221 M-] - nil, - # 222 M-^ - nil, - # 223 M-_ - nil, - # 224 M-` - nil, - # 225 M-a - nil, - # 226 M-b - nil, - # 227 M-c - nil, - # 228 M-d - nil, - # 229 M-e - nil, - # 230 M-f - nil, - # 231 M-g - nil, - # 232 M-h - nil, - # 233 M-i - nil, - # 234 M-j - nil, - # 235 M-k - nil, - # 236 M-l - nil, - # 237 M-m - nil, - # 238 M-n - nil, - # 239 M-o - nil, - # 240 M-p - nil, - # 241 M-q - nil, - # 242 M-r - nil, - # 243 M-s - nil, - # 244 M-t - nil, - # 245 M-u - nil, - # 246 M-v - nil, - # 247 M-w - nil, - # 248 M-x - nil, - # 249 M-y - nil, - # 250 M-z - nil, - # 251 M-{ - nil, - # 252 M-| - nil, - # 253 M-} - nil, - # 254 M-~ - nil, - # 255 M-^? - nil - # EOF - ] -end - diff --git a/lib/reline/key_actor/vi_insert.rb b/lib/reline/key_actor/vi_insert.rb deleted file mode 100644 index 235b6fdf38..0000000000 --- a/lib/reline/key_actor/vi_insert.rb +++ /dev/null @@ -1,517 +0,0 @@ -module Reline::KeyActor - VI_INSERT_MAPPING = [ - # 0 ^@ - nil, - # 1 ^A - :ed_insert, - # 2 ^B - :ed_insert, - # 3 ^C - :ed_insert, - # 4 ^D - :vi_list_or_eof, - # 5 ^E - :ed_insert, - # 6 ^F - :ed_insert, - # 7 ^G - :ed_insert, - # 8 ^H - :vi_delete_prev_char, - # 9 ^I - :complete, - # 10 ^J - :ed_newline, - # 11 ^K - :ed_insert, - # 12 ^L - :ed_insert, - # 13 ^M - :ed_newline, - # 14 ^N - :menu_complete, - # 15 ^O - :ed_insert, - # 16 ^P - :menu_complete_backward, - # 17 ^Q - :ed_ignore, - # 18 ^R - :vi_search_prev, - # 19 ^S - :vi_search_next, - # 20 ^T - :ed_transpose_chars, - # 21 ^U - :vi_kill_line_prev, - # 22 ^V - :ed_quoted_insert, - # 23 ^W - :ed_delete_prev_word, - # 24 ^X - :ed_insert, - # 25 ^Y - :em_yank, - # 26 ^Z - :ed_insert, - # 27 ^[ - :vi_command_mode, - # 28 ^\ - :ed_ignore, - # 29 ^] - :ed_insert, - # 30 ^^ - :ed_insert, - # 31 ^_ - :ed_insert, - # 32 SPACE - :ed_insert, - # 33 ! - :ed_insert, - # 34 " - :ed_insert, - # 35 # - :ed_insert, - # 36 $ - :ed_insert, - # 37 % - :ed_insert, - # 38 & - :ed_insert, - # 39 ' - :ed_insert, - # 40 ( - :ed_insert, - # 41 ) - :ed_insert, - # 42 * - :ed_insert, - # 43 + - :ed_insert, - # 44 , - :ed_insert, - # 45 - - :ed_insert, - # 46 . - :ed_insert, - # 47 / - :ed_insert, - # 48 0 - :ed_digit, - # 49 1 - :ed_digit, - # 50 2 - :ed_digit, - # 51 3 - :ed_digit, - # 52 4 - :ed_digit, - # 53 5 - :ed_digit, - # 54 6 - :ed_digit, - # 55 7 - :ed_digit, - # 56 8 - :ed_digit, - # 57 9 - :ed_digit, - # 58 : - :ed_insert, - # 59 ; - :ed_insert, - # 60 < - :ed_insert, - # 61 = - :ed_insert, - # 62 > - :ed_insert, - # 63 ? - :ed_insert, - # 64 @ - :ed_insert, - # 65 A - :ed_insert, - # 66 B - :ed_insert, - # 67 C - :ed_insert, - # 68 D - :ed_insert, - # 69 E - :ed_insert, - # 70 F - :ed_insert, - # 71 G - :ed_insert, - # 72 H - :ed_insert, - # 73 I - :ed_insert, - # 74 J - :ed_insert, - # 75 K - :ed_insert, - # 76 L - :ed_insert, - # 77 M - :ed_insert, - # 78 N - :ed_insert, - # 79 O - :ed_insert, - # 80 P - :ed_insert, - # 81 Q - :ed_insert, - # 82 R - :ed_insert, - # 83 S - :ed_insert, - # 84 T - :ed_insert, - # 85 U - :ed_insert, - # 86 V - :ed_insert, - # 87 W - :ed_insert, - # 88 X - :ed_insert, - # 89 Y - :ed_insert, - # 90 Z - :ed_insert, - # 91 [ - :ed_insert, - # 92 \ - :ed_insert, - # 93 ] - :ed_insert, - # 94 ^ - :ed_insert, - # 95 _ - :ed_insert, - # 96 ` - :ed_insert, - # 97 a - :ed_insert, - # 98 b - :ed_insert, - # 99 c - :ed_insert, - # 100 d - :ed_insert, - # 101 e - :ed_insert, - # 102 f - :ed_insert, - # 103 g - :ed_insert, - # 104 h - :ed_insert, - # 105 i - :ed_insert, - # 106 j - :ed_insert, - # 107 k - :ed_insert, - # 108 l - :ed_insert, - # 109 m - :ed_insert, - # 110 n - :ed_insert, - # 111 o - :ed_insert, - # 112 p - :ed_insert, - # 113 q - :ed_insert, - # 114 r - :ed_insert, - # 115 s - :ed_insert, - # 116 t - :ed_insert, - # 117 u - :ed_insert, - # 118 v - :ed_insert, - # 119 w - :ed_insert, - # 120 x - :ed_insert, - # 121 y - :ed_insert, - # 122 z - :ed_insert, - # 123 { - :ed_insert, - # 124 | - :ed_insert, - # 125 } - :ed_insert, - # 126 ~ - :ed_insert, - # 127 ^? - :vi_delete_prev_char, - # 128 M-^@ - nil, - # 129 M-^A - nil, - # 130 M-^B - nil, - # 131 M-^C - nil, - # 132 M-^D - nil, - # 133 M-^E - nil, - # 134 M-^F - nil, - # 135 M-^G - nil, - # 136 M-^H - nil, - # 137 M-^I - nil, - # 138 M-^J - :key_newline, - # 139 M-^K - nil, - # 140 M-^L - nil, - # 141 M-^M - :key_newline, - # 142 M-^N - nil, - # 143 M-^O - nil, - # 144 M-^P - nil, - # 145 M-^Q - nil, - # 146 M-^R - nil, - # 147 M-^S - nil, - # 148 M-^T - nil, - # 149 M-^U - nil, - # 150 M-^V - nil, - # 151 M-^W - nil, - # 152 M-^X - nil, - # 153 M-^Y - nil, - # 154 M-^Z - nil, - # 155 M-^[ - nil, - # 156 M-^\ - nil, - # 157 M-^] - nil, - # 158 M-^^ - nil, - # 159 M-^_ - nil, - # 160 M-SPACE - nil, - # 161 M-! - nil, - # 162 M-" - nil, - # 163 M-# - nil, - # 164 M-$ - nil, - # 165 M-% - nil, - # 166 M-& - nil, - # 167 M-' - nil, - # 168 M-( - nil, - # 169 M-) - nil, - # 170 M-* - nil, - # 171 M-+ - nil, - # 172 M-, - nil, - # 173 M-- - nil, - # 174 M-. - nil, - # 175 M-/ - nil, - # 176 M-0 - nil, - # 177 M-1 - nil, - # 178 M-2 - nil, - # 179 M-3 - nil, - # 180 M-4 - nil, - # 181 M-5 - nil, - # 182 M-6 - nil, - # 183 M-7 - nil, - # 184 M-8 - nil, - # 185 M-9 - nil, - # 186 M-: - nil, - # 187 M-; - nil, - # 188 M-< - nil, - # 189 M-= - nil, - # 190 M-> - nil, - # 191 M-? - nil, - # 192 M-@ - nil, - # 193 M-A - nil, - # 194 M-B - nil, - # 195 M-C - nil, - # 196 M-D - nil, - # 197 M-E - nil, - # 198 M-F - nil, - # 199 M-G - nil, - # 200 M-H - nil, - # 201 M-I - nil, - # 202 M-J - nil, - # 203 M-K - nil, - # 204 M-L - nil, - # 205 M-M - nil, - # 206 M-N - nil, - # 207 M-O - nil, - # 208 M-P - nil, - # 209 M-Q - nil, - # 210 M-R - nil, - # 211 M-S - nil, - # 212 M-T - nil, - # 213 M-U - nil, - # 214 M-V - nil, - # 215 M-W - nil, - # 216 M-X - nil, - # 217 M-Y - nil, - # 218 M-Z - nil, - # 219 M-[ - nil, - # 220 M-\ - nil, - # 221 M-] - nil, - # 222 M-^ - nil, - # 223 M-_ - nil, - # 224 M-` - nil, - # 225 M-a - nil, - # 226 M-b - nil, - # 227 M-c - nil, - # 228 M-d - nil, - # 229 M-e - nil, - # 230 M-f - nil, - # 231 M-g - nil, - # 232 M-h - nil, - # 233 M-i - nil, - # 234 M-j - nil, - # 235 M-k - nil, - # 236 M-l - nil, - # 237 M-m - nil, - # 238 M-n - nil, - # 239 M-o - nil, - # 240 M-p - nil, - # 241 M-q - nil, - # 242 M-r - nil, - # 243 M-s - nil, - # 244 M-t - nil, - # 245 M-u - nil, - # 246 M-v - nil, - # 247 M-w - nil, - # 248 M-x - nil, - # 249 M-y - nil, - # 250 M-z - nil, - # 251 M-{ - nil, - # 252 M-| - nil, - # 253 M-} - nil, - # 254 M-~ - nil, - # 255 M-^? - nil - # EOF - ] -end diff --git a/lib/reline/key_stroke.rb b/lib/reline/key_stroke.rb deleted file mode 100644 index 4999225c9b..0000000000 --- a/lib/reline/key_stroke.rb +++ /dev/null @@ -1,119 +0,0 @@ -class Reline::KeyStroke - ESC_BYTE = 27 - CSI_PARAMETER_BYTES_RANGE = 0x30..0x3f - CSI_INTERMEDIATE_BYTES_RANGE = (0x20..0x2f) - - attr_accessor :encoding - - def initialize(config, encoding) - @config = config - @encoding = encoding - end - - # Input exactly matches to a key sequence - MATCHING = :matching - # Input partially matches to a key sequence - MATCHED = :matched - # Input matches to a key sequence and the key sequence is a prefix of another key sequence - MATCHING_MATCHED = :matching_matched - # Input does not match to any key sequence - UNMATCHED = :unmatched - - def match_status(input) - matching = key_mapping.matching?(input) - matched = key_mapping.get(input) - if matching && matched - MATCHING_MATCHED - elsif matching - MATCHING - elsif matched - MATCHED - elsif input[0] == ESC_BYTE - match_unknown_escape_sequence(input, vi_mode: @config.editing_mode_is?(:vi_insert, :vi_command)) - else - s = input.pack('c*').force_encoding(@encoding) - if s.valid_encoding? - s.size == 1 ? MATCHED : UNMATCHED - else - # Invalid string is MATCHING (part of valid string) or MATCHED (invalid bytes to be ignored) - MATCHING_MATCHED - end - end - end - - def expand(input) - matched_bytes = nil - (1..input.size).each do |i| - bytes = input.take(i) - status = match_status(bytes) - matched_bytes = bytes if status == MATCHED || status == MATCHING_MATCHED - break if status == MATCHED || status == UNMATCHED - end - return [[], []] unless matched_bytes - - func = key_mapping.get(matched_bytes) - s = matched_bytes.pack('c*').force_encoding(@encoding) - if func.is_a?(Array) - # Perform simple macro expansion for single byte key bindings. - # Multibyte key bindings and recursive macro expansion are not supported yet. - macro = func.pack('c*').force_encoding(@encoding) - keys = macro.chars.map do |c| - f = key_mapping.get(c.bytes) - Reline::Key.new(c, f.is_a?(Symbol) ? f : :ed_insert, false) - end - elsif func - keys = [Reline::Key.new(s, func, false)] - else - if s.valid_encoding? && s.size == 1 - keys = [Reline::Key.new(s, :ed_insert, false)] - else - keys = [] - end - end - - [keys, input.drop(matched_bytes.size)] - end - - private - - # returns match status of CSI/SS3 sequence and matched length - def match_unknown_escape_sequence(input, vi_mode: false) - idx = 0 - return UNMATCHED unless input[idx] == ESC_BYTE - idx += 1 - idx += 1 if input[idx] == ESC_BYTE - - case input[idx] - when nil - if idx == 1 # `ESC` - return MATCHING_MATCHED - else # `ESC ESC` - return MATCHING - end - when 91 # == '['.ord - # CSI sequence `ESC [ ... char` - idx += 1 - idx += 1 while idx < input.size && CSI_PARAMETER_BYTES_RANGE.cover?(input[idx]) - idx += 1 while idx < input.size && CSI_INTERMEDIATE_BYTES_RANGE.cover?(input[idx]) - when 79 # == 'O'.ord - # SS3 sequence `ESC O char` - idx += 1 - else - # `ESC char` or `ESC ESC char` - return UNMATCHED if vi_mode - end - - case input.size - when idx - MATCHING - when idx + 1 - MATCHED - else - UNMATCHED - end - end - - def key_mapping - @config.key_bindings - end -end diff --git a/lib/reline/kill_ring.rb b/lib/reline/kill_ring.rb deleted file mode 100644 index 201f6f3ca0..0000000000 --- a/lib/reline/kill_ring.rb +++ /dev/null @@ -1,125 +0,0 @@ -class Reline::KillRing - include Enumerable - - module State - FRESH = :fresh - CONTINUED = :continued - PROCESSED = :processed - YANK = :yank - end - - RingPoint = Struct.new(:backward, :forward, :str) do - def initialize(str) - super(nil, nil, str) - end - - def ==(other) - equal?(other) - end - end - - class RingBuffer - attr_reader :size - attr_reader :head - - def initialize(max = 1024) - @max = max - @size = 0 - @head = nil # reading head of ring-shaped tape - end - - def <<(point) - if @size.zero? - @head = point - @head.backward = @head - @head.forward = @head - @size = 1 - elsif @size >= @max - tail = @head.forward - new_tail = tail.forward - @head.forward = point - point.backward = @head - new_tail.backward = point - point.forward = new_tail - @head = point - else - tail = @head.forward - @head.forward = point - point.backward = @head - tail.backward = point - point.forward = tail - @head = point - @size += 1 - end - end - - def empty? - @size.zero? - end - end - - def initialize(max = 1024) - @ring = RingBuffer.new(max) - @ring_pointer = nil - @buffer = nil - @state = State::FRESH - end - - def append(string, before_p = false) - case @state - when State::FRESH, State::YANK - @ring << RingPoint.new(+string) - @state = State::CONTINUED - when State::CONTINUED, State::PROCESSED - if before_p - @ring.head.str.prepend(string) - else - @ring.head.str.concat(string) - end - @state = State::CONTINUED - end - end - - def process - case @state - when State::FRESH - # nothing to do - when State::CONTINUED - @state = State::PROCESSED - when State::PROCESSED - @state = State::FRESH - when State::YANK - # nothing to do - end - end - - def yank - unless @ring.empty? - @state = State::YANK - @ring_pointer = @ring.head - @ring_pointer.str - else - nil - end - end - - def yank_pop - if @state == State::YANK - prev_yank = @ring_pointer.str - @ring_pointer = @ring_pointer.backward - [@ring_pointer.str, prev_yank] - else - nil - end - end - - def each - start = head = @ring.head - loop do - break if head.nil? - yield head.str - head = head.backward - break if head == start - end - end -end diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb deleted file mode 100644 index 20f10b4897..0000000000 --- a/lib/reline/line_editor.rb +++ /dev/null @@ -1,2362 +0,0 @@ -require 'reline/kill_ring' -require 'reline/unicode' - -require 'tempfile' - -class Reline::LineEditor - # TODO: Use "private alias_method" idiom after drop Ruby 2.5. - attr_reader :byte_pointer - attr_accessor :confirm_multiline_termination_proc - attr_accessor :completion_proc - attr_accessor :completion_append_character - attr_accessor :output_modifier_proc - attr_accessor :prompt_proc - attr_accessor :auto_indent_proc - attr_accessor :dig_perfect_match_proc - - VI_MOTIONS = %i{ - ed_prev_char - ed_next_char - vi_zero - ed_move_to_beg - ed_move_to_end - vi_to_column - vi_next_char - vi_prev_char - vi_next_word - vi_prev_word - vi_to_next_char - vi_to_prev_char - vi_end_word - vi_next_big_word - vi_prev_big_word - vi_end_big_word - } - - module CompletionState - NORMAL = :normal - MENU = :menu - MENU_WITH_PERFECT_MATCH = :menu_with_perfect_match - PERFECT_MATCH = :perfect_match - end - - RenderedScreen = Struct.new(:base_y, :lines, :cursor_y, keyword_init: true) - - CompletionJourneyState = Struct.new(:line_index, :pre, :target, :post, :list, :pointer) - NullActionState = [nil, nil].freeze - - 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 - - MINIMUM_SCROLLBAR_HEIGHT = 1 - - def initialize(config) - @config = config - @completion_append_character = '' - @screen_size = [0, 0] # Should be initialized with actual winsize in LineEditor#reset - reset_variables - end - - def io_gate - Reline::IOGate - end - - def encoding - io_gate.encoding - 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 - - private def check_mode_string - if @config.show_mode_in_prompt - if @config.editing_mode_is?(:vi_command) - @config.vi_cmd_mode_string - elsif @config.editing_mode_is?(:vi_insert) - @config.vi_ins_mode_string - elsif @config.editing_mode_is?(:emacs) - @config.emacs_mode_string - else - '?' - end - end - end - - private def check_multiline_prompt(buffer, mode_string) - if @vi_arg - prompt = "(arg: #{@vi_arg}) " - elsif @searching_prompt - prompt = @searching_prompt - else - prompt = @prompt - end - if !@is_multiline - mode_string = check_mode_string - prompt = mode_string + prompt if mode_string - [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? - prompt_list = prompt_list.map{ |pr| mode_string + pr } if mode_string - prompt = prompt_list[@line_index] - prompt = prompt_list[0] if prompt.nil? - prompt = prompt_list.last if prompt.nil? - if buffer.size > prompt_list.size - (buffer.size - prompt_list.size).times do - prompt_list << prompt_list.last - end - end - prompt_list - else - prompt = mode_string + prompt if mode_string - [prompt] * buffer.size - end - end - - def reset(prompt = '') - @screen_size = Reline::IOGate.get_screen_size - reset_variables(prompt) - @rendered_screen.base_y = Reline::IOGate.cursor_pos.y - if ENV.key?('RELINE_ALT_SCROLLBAR') - @full_block = '::' - @upper_half_block = "''" - @lower_half_block = '..' - @block_elem_width = 2 - elsif Reline::IOGate.win? - @full_block = '█' - @upper_half_block = '▀' - @lower_half_block = '▄' - @block_elem_width = 1 - elsif encoding == Encoding::UTF_8 - @full_block = '█' - @upper_half_block = '▀' - @lower_half_block = '▄' - @block_elem_width = Reline::Unicode.calculate_width('█') - else - @full_block = '::' - @upper_half_block = "''" - @lower_half_block = '..' - @block_elem_width = 2 - end - end - - def handle_signal - handle_interrupted - handle_resized - end - - private def handle_resized - return unless @resized - - @screen_size = Reline::IOGate.get_screen_size - @resized = false - scroll_into_view - Reline::IOGate.move_cursor_up @rendered_screen.cursor_y - @rendered_screen.base_y = Reline::IOGate.cursor_pos.y - clear_rendered_screen_cache - render - end - - private def handle_interrupted - return unless @interrupted - - @interrupted = false - clear_dialogs - render - cursor_to_bottom_offset = @rendered_screen.lines.size - @rendered_screen.cursor_y - Reline::IOGate.scroll_down cursor_to_bottom_offset - Reline::IOGate.move_cursor_column 0 - clear_rendered_screen_cache - 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 - end - - def set_signal_handlers - Reline::IOGate.set_winch_handler do - @resized = true - end - @old_trap = Signal.trap('INT') do - @interrupted = true - end - end - - def finalize - Signal.trap('INT', @old_trap) - end - - def eof? - @eof - end - - def reset_variables(prompt = '') - @prompt = prompt.gsub("\n", "\\n") - @mark_pointer = nil - @is_multiline = false - @finished = false - @history_pointer = nil - @kill_ring ||= Reline::KillRing.new - @vi_clipboard = '' - @vi_arg = nil - @waiting_proc = nil - @vi_waiting_operator = nil - @vi_waiting_operator_arg = nil - @completion_journey_state = nil - @completion_state = CompletionState::NORMAL - @perfect_matched = nil - @menu_info = nil - @searching_prompt = nil - @just_cursor_moving = false - @eof = false - @continuous_insertion_buffer = String.new(encoding: encoding) - @scroll_partial_screen = 0 - @drop_terminate_spaces = false - @in_pasting = false - @auto_indent_proc = nil - @dialogs = [] - @interrupted = false - @resized = false - @cache = {} - @rendered_screen = RenderedScreen.new(base_y: 0, lines: [], cursor_y: 0) - @input_lines = [[[""], 0, 0]] - @input_lines_position = 0 - @restoring = false - @prev_action_state = NullActionState - @next_action_state = NullActionState - reset_line - end - - def reset_line - @byte_pointer = 0 - @buffer_of_lines = [String.new(encoding: encoding)] - @line_index = 0 - @cache.clear - @line_backup_in_history = nil - end - - def multiline_on - @is_multiline = true - end - - def multiline_off - @is_multiline = false - end - - private def insert_new_line(cursor_line, next_line) - @buffer_of_lines.insert(@line_index + 1, String.new(next_line, encoding: encoding)) - @buffer_of_lines[@line_index] = cursor_line - @line_index += 1 - @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 split_line_by_width(str, max_width, offset: 0) - Reline::Unicode.split_line_by_width(str, max_width, encoding, offset: offset) - end - - def current_byte_pointer_cursor - calculate_width(current_line.byteslice(0, @byte_pointer)) - end - - 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_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 - last_mbchar = line_to_calc.byteslice(line_to_calc.bytesize - last_byte_size, last_byte_size) - last_width = Reline::Unicode.get_mbchar_width(last_mbchar) - end_of_line_cursor = new_cursor_max - last_width - else - end_of_line_cursor = new_cursor_max - end - else - end_of_line_cursor = new_cursor_max - end - line_to_calc.grapheme_clusters.each do |gc| - mbchar = gc.encode(Encoding::UTF_8) - mbchar_width = Reline::Unicode.get_mbchar_width(mbchar) - now = new_cursor + mbchar_width - if now > end_of_line_cursor or now > cursor - break - end - new_cursor += mbchar_width - if new_cursor > max_width * height - height += 1 - end - new_byte_pointer += gc.bytesize - end - @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 modified_lines - with_cache(__method__, whole_lines, finished?) do |whole, complete| - modify_lines(whole, complete) - end - end - - 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 - 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_line_by_width(prompt, width) - wrapped_lines = split_line_by_width(line, width, offset: calculate_width(code_line_prompt, true)) - wrapped_prompts.map { |p| [p, ''] } + [[code_line_prompt, wrapped_lines.first]] + wrapped_lines.drop(1).map { |c| ['', c] } - end - end - end - - def calculate_overlay_levels(overlay_levels) - levels = [] - overlay_levels.each do |x, w, l| - levels.fill(l, x, w) - end - 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 - Reline::IOGate.write "#{Reline::IOGate.reset_color_sequence}#{' ' * 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 - Reline::IOGate.write "#{Reline::IOGate.reset_color_sequence}#{content}#{Reline::IOGate.reset_color_sequence}" - end - base_x += width - end - 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 = Reline::Unicode.escape_for_print(whole_lines[@line_index].byteslice(0, @byte_pointer)) - wrapped_line_before_cursor = split_line_by_width(' ' * prompt_width + line_before_cursor, screen_width) - 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 - Reline::IOGate.buffered_output do - render_differential([], 0, 0) - lines = @buffer_of_lines.size.times.map do |i| - line = Reline::Unicode.strip_non_printing_start_end(prompt_list[i]) + modified_lines[i] - wrapped_lines = split_line_by_width(line, screen_width) - wrapped_lines.last.empty? ? "#{line} " : line - end - Reline::IOGate.write lines.map { |l| "#{l}\r\n" }.join - end - end - - def print_nomultiline_prompt - Reline::IOGate.disable_auto_linewrap(true) if Reline::IOGate.win? - # Readline's test `TestRelineAsReadline#test_readline` requires first output to be prompt, not cursor reset escape sequence. - Reline::IOGate.write Reline::Unicode.strip_non_printing_start_end(@prompt) if @prompt && !@is_multiline - ensure - Reline::IOGate.disable_auto_linewrap(false) if Reline::IOGate.win? - end - - def render - wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position - 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 - @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 - - Reline::IOGate.buffered_output do - render_differential new_lines, wrapped_cursor_x, wrapped_cursor_y - screen_scroll_top - end - end - - # Reflects lines to be rendered and new cursor position to the screen - # by calculating the difference from the previous render. - - private def render_differential(new_lines, new_cursor_x, new_cursor_y) - Reline::IOGate.disable_auto_linewrap(true) if Reline::IOGate.win? - rendered_lines = @rendered_screen.lines - 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 - Reline::IOGate.move_cursor_column new_cursor_x - new_cursor_y = new_cursor_y.clamp(0, screen_height - 1) - Reline::IOGate.move_cursor_down new_cursor_y - cursor_y - @rendered_screen.cursor_y = new_cursor_y - ensure - Reline::IOGate.disable_auto_linewrap(false) if Reline::IOGate.win? - end - - private def clear_rendered_screen_cache - @rendered_screen.lines = [] - @rendered_screen.cursor_y = 0 - 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 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 - @proc_to_exec = proc_to_exec - @context = context - @cursor_pos = Reline::CursorPos.new - end - - def context - @context - end - - def retrieve_completion_block(_unused = false) - preposing, target, postposing, _quote = @line_editor.retrieve_completion_block - [preposing, target, postposing] - end - - def call_completion_proc_with_checking_args(pre, target, post) - @line_editor.call_completion_proc_with_checking_args(pre, target, post) - end - - def set_dialog(dialog) - @dialog = dialog - end - - def dialog - @dialog - end - - def set_cursor_pos(col, row) - @cursor_pos.x = col - @cursor_pos.y = row - end - - def set_key(key) - @key = key - end - - def key - @key - end - - def cursor_pos - @cursor_pos - end - - def just_cursor_moving - @line_editor.instance_variable_get(:@just_cursor_moving) - end - - def screen_width - @line_editor.screen_width - end - - def screen_height - @line_editor.screen_height - end - - def preferred_dialog_height - _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.dialog_proc_scope_completion_journey_data - end - - def config - @config - end - - def call - instance_exec(&@proc_to_exec) - end - end - - class Dialog - attr_reader :name, :contents, :width - attr_accessor :scroll_top, :pointer, :column, :vertical_offset, :trap_key - - def initialize(name, config, proc_scope) - @name = name - @config = config - @proc_scope = proc_scope - @width = nil - @scroll_top = 0 - @trap_key = nil - end - - def set_cursor_pos(col, row) - @proc_scope.set_cursor_pos(col, row) - end - - def width=(v) - @width = v - end - - def contents=(contents) - @contents = contents - if contents and @width.nil? - @width = contents.map{ |line| Reline::Unicode.calculate_width(line, true) }.max - end - end - - def call(key) - @proc_scope.set_dialog(self) - @proc_scope.set_key(key) - dialog_render_info = @proc_scope.call - if @trap_key - if @trap_key.any?{ |i| i.is_a?(Array) } # multiple trap - @trap_key.each do |t| - @config.add_oneshot_key_binding(t, @name) - end - else - @config.add_oneshot_key_binding(@trap_key, @name) - end - end - dialog_render_info - end - end - - def add_dialog_proc(name, p, context = nil) - dialog = Dialog.new(name, @config, DialogProcScope.new(self, @config, p, context)) - if index = @dialogs.find_index { |d| d.name == name } - @dialogs[index] = dialog - else - @dialogs << dialog - end - end - - DIALOG_DEFAULT_HEIGHT = 20 - - private def dialog_range(dialog, dialog_y) - x_range = dialog.column...dialog.column + dialog.width - y_range = dialog_y + dialog.vertical_offset...dialog_y + dialog.vertical_offset + dialog.contents.size - [x_range, y_range] - end - - 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 - return - end - contents = dialog_render_info.contents - pointer = dialog.pointer - if dialog_render_info.width - dialog.width = dialog_render_info.width - else - dialog.width = contents.map { |l| calculate_width(l, true) }.max - end - height = dialog_render_info.height || DIALOG_DEFAULT_HEIGHT - height = contents.size if contents.size < height - if contents.size > height - if dialog.pointer - if dialog.pointer < 0 - dialog.scroll_top = 0 - elsif (dialog.pointer - dialog.scroll_top) >= (height - 1) - dialog.scroll_top = dialog.pointer - (height - 1) - elsif (dialog.pointer - dialog.scroll_top) < 0 - dialog.scroll_top = dialog.pointer - end - pointer = dialog.pointer - dialog.scroll_top - else - dialog.scroll_top = 0 - end - contents = contents[dialog.scroll_top, height] - end - if dialog_render_info.scrollbar and dialog_render_info.contents.size > height - bar_max_height = height * 2 - moving_distance = (dialog_render_info.contents.size - height) * 2 - position_ratio = dialog.scroll_top.zero? ? 0.0 : ((dialog.scroll_top * 2).to_f / moving_distance) - bar_height = (bar_max_height * ((contents.size * 2).to_f / (dialog_render_info.contents.size * 2))).floor.to_i - bar_height = MINIMUM_SCROLLBAR_HEIGHT if bar_height < MINIMUM_SCROLLBAR_HEIGHT - scrollbar_pos = ((bar_max_height - bar_height) * position_ratio).floor.to_i - else - scrollbar_pos = nil - end - dialog.column = dialog_render_info.pos.x - dialog.width += @block_elem_width if scrollbar_pos - diff = (dialog.column + dialog.width) - screen_width - if diff > 0 - dialog.column -= diff - end - if rest_height(screen_scroll_top + cursor_row) - dialog_render_info.pos.y >= height - dialog.vertical_offset = dialog_render_info.pos.y + 1 - 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_width - end - face = Reline::Face[dialog_render_info.face || :default] - scrollbar_sgr = face[:scrollbar] - default_sgr = face[:default] - enhanced_sgr = face[:enhanced] - 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, = 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) - colored_content + scrollbar_sgr + @full_block - elsif scrollbar_pos <= (i * 2) and (i * 2) < (scrollbar_pos + bar_height) - colored_content + scrollbar_sgr + @upper_half_block - elsif scrollbar_pos <= (i * 2 + 1) and (i * 2) < (scrollbar_pos + bar_height) - colored_content + scrollbar_sgr + @lower_half_block - else - colored_content + scrollbar_sgr + ' ' * @block_elem_width - end - else - colored_content - end - end - end - - 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.map { |l| Reline::Unicode.escape_for_print(l) } - end - end - - def editing_mode - @config.editing_mode - end - - private def menu(list) - @menu_info = MenuInfo.new(list) - end - - private def filter_normalize_candidates(target, list) - target = target.downcase if @config.completion_ignore_case - list.select do |item| - next unless item - unless Encoding.compatible?(target.encoding, item.encoding) - # Workaround for Readline test - if defined?(::Readline) && ::Readline == ::Reline - raise Encoding::CompatibilityError, "incompatible character encodings: #{target.encoding} and #{item.encoding}" - end - end - - if @config.completion_ignore_case - item.downcase.start_with?(target) - else - item.start_with?(target) - end - end.map do |item| - item.unicode_normalize - rescue Encoding::CompatibilityError - item - end.uniq - end - - private def perform_completion(preposing, target, postposing, quote, list) - candidates = filter_normalize_candidates(target, list) - - case @completion_state - when CompletionState::PERFECT_MATCH - if @dig_perfect_match_proc - @dig_perfect_match_proc.call(@perfect_matched) - return - end - when CompletionState::MENU - menu(candidates) - return - when CompletionState::MENU_WITH_PERFECT_MATCH - menu(candidates) - @completion_state = CompletionState::PERFECT_MATCH - return - end - - completed = Reline::Unicode.common_prefix(candidates, ignore_case: @config.completion_ignore_case) - return if completed.empty? - - append_character = '' - if candidates.include?(completed) - if candidates.one? - append_character = quote || completion_append_character.to_s - @completion_state = CompletionState::PERFECT_MATCH - elsif @config.show_all_if_ambiguous - menu(candidates) - @completion_state = CompletionState::PERFECT_MATCH - else - @completion_state = CompletionState::MENU_WITH_PERFECT_MATCH - end - @perfect_matched = completed - else - @completion_state = CompletionState::MENU - menu(candidates) if @config.show_all_if_ambiguous - end - @buffer_of_lines[@line_index] = (preposing + completed + append_character + postposing).split("\n")[@line_index] || String.new(encoding: encoding) - line_to_pointer = (preposing + completed + append_character).split("\n")[@line_index] || String.new(encoding: encoding) - @byte_pointer = line_to_pointer.bytesize - 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_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, quote = retrieve_completion_block - list = call_completion_proc(preposing, target, postposing, quote) - 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) - # Reject multibyte input (converted to ed_insert) in vi_command mode - return if method_symbol == :ed_insert && @config.editing_mode_is?(:vi_command) && !@waiting_proc - - if ARGUMENT_DIGIT_METHODS.include?(method_symbol) && !@waiting_proc - wrap_method_call(method_symbol, key, false) - return - end - - if @vi_waiting_operator - if @waiting_proc || VI_MOTIONS.include?(method_symbol) - old_byte_pointer = @byte_pointer - @vi_arg = (@vi_arg || 1) * @vi_waiting_operator_arg - wrap_method_call(method_symbol, key, true) - unless @waiting_proc - byte_pointer_diff = @byte_pointer - old_byte_pointer - @byte_pointer = old_byte_pointer - __send__(@vi_waiting_operator, byte_pointer_diff) - cleanup_waiting - end - else - # Ignores operator when not motion is given. - wrap_method_call(method_symbol, key, false) - cleanup_waiting - end - else - wrap_method_call(method_symbol, key, false) - end - @vi_arg = nil - @kill_ring.process - end - - private def argumentable?(method_obj) - method_obj and method_obj.parameters.any? { |param| param[0] == :key and param[1] == :arg } - end - - private def inclusive?(method_obj) - # If a motion method with the keyword argument "inclusive" follows the - # operator, it must contain the character at the cursor position. - method_obj and method_obj.parameters.any? { |param| param[0] == :key and param[1] == :inclusive } - end - - def wrap_method_call(method_symbol, key, with_operator) - if @waiting_proc - @waiting_proc.call(key) - return - end - - return unless respond_to?(method_symbol, true) - method_obj = method(method_symbol) - if @vi_arg and argumentable?(method_obj) - if inclusive?(method_obj) - method_obj.(key, arg: @vi_arg, inclusive: with_operator) - else - method_obj.(key, arg: @vi_arg) - end - else - if inclusive?(method_obj) - method_obj.(key, inclusive: with_operator) - else - method_obj.(key) - end - 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 - - ARGUMENT_DIGIT_METHODS = %i[ed_digit vi_zero ed_argument_digit] - VI_WAITING_ACCEPT_METHODS = %i[vi_change_meta vi_delete_meta vi_yank ed_insert ed_argument_digit] - - private def process_key(key, method_symbol) - if @waiting_proc - cleanup_waiting unless key.size == 1 - end - if @vi_waiting_operator - cleanup_waiting unless VI_WAITING_ACCEPT_METHODS.include?(method_symbol) || VI_MOTIONS.include?(method_symbol) - end - - process_insert(force: method_symbol != :ed_insert) - - run_for_operators(key, method_symbol) - 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) - save_old_buffer - @config.reset_oneshot_key_bindings - if key.char.nil? - process_insert(force: true) - @eof = buffer_empty? - finish - return - end - return if @dialogs.any? { |dialog| dialog.name == key.method_symbol } - - @completion_occurs = false - - process_key(key.char, key.method_symbol) - 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 - end - - @prev_action_state, @next_action_state = @next_action_state, NullActionState - - unless @completion_occurs - @completion_state = CompletionState::NORMAL - @completion_journey_state = nil - end - - push_input_lines unless @restoring - @restoring = false - - if @in_pasting - clear_dialogs - return - end - - modified = @old_buffer_of_lines != @buffer_of_lines - if !@completion_occurs && modified && [email protected]_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 - end - - def push_input_lines - if @old_buffer_of_lines == @buffer_of_lines - @input_lines[@input_lines_position] = [@buffer_of_lines.dup, @byte_pointer, @line_index] - else - @input_lines = @input_lines[0..@input_lines_position] - @input_lines_position += 1 - @input_lines.push([@buffer_of_lines.dup, @byte_pointer, @line_index]) - end - trim_input_lines - end - - MAX_INPUT_LINES = 100 - def trim_input_lines - if @input_lines.size > MAX_INPUT_LINES - @input_lines.shift - @input_lines_position -= 1 - 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 - - def call_completion_proc(pre, target, post, quote) - Reline.core.instance_variable_set(:@completion_quote_character, quote) - result = call_completion_proc_with_checking_args(pre, target, post) - Reline.core.instance_variable_set(:@completion_quote_character, nil) - result - end - - def call_completion_proc_with_checking_args(pre, target, post) - if @completion_proc and target - argnum = @completion_proc.parameters.inject(0) { |result, item| - case item.first - when :req, :opt - result + 1 - when :rest - break 3 - end - } - case argnum - when 1 - result = @completion_proc.(target) - when 2 - result = @completion_proc.(target, pre) - when 3..Float::INFINITY - result = @completion_proc.(target, pre, post) - end - end - result - 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 - process_auto_indent - end - - def retrieve_completion_block - quote_characters = Reline.completer_quote_characters - before = current_line.byteslice(0, @byte_pointer).grapheme_clusters - quote = nil - # Calculate closing quote when cursor is at the end of the line - if current_line.bytesize == @byte_pointer && !quote_characters.empty? - escaped = false - before.each do |c| - if escaped - escaped = false - next - elsif c == '\\' - escaped = true - elsif quote - quote = nil if c == quote - elsif quote_characters.include?(c) - quote = c - end - end - end - - word_break_characters = quote_characters + Reline.completer_word_break_characters - break_index = before.rindex { |c| word_break_characters.include?(c) || quote_characters.include?(c) } || -1 - preposing = before.take(break_index + 1).join - target = before.drop(break_index + 1).join - postposing = current_line.byteslice(@byte_pointer, current_line.bytesize - @byte_pointer) - 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), quote&.encode(encoding)] - end - - def confirm_multiline_termination - temp_buffer = @buffer_of_lines.dup - @confirm_multiline_termination_proc.(temp_buffer.join("\n") + "\n") - end - - def insert_multiline_text(text) - pre = @buffer_of_lines[@line_index].byteslice(0, @byte_pointer) - post = @buffer_of_lines[@line_index].byteslice(@byte_pointer..) - lines = (pre + Reline::Unicode.safe_encode(text, encoding).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 - end - - def insert_text(text) - if @buffer_of_lines[@line_index].bytesize == @byte_pointer - @buffer_of_lines[@line_index] += text - else - @buffer_of_lines[@line_index] = byteinsert(@buffer_of_lines[@line_index], @byte_pointer, text) - end - @byte_pointer += text.bytesize - process_auto_indent - end - - def delete_text(start = nil, length = nil) - if start.nil? and length.nil? - 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 - end - elsif not start.nil? and not length.nil? - 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 = 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 = 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 - set_current_line(current_line.byteslice(0, start)) - end - end - - def byte_pointer=(val) - @byte_pointer = val - end - - def whole_lines - @buffer_of_lines.dup - end - - def whole_buffer - whole_lines.join("\n") - end - - private def buffer_empty? - current_line.empty? and @buffer_of_lines.size == 1 - end - - def finished? - @finished - end - - def finish - @finished = true - @config.reset - end - - private def byteslice!(str, byte_pointer, size) - new_str = str.byteslice(0, byte_pointer) - new_str << str.byteslice(byte_pointer + size, str.bytesize) - [new_str, str.byteslice(byte_pointer, size)] - end - - private def byteinsert(str, byte_pointer, other) - new_str = str.byteslice(0, byte_pointer) - new_str << other - new_str << str.byteslice(byte_pointer, str.bytesize) - new_str - end - - private def calculate_width(str, allow_escape_code = false) - Reline::Unicode.calculate_width(str, allow_escape_code) - end - - private def key_delete(key) - if @config.editing_mode_is?(:vi_insert) - ed_delete_next_char(key) - elsif @config.editing_mode_is?(:emacs) - em_delete(key) - end - end - - private def key_newline(key) - if @is_multiline - 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) - 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 - pre, target, post, quote = retrieve_completion_block - result = call_completion_proc(pre, target, post, quote) - if result.is_a?(Array) - @completion_occurs = true - perform_completion(pre, target, post, quote, result) - 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) - insert_text(@continuous_insertion_buffer) - @continuous_insertion_buffer.clear - end - - # Editline:: +ed-insert+ (vi input: almost all; emacs: printable characters) - # In insert mode, insert the input character left of the cursor - # position. In replace mode, overwrite the character at the - # cursor and move the cursor to the right by one character - # position. Accept an argument to do this repeatedly. It is an - # error if the input character is the NUL character (+Ctrl-@+). - # Failure to enlarge the edit buffer also results in an error. - # Editline:: +ed-digit+ (emacs: 0 to 9) If in argument input mode, append - # the input digit to the argument being read. Otherwise, call - # +ed-insert+. It is an error if the input character is not a - # digit or if the existing argument is already greater than a - # million. - # GNU Readline:: +self-insert+ (a, b, A, 1, !, …) Insert yourself. - private def ed_insert(str) - begin - str.encode(Encoding::UTF_8) - rescue Encoding::UndefinedConversionError - return - end - if @in_pasting - @continuous_insertion_buffer << str - return - elsif not @continuous_insertion_buffer.empty? - process_insert - end - - insert_text(str) - end - alias_method :self_insert, :ed_insert - - private def ed_digit(key) - if @vi_arg - ed_argument_digit(key) - else - ed_insert(key) - end - end - - private def insert_raw_char(str, arg: 1) - arg.times do - if str == "\C-j" or str == "\C-m" - key_newline(str) - elsif str != "\0" - # Ignore NUL. - ed_insert(str) - end - end - end - - private def ed_next_char(key, arg: 1) - byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer) - if (@byte_pointer < current_line.bytesize) - @byte_pointer += byte_size - elsif @config.editing_mode_is?(:emacs) and @byte_pointer == current_line.bytesize and @line_index < @buffer_of_lines.size - 1 - @byte_pointer = 0 - @line_index += 1 - end - arg -= 1 - ed_next_char(key, arg: arg) if arg > 0 - end - alias_method :forward_char, :ed_next_char - - private def ed_prev_char(key, arg: 1) - if @byte_pointer > 0 - byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer) - @byte_pointer -= byte_size - 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 - end - alias_method :backward_char, :ed_prev_char - - private def vi_first_print(key) - @byte_pointer = Reline::Unicode.vi_first_print(current_line) - end - - private def ed_move_to_beg(key) - @byte_pointer = 0 - end - alias_method :beginning_of_line, :ed_move_to_beg - - private def vi_zero(key) - if @vi_arg - ed_argument_digit(key) - else - ed_move_to_beg(key) - end - end - - private def ed_move_to_end(key) - @byte_pointer = current_line.bytesize - end - alias_method :end_of_line, :ed_move_to_end - - private def generate_searcher(search_key) - search_word = String.new(encoding: encoding) - hit_pointer = nil - lambda do |key| - search_again = false - case key - when "\C-h", "\C-?" - grapheme_clusters = search_word.grapheme_clusters - if grapheme_clusters.size > 0 - grapheme_clusters.pop - search_word = grapheme_clusters.join - end - when "\C-r", "\C-s" - search_again = true if search_key == key - search_key = key - else - search_word << key - end - 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 - if @history_pointer - case search_key - when "\C-r" - history_pointer_base = 0 - history = Reline::HISTORY[0..(@history_pointer - 1)] - when "\C-s" - 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 search_key - when "\C-r" - history_pointer_base = 0 - history = Reline::HISTORY[0..@history_pointer] - when "\C-s" - history_pointer_base = @history_pointer - history = Reline::HISTORY[@history_pointer..-1] - end - else - history_pointer_base = 0 - history = Reline::HISTORY - end - case search_key - when "\C-r" - hit_index = history.rindex { |item| - item.include?(search_word) - } - when "\C-s" - hit_index = history.index { |item| - item.include?(search_word) - } - end - if hit_index - hit_pointer = history_pointer_base + hit_index - hit = Reline::HISTORY[hit_pointer] - end - end - case search_key - when "\C-r" - prompt_name = 'reverse-i-search' - when "\C-s" - 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) - backup = @buffer_of_lines.dup, @line_index, @byte_pointer, @history_pointer, @line_backup_in_history - searcher = generate_searcher(key) - @searching_prompt = "(reverse-i-search)`': " - termination_keys = ["\C-j"] - termination_keys.concat(@config.isearch_terminators.chars) if @config.isearch_terminators - @waiting_proc = ->(k) { - if k == "\C-g" - # cancel search and restore buffer - @buffer_of_lines, @line_index, @byte_pointer, @history_pointer, @line_backup_in_history = backup - @searching_prompt = nil - @waiting_proc = nil - elsif !termination_keys.include?(k) && (k.match?(/[[:print:]]/) || k == "\C-h" || k == "\C-?" || k == "\C-r" || k == "\C-s") - 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) if hit_pointer - else - # terminaton_keys and other keys will terminalte - move_history(@history_pointer, line: :end, cursor: :start) - @searching_prompt = nil - @waiting_proc = nil - end - } - end - - private def vi_search_prev(key) - incremental_search_history(key) - end - alias_method :reverse_search_history, :vi_search_prev - - private def vi_search_next(key) - incremental_search_history(key) - end - alias_method :forward_search_history, :vi_search_next - - 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 - nil - end - - private def ed_search_prev_history(key, arg: 1) - substr = prev_action_state_value(:search_history) == :empty ? '' : 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: substr.empty? ? :end : @byte_pointer) - arg -= 1 - set_next_action_state(:search_history, :empty) if substr.empty? - 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 = prev_action_state_value(:search_history) == :empty ? '' : 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? - - move_history(h_pointer, line: line_index || :start, cursor: substr.empty? ? :end : @byte_pointer) - arg -= 1 - set_next_action_state(:search_history, :empty) if substr.empty? - ed_search_next_history(key, arg: arg) if arg > 0 - end - alias_method :history_search_forward, :ed_search_next_history - - private def move_history(history_pointer, line:, cursor:) - 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 = whole_buffer - else - Reline::HISTORY[old_history_pointer] = whole_buffer - end - if history_pointer == Reline::HISTORY.size - buf = @line_backup_in_history - @history_pointer = @line_backup_in_history = nil - else - buf = Reline::HISTORY[history_pointer] - @history_pointer = history_pointer - end - @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 @line_index < (@buffer_of_lines.size - 1) - cursor = current_byte_pointer_cursor - @line_index += 1 - calculate_nearest_cursor(cursor) - return - 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 - alias_method :next_history, :ed_next_history - - private def ed_newline(key) - process_insert(force: true) - if @is_multiline - if @config.editing_mode_is?(:vi_command) - if @line_index < (@buffer_of_lines.size - 1) - ed_next_history(key) # means cursor down - else - # should check confirm_multiline_termination to finish? - finish - end - else - if @line_index == @buffer_of_lines.size - 1 && confirm_multiline_termination - finish - else - key_newline(key) - end - end - else - finish - end - end - - private def ed_force_submit(_key) - process_insert(force: true) - finish - end - - private def em_delete_prev_char(key, arg: 1) - 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 - process_auto_indent - end - alias_method :backward_delete_char, :em_delete_prev_char - - # Editline:: +ed-kill-line+ (vi command: +D+, +Ctrl-K+; emacs: +Ctrl-K+, - # +Ctrl-U+) + Kill from the cursor to the end of the line. - # GNU Readline:: +kill-line+ (+C-k+) Kill the text from point to the end of - # 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 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 @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. - # GNU Readline:: +unix-line-discard+ (+C-u+) Kill backward from the cursor - # to the beginning of the current line. - private def vi_kill_line_prev(key) - if @byte_pointer > 0 - line, deleted = byteslice!(current_line, 0, @byte_pointer) - set_current_line(line, 0) - @kill_ring.append(deleted, true) - end - end - alias_method :unix_line_discard, :vi_kill_line_prev - - # Editline:: +em-kill-line+ (not bound) Delete the entire contents of the - # edit buffer and save it to the cut buffer. +vi-kill-line-prev+ - # 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 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 buffer_empty? and key == "\C-d" - @eof = true - finish - elsif @byte_pointer < current_line.bytesize - splitted_last = current_line.byteslice(@byte_pointer, current_line.bytesize) - mbchar = splitted_last.grapheme_clusters.first - 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 current_line.empty? or @byte_pointer < current_line.bytesize - em_delete(key) - elsif [email protected] # show completed list - pre, target, post, quote = retrieve_completion_block - result = call_completion_proc(pre, target, post, quote) - if result.is_a?(Array) - candidates = filter_normalize_candidates(target, result) - menu(candidates) - end - end - end - alias_method :delete_char_or_list, :em_delete_or_list - - private def em_yank(key) - yanked = @kill_ring.yank - 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 - 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) - Reline::IOGate.clear_screen - @screen_size = Reline::IOGate.get_screen_size - @rendered_screen.base_y = 0 - clear_rendered_screen_cache - end - alias_method :clear_screen, :ed_clear_screen - - private def em_next_word(key) - if current_line.bytesize > @byte_pointer - byte_size = Reline::Unicode.em_forward_word(current_line, @byte_pointer) - @byte_pointer += byte_size - end - end - alias_method :forward_word, :em_next_word - - private def ed_prev_word(key) - if @byte_pointer > 0 - byte_size = Reline::Unicode.em_backward_word(current_line, @byte_pointer) - @byte_pointer -= byte_size - end - end - alias_method :backward_word, :ed_prev_word - - private def em_delete_next_word(key) - 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) - end - end - alias_method :kill_word, :em_delete_next_word - - private def ed_delete_prev_word(key) - if @byte_pointer > 0 - 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) - end - end - alias_method :backward_kill_word, :ed_delete_prev_word - - private def ed_transpose_chars(key) - if @byte_pointer > 0 - 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(current_line, @byte_pointer) - if (@byte_pointer - back1_byte_size) > 0 - 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!(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(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? - from_head_to_left_word = before + right_word + middle + 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 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 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 = 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 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 = 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 = 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 - alias_method :unix_word_rubout, :em_kill_region - - private def copy_for_vi(text) - if @config.editing_mode_is?(:vi_insert) or @config.editing_mode_is?(:vi_command) - @vi_clipboard = text - end - end - - private def vi_insert(key) - @config.editing_mode = :vi_insert - end - - private def vi_add(key) - @config.editing_mode = :vi_insert - ed_next_char(key) - end - - private def vi_command_mode(key) - ed_prev_char(key) - @config.editing_mode = :vi_command - end - alias_method :vi_movement_mode, :vi_command_mode - - private def vi_next_word(key, arg: 1) - if current_line.bytesize > @byte_pointer - byte_size = Reline::Unicode.vi_forward_word(current_line, @byte_pointer, @drop_terminate_spaces) - @byte_pointer += byte_size - end - arg -= 1 - vi_next_word(key, arg: arg) if arg > 0 - end - - private def vi_prev_word(key, arg: 1) - if @byte_pointer > 0 - byte_size = Reline::Unicode.vi_backward_word(current_line, @byte_pointer) - @byte_pointer -= byte_size - end - arg -= 1 - vi_prev_word(key, arg: arg) if arg > 0 - end - - private def vi_end_word(key, arg: 1, inclusive: false) - if current_line.bytesize > @byte_pointer - byte_size = Reline::Unicode.vi_forward_end_word(current_line, @byte_pointer) - @byte_pointer += byte_size - end - arg -= 1 - if inclusive and arg.zero? - byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer) - if byte_size > 0 - @byte_pointer += byte_size - end - end - vi_end_word(key, arg: arg) if arg > 0 - end - - private def vi_next_big_word(key, arg: 1) - if current_line.bytesize > @byte_pointer - byte_size = Reline::Unicode.vi_big_forward_word(current_line, @byte_pointer) - @byte_pointer += byte_size - end - arg -= 1 - vi_next_big_word(key, arg: arg) if arg > 0 - end - - private def vi_prev_big_word(key, arg: 1) - if @byte_pointer > 0 - byte_size = Reline::Unicode.vi_big_backward_word(current_line, @byte_pointer) - @byte_pointer -= byte_size - 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 current_line.bytesize > @byte_pointer - byte_size = Reline::Unicode.vi_big_forward_end_word(current_line, @byte_pointer) - @byte_pointer += byte_size - end - arg -= 1 - if inclusive and arg.zero? - byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer) - if byte_size > 0 - @byte_pointer += byte_size - end - end - vi_end_big_word(key, arg: arg) if arg > 0 - end - - private def vi_delete_prev_char(key) - 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 - 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, _ = byteslice!(current_line, @byte_pointer, byte_size) - set_current_line(line) - end - end - - private def vi_insert_at_bol(key) - ed_move_to_beg(key) - @config.editing_mode = :vi_insert - end - - private def vi_add_at_eol(key) - ed_move_to_end(key) - @config.editing_mode = :vi_insert - end - - private def ed_delete_prev_char(key, arg: 1) - deleted = +'' - arg.times do - if @byte_pointer > 0 - byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer) - @byte_pointer -= byte_size - line, mbchar = byteslice!(current_line, @byte_pointer, byte_size) - set_current_line(line) - deleted.prepend(mbchar) - end - end - copy_for_vi(deleted) - end - - 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_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_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) - else - return - 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) - else - return - end - copy_for_vi(cut) - end - - private def vi_list_or_eof(key) - if buffer_empty? - @eof = true - finish - else - ed_newline(key) - end - end - alias_method :vi_end_of_transmission, :vi_list_or_eof - 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(current_line, @byte_pointer) - unless current_line.empty? || byte_size == 0 - line, mbchar = byteslice!(current_line, @byte_pointer, byte_size) - copy_for_vi(mbchar) - 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 - ed_delete_next_char(key, arg: arg) if arg > 0 - end - - private def vi_to_history_line(key) - if Reline::HISTORY.empty? - return - end - move_history(0, line: :start, cursor: :start) - end - - private def vi_histedit(key) - path = Tempfile.open { |fp| - fp.write whole_lines.join("\n") - fp.path - } - system("#{ENV['EDITOR']} #{path}") - @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 - cursor_point = @vi_clipboard.grapheme_clusters[0..-2].join - 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 - end - - private def vi_paste_next(key, arg: 1) - if @vi_clipboard.size > 0 - 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 - end - - private def ed_argument_digit(key) - # key is expected to be `ESC digit` or `digit` - num = key[/\d/].to_i - @vi_arg = (@vi_arg || 0) * 10 + num - end - - private def vi_to_column(key, arg: 0) - # 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) - 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(current_line, @byte_pointer) - before = current_line.byteslice(0, @byte_pointer) - remaining_point = @byte_pointer + byte_size - after = current_line.byteslice(remaining_point, current_line.bytesize - remaining_point) - set_current_line(before + k + after) - @waiting_proc = nil - elsif arg > 1 - byte_size = 0 - arg.times do - byte_size += Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer + byte_size) - end - before = current_line.byteslice(0, @byte_pointer) - remaining_point = @byte_pointer + byte_size - after = current_line.byteslice(remaining_point, current_line.bytesize - remaining_point) - replaced = k * arg - set_current_line(before + replaced + after, @byte_pointer + replaced.bytesize) - @waiting_proc = nil - end - } - end - - private def vi_next_char(key, arg: 1, inclusive: false) - @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, inclusive: inclusive) } - end - - private def vi_to_next_char(key, arg: 1, inclusive: false) - @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, need_prev_char: true, inclusive: inclusive) } - end - - private def search_next_char(key, arg, need_prev_char: false, inclusive: false) - prev_total = nil - total = nil - found = false - current_line.byteslice(@byte_pointer..-1).grapheme_clusters.each do |mbchar| - # total has [byte_size, cursor] - unless total - # skip cursor point - width = Reline::Unicode.get_mbchar_width(mbchar) - total = [mbchar.bytesize, width] - else - if key == mbchar - arg -= 1 - if arg.zero? - found = true - break - end - end - width = Reline::Unicode.get_mbchar_width(mbchar) - prev_total = total - total = [total.first + mbchar.bytesize, total.last + width] - end - end - if not need_prev_char and found and total - byte_size, _ = total - @byte_pointer += byte_size - elsif need_prev_char and found and prev_total - byte_size, _ = prev_total - @byte_pointer += byte_size - end - if inclusive - byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer) - if byte_size > 0 - @byte_pointer += byte_size - end - end - @waiting_proc = nil - end - - private def vi_prev_char(key, arg: 1) - @waiting_proc = ->(key_for_proc) { search_prev_char(key_for_proc, arg) } - end - - private def vi_to_prev_char(key, arg: 1) - @waiting_proc = ->(key_for_proc) { search_prev_char(key_for_proc, arg, true) } - end - - private def search_prev_char(key, arg, need_next_char = false) - prev_total = nil - total = nil - found = false - current_line.byteslice(0..@byte_pointer).grapheme_clusters.reverse_each do |mbchar| - # total has [byte_size, cursor] - unless total - # skip cursor point - width = Reline::Unicode.get_mbchar_width(mbchar) - total = [mbchar.bytesize, width] - else - if key == mbchar - arg -= 1 - if arg.zero? - found = true - break - end - end - width = Reline::Unicode.get_mbchar_width(mbchar) - prev_total = total - total = [total.first + mbchar.bytesize, total.last + width] - end - end - if not need_next_char and found and total - byte_size, _ = total - @byte_pointer -= byte_size - elsif need_next_char and found and prev_total - byte_size, _ = prev_total - @byte_pointer -= byte_size - end - @waiting_proc = nil - end - - private def vi_join_lines(key, arg: 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 - end - - private def em_set_mark(key) - @mark_pointer = [@byte_pointer, @line_index] - end - alias_method :set_mark, :em_set_mark - - private def em_exchange_mark(key) - return unless @mark_pointer - new_pointer = [@byte_pointer, @line_index] - @byte_pointer, @line_index = @mark_pointer - @mark_pointer = new_pointer - end - alias_method :exchange_point_and_mark, :em_exchange_mark - - 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 move_undo_redo(direction) - @restoring = true - return unless (0..@input_lines.size - 1).cover?(@input_lines_position + direction) - - @input_lines_position += direction - buffer_of_lines, byte_pointer, line_index = @input_lines[@input_lines_position] - @buffer_of_lines = buffer_of_lines.dup - @line_index = line_index - @byte_pointer = byte_pointer - end - - private def undo(_key) - move_undo_redo(-1) - end - - private def redo(_key) - move_undo_redo(+1) - end - - private def prev_action_state_value(type) - @prev_action_state[0] == type ? @prev_action_state[1] : nil - end - - private def set_next_action_state(type, value) - @next_action_state = [type, value] - end - - private def re_read_init_file(_key) - @config.reload - end -end diff --git a/lib/reline/reline.gemspec b/lib/reline/reline.gemspec deleted file mode 100644 index dfaf966728..0000000000 --- a/lib/reline/reline.gemspec +++ /dev/null @@ -1,30 +0,0 @@ - -begin - require_relative 'lib/reline/version' -rescue LoadError - require_relative 'version' -end - -Gem::Specification.new do |spec| - spec.name = 'reline' - spec.version = Reline::VERSION - spec.authors = ['aycabta'] - spec.email = ['[email protected]'] - - spec.summary = %q{Alternative GNU Readline or Editline implementation by pure Ruby.} - spec.description = %q{Alternative GNU Readline or Editline implementation by pure Ruby.} - spec.homepage = 'https://github.com/ruby/reline' - spec.license = 'Ruby' - - 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') - - spec.add_dependency 'io-console', '~> 0.5' -end diff --git a/lib/reline/unicode.rb b/lib/reline/unicode.rb deleted file mode 100644 index 21e4ea240e..0000000000 --- a/lib/reline/unicode.rb +++ /dev/null @@ -1,415 +0,0 @@ -class Reline::Unicode - EscapedPairs = { - 0x00 => '^@', - 0x01 => '^A', # C-a - 0x02 => '^B', - 0x03 => '^C', - 0x04 => '^D', - 0x05 => '^E', - 0x06 => '^F', - 0x07 => '^G', - 0x08 => '^H', # Backspace - 0x09 => '^I', - 0x0A => '^J', - 0x0B => '^K', - 0x0C => '^L', - 0x0D => '^M', # Enter - 0x0E => '^N', - 0x0F => '^O', - 0x10 => '^P', - 0x11 => '^Q', - 0x12 => '^R', - 0x13 => '^S', - 0x14 => '^T', - 0x15 => '^U', - 0x16 => '^V', - 0x17 => '^W', - 0x18 => '^X', - 0x19 => '^Y', - 0x1A => '^Z', # C-z - 0x1B => '^[', # C-[ C-3 - 0x1C => '^\\', # C-\ - 0x1D => '^]', # C-] - 0x1E => '^^', # C-~ C-6 - 0x1F => '^_', # C-_ C-7 - 0x7F => '^?', # C-? C-8 - } - - NON_PRINTING_START = "\1" - NON_PRINTING_END = "\2" - CSI_REGEXP = /\e\[[\d;]*[ABCDEFGHJKSTfminsuhl]/ - OSC_REGEXP = /\e\]\d+(?:;[^;\a\e]+)*(?:\a|\e\\)/ - WIDTH_SCANNER = /\G(?:(#{NON_PRINTING_START})|(#{NON_PRINTING_END})|(#{CSI_REGEXP})|(#{OSC_REGEXP})|(\X))/o - - def self.escape_for_print(str) - str.chars.map! { |gr| - case gr - when -"\n" - gr - when -"\t" - -' ' - else - EscapedPairs[gr.ord] || gr - end - }.join - end - - def self.safe_encode(str, encoding) - # Reline only supports utf-8 convertible string. - converted = str.encode(encoding, invalid: :replace, undef: :replace) - return converted if str.encoding == Encoding::UTF_8 || converted.encoding == Encoding::UTF_8 || converted.ascii_only? - - # This code is essentially doing the same thing as - # `str.encode(utf8, **replace_options).encode(encoding, **replace_options)` - # but also avoids unnecessary irreversible encoding conversion. - converted.gsub(/\X/) do |c| - c.encode(Encoding::UTF_8) - c - rescue Encoding::UndefinedConversionError - '?' - end - end - - require 'reline/unicode/east_asian_width' - - def self.get_mbchar_width(mbchar) - ord = mbchar.ord - if ord <= 0x1F # in EscapedPairs - return 2 - elsif ord <= 0x7E # printable ASCII chars - return 1 - end - utf8_mbchar = mbchar.encode(Encoding::UTF_8) - ord = utf8_mbchar.ord - chunk_index = EastAsianWidth::CHUNK_LAST.bsearch_index { |o| ord <= o } - size = EastAsianWidth::CHUNK_WIDTH[chunk_index] - if size == -1 - Reline.ambiguous_width - elsif size == 1 && utf8_mbchar.size >= 2 - second_char_ord = utf8_mbchar[1].ord - # Halfwidth Dakuten Handakuten - # Only these two character has Letter Modifier category and can be combined in a single grapheme cluster - (second_char_ord == 0xFF9E || second_char_ord == 0xFF9F) ? 2 : 1 - else - size - end - end - - def self.calculate_width(str, allow_escape_code = false) - if allow_escape_code - width = 0 - rest = str.encode(Encoding::UTF_8) - in_zero_width = false - rest.scan(WIDTH_SCANNER) do |non_printing_start, non_printing_end, csi, osc, gc| - case - when non_printing_start - in_zero_width = true - when non_printing_end - in_zero_width = false - when csi, osc - when gc - unless in_zero_width - width += get_mbchar_width(gc) - end - end - end - width - else - str.encode(Encoding::UTF_8).grapheme_clusters.inject(0) { |w, gc| - w + get_mbchar_width(gc) - } - end - end - - # This method is used by IRB - def self.split_by_width(str, max_width) - lines = split_line_by_width(str, max_width) - [lines, lines.size] - end - - def self.split_line_by_width(str, max_width, encoding = str.encoding, offset: 0) - lines = [String.new(encoding: encoding)] - width = offset - rest = str.encode(Encoding::UTF_8) - in_zero_width = false - seq = String.new(encoding: encoding) - rest.scan(WIDTH_SCANNER) do |non_printing_start, non_printing_end, csi, osc, gc| - case - when non_printing_start - in_zero_width = true - when non_printing_end - in_zero_width = false - when csi - lines.last << 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 unless in_zero_width - when gc - unless in_zero_width - mbchar_width = get_mbchar_width(gc) - if (width += mbchar_width) > max_width - width = mbchar_width - lines << seq.dup - end - end - lines.last << gc - end - end - # The cursor moves to next line in first - if width == max_width - lines << String.new(encoding: encoding) - end - lines - end - - def self.strip_non_printing_start_end(prompt) - prompt.gsub(/\x01([^\x02]*)(?:\x02|\z)/) { $1 } - end - - # 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 - when non_printing_end - in_zero_width = false - 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 - # 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_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) - grapheme = line.byteslice(byte_pointer..-1).grapheme_clusters.first - grapheme ? grapheme.bytesize : 0 - end - - def self.get_prev_mbchar_size(line, byte_pointer) - if byte_pointer.zero? - 0 - else - grapheme = line.byteslice(0..(byte_pointer - 1)).grapheme_clusters.last - grapheme ? grapheme.bytesize : 0 - end - end - - def self.em_forward_word(line, byte_pointer) - gcs = line.byteslice(byte_pointer..).grapheme_clusters - nonwords = gcs.take_while { |c| !word_character?(c) } - words = gcs.drop(nonwords.size).take_while { |c| word_character?(c) } - nonwords.sum(&:bytesize) + words.sum(&:bytesize) - end - - def self.em_forward_word_with_capitalization(line, byte_pointer) - gcs = line.byteslice(byte_pointer..).grapheme_clusters - nonwords = gcs.take_while { |c| !word_character?(c) } - words = gcs.drop(nonwords.size).take_while { |c| word_character?(c) } - [nonwords.sum(&:bytesize) + words.sum(&:bytesize), nonwords.join + words.join.capitalize] - end - - def self.em_backward_word(line, byte_pointer) - gcs = line.byteslice(0, byte_pointer).grapheme_clusters.reverse - nonwords = gcs.take_while { |c| !word_character?(c) } - words = gcs.drop(nonwords.size).take_while { |c| word_character?(c) } - nonwords.sum(&:bytesize) + words.sum(&:bytesize) - end - - def self.em_big_backward_word(line, byte_pointer) - gcs = line.byteslice(0, byte_pointer).grapheme_clusters.reverse - spaces = gcs.take_while { |c| space_character?(c) } - nonspaces = gcs.drop(spaces.size).take_while { |c| !space_character?(c) } - spaces.sum(&:bytesize) + nonspaces.sum(&:bytesize) - end - - def self.ed_transpose_words(line, byte_pointer) - gcs = line.byteslice(0, byte_pointer).grapheme_clusters - pos = gcs.size - gcs += line.byteslice(byte_pointer..).grapheme_clusters - pos += 1 while pos < gcs.size && !word_character?(gcs[pos]) - if pos == gcs.size # 'aaa bbb [cursor] ' - pos -= 1 while pos > 0 && !word_character?(gcs[pos - 1]) - second_word_end = gcs.size - else # 'aaa [cursor]bbb' - pos += 1 while pos < gcs.size && word_character?(gcs[pos]) - second_word_end = pos - end - pos -= 1 while pos > 0 && word_character?(gcs[pos - 1]) - second_word_start = pos - pos -= 1 while pos > 0 && !word_character?(gcs[pos - 1]) - first_word_end = pos - pos -= 1 while pos > 0 && word_character?(gcs[pos - 1]) - first_word_start = pos - - [first_word_start, first_word_end, second_word_start, second_word_end].map do |idx| - gcs.take(idx).sum(&:bytesize) - end - end - - def self.vi_big_forward_word(line, byte_pointer) - gcs = line.byteslice(byte_pointer..).grapheme_clusters - nonspaces = gcs.take_while { |c| !space_character?(c) } - spaces = gcs.drop(nonspaces.size).take_while { |c| space_character?(c) } - nonspaces.sum(&:bytesize) + spaces.sum(&:bytesize) - end - - def self.vi_big_forward_end_word(line, byte_pointer) - gcs = line.byteslice(byte_pointer..).grapheme_clusters - first = gcs.shift(1) - spaces = gcs.take_while { |c| space_character?(c) } - nonspaces = gcs.drop(spaces.size).take_while { |c| !space_character?(c) } - matched = spaces + nonspaces - matched.pop - first.sum(&:bytesize) + matched.sum(&:bytesize) - end - - def self.vi_big_backward_word(line, byte_pointer) - gcs = line.byteslice(0, byte_pointer).grapheme_clusters.reverse - spaces = gcs.take_while { |c| space_character?(c) } - nonspaces = gcs.drop(spaces.size).take_while { |c| !space_character?(c) } - spaces.sum(&:bytesize) + nonspaces.sum(&:bytesize) - end - - def self.vi_forward_word(line, byte_pointer, drop_terminate_spaces = false) - gcs = line.byteslice(byte_pointer..).grapheme_clusters - return 0 if gcs.empty? - - c = gcs.first - matched = - if word_character?(c) - gcs.take_while { |c| word_character?(c) } - elsif space_character?(c) - gcs.take_while { |c| space_character?(c) } - else - gcs.take_while { |c| !word_character?(c) && !space_character?(c) } - end - - return matched.sum(&:bytesize) if drop_terminate_spaces - - spaces = gcs.drop(matched.size).take_while { |c| space_character?(c) } - matched.sum(&:bytesize) + spaces.sum(&:bytesize) - end - - def self.vi_forward_end_word(line, byte_pointer) - gcs = line.byteslice(byte_pointer..).grapheme_clusters - return 0 if gcs.empty? - return gcs.first.bytesize if gcs.size == 1 - - start = gcs.shift - skips = [start] - if space_character?(start) || space_character?(gcs.first) - spaces = gcs.take_while { |c| space_character?(c) } - skips += spaces - gcs.shift(spaces.size) - end - start_with_word = word_character?(gcs.first) - matched = gcs.take_while { |c| start_with_word ? word_character?(c) : !word_character?(c) && !space_character?(c) } - matched.pop - skips.sum(&:bytesize) + matched.sum(&:bytesize) - end - - def self.vi_backward_word(line, byte_pointer) - gcs = line.byteslice(0, byte_pointer).grapheme_clusters.reverse - spaces = gcs.take_while { |c| space_character?(c) } - gcs.shift(spaces.size) - start_with_word = word_character?(gcs.first) - matched = gcs.take_while { |c| start_with_word ? word_character?(c) : !word_character?(c) && !space_character?(c) } - spaces.sum(&:bytesize) + matched.sum(&:bytesize) - end - - def self.common_prefix(list, ignore_case: false) - return '' if list.empty? - - common_prefix_gcs = list.first.grapheme_clusters - list.each do |item| - gcs = item.grapheme_clusters - common_prefix_gcs = common_prefix_gcs.take_while.with_index do |gc, i| - ignore_case ? gc.casecmp?(gcs[i]) : gc == gcs[i] - end - end - common_prefix_gcs.join - end - - def self.vi_first_print(line) - gcs = line.grapheme_clusters - spaces = gcs.take_while { |c| space_character?(c) } - spaces.sum(&:bytesize) - end - - def self.word_character?(s) - s.encode(Encoding::UTF_8).match?(/\p{Word}/) if s - rescue Encoding::UndefinedConversionError - false - end - - def self.space_character?(s) - s.match?(/\s/) if s - end -end diff --git a/lib/reline/unicode/east_asian_width.rb b/lib/reline/unicode/east_asian_width.rb deleted file mode 100644 index 9c5e42e239..0000000000 --- a/lib/reline/unicode/east_asian_width.rb +++ /dev/null @@ -1,1293 +0,0 @@ -class Reline::Unicode::EastAsianWidth - # This is based on EastAsianWidth.txt - # UNICODE_VERSION = '16.0.0' - - CHUNK_LAST, CHUNK_WIDTH = [ - [0x1f, 2], - [0x7e, 1], - [0x7f, 2], - [0xa0, 1], - [0xa1, -1], - [0xa3, 1], - [0xa4, -1], - [0xa6, 1], - [0xa8, -1], - [0xa9, 1], - [0xaa, -1], - [0xac, 1], - [0xae, -1], - [0xaf, 1], - [0xb4, -1], - [0xb5, 1], - [0xba, -1], - [0xbb, 1], - [0xbf, -1], - [0xc5, 1], - [0xc6, -1], - [0xcf, 1], - [0xd0, -1], - [0xd6, 1], - [0xd8, -1], - [0xdd, 1], - [0xe1, -1], - [0xe5, 1], - [0xe6, -1], - [0xe7, 1], - [0xea, -1], - [0xeb, 1], - [0xed, -1], - [0xef, 1], - [0xf0, -1], - [0xf1, 1], - [0xf3, -1], - [0xf6, 1], - [0xfa, -1], - [0xfb, 1], - [0xfc, -1], - [0xfd, 1], - [0xfe, -1], - [0x100, 1], - [0x101, -1], - [0x110, 1], - [0x111, -1], - [0x112, 1], - [0x113, -1], - [0x11a, 1], - [0x11b, -1], - [0x125, 1], - [0x127, -1], - [0x12a, 1], - [0x12b, -1], - [0x130, 1], - [0x133, -1], - [0x137, 1], - [0x138, -1], - [0x13e, 1], - [0x142, -1], - [0x143, 1], - [0x144, -1], - [0x147, 1], - [0x14b, -1], - [0x14c, 1], - [0x14d, -1], - [0x151, 1], - [0x153, -1], - [0x165, 1], - [0x167, -1], - [0x16a, 1], - [0x16b, -1], - [0x1cd, 1], - [0x1ce, -1], - [0x1cf, 1], - [0x1d0, -1], - [0x1d1, 1], - [0x1d2, -1], - [0x1d3, 1], - [0x1d4, -1], - [0x1d5, 1], - [0x1d6, -1], - [0x1d7, 1], - [0x1d8, -1], - [0x1d9, 1], - [0x1da, -1], - [0x1db, 1], - [0x1dc, -1], - [0x250, 1], - [0x251, -1], - [0x260, 1], - [0x261, -1], - [0x2c3, 1], - [0x2c4, -1], - [0x2c6, 1], - [0x2c7, -1], - [0x2c8, 1], - [0x2cb, -1], - [0x2cc, 1], - [0x2cd, -1], - [0x2cf, 1], - [0x2d0, -1], - [0x2d7, 1], - [0x2db, -1], - [0x2dc, 1], - [0x2dd, -1], - [0x2de, 1], - [0x2df, -1], - [0x2ff, 1], - [0x36f, 0], - [0x390, 1], - [0x3a1, -1], - [0x3a2, 1], - [0x3a9, -1], - [0x3b0, 1], - [0x3c1, -1], - [0x3c2, 1], - [0x3c9, -1], - [0x400, 1], - [0x401, -1], - [0x40f, 1], - [0x44f, -1], - [0x450, 1], - [0x451, -1], - [0x482, 1], - [0x487, 0], - [0x590, 1], - [0x5bd, 0], - [0x5be, 1], - [0x5bf, 0], - [0x5c0, 1], - [0x5c2, 0], - [0x5c3, 1], - [0x5c5, 0], - [0x5c6, 1], - [0x5c7, 0], - [0x60f, 1], - [0x61a, 0], - [0x64a, 1], - [0x65f, 0], - [0x66f, 1], - [0x670, 0], - [0x6d5, 1], - [0x6dc, 0], - [0x6de, 1], - [0x6e4, 0], - [0x6e6, 1], - [0x6e8, 0], - [0x6e9, 1], - [0x6ed, 0], - [0x710, 1], - [0x711, 0], - [0x72f, 1], - [0x74a, 0], - [0x7a5, 1], - [0x7b0, 0], - [0x7ea, 1], - [0x7f3, 0], - [0x7fc, 1], - [0x7fd, 0], - [0x815, 1], - [0x819, 0], - [0x81a, 1], - [0x823, 0], - [0x824, 1], - [0x827, 0], - [0x828, 1], - [0x82d, 0], - [0x858, 1], - [0x85b, 0], - [0x896, 1], - [0x89f, 0], - [0x8c9, 1], - [0x8e1, 0], - [0x8e2, 1], - [0x902, 0], - [0x939, 1], - [0x93a, 0], - [0x93b, 1], - [0x93c, 0], - [0x940, 1], - [0x948, 0], - [0x94c, 1], - [0x94d, 0], - [0x950, 1], - [0x957, 0], - [0x961, 1], - [0x963, 0], - [0x980, 1], - [0x981, 0], - [0x9bb, 1], - [0x9bc, 0], - [0x9c0, 1], - [0x9c4, 0], - [0x9cc, 1], - [0x9cd, 0], - [0x9e1, 1], - [0x9e3, 0], - [0x9fd, 1], - [0x9fe, 0], - [0xa00, 1], - [0xa02, 0], - [0xa3b, 1], - [0xa3c, 0], - [0xa40, 1], - [0xa42, 0], - [0xa46, 1], - [0xa48, 0], - [0xa4a, 1], - [0xa4d, 0], - [0xa50, 1], - [0xa51, 0], - [0xa6f, 1], - [0xa71, 0], - [0xa74, 1], - [0xa75, 0], - [0xa80, 1], - [0xa82, 0], - [0xabb, 1], - [0xabc, 0], - [0xac0, 1], - [0xac5, 0], - [0xac6, 1], - [0xac8, 0], - [0xacc, 1], - [0xacd, 0], - [0xae1, 1], - [0xae3, 0], - [0xaf9, 1], - [0xaff, 0], - [0xb00, 1], - [0xb01, 0], - [0xb3b, 1], - [0xb3c, 0], - [0xb3e, 1], - [0xb3f, 0], - [0xb40, 1], - [0xb44, 0], - [0xb4c, 1], - [0xb4d, 0], - [0xb54, 1], - [0xb56, 0], - [0xb61, 1], - [0xb63, 0], - [0xb81, 1], - [0xb82, 0], - [0xbbf, 1], - [0xbc0, 0], - [0xbcc, 1], - [0xbcd, 0], - [0xbff, 1], - [0xc00, 0], - [0xc03, 1], - [0xc04, 0], - [0xc3b, 1], - [0xc3c, 0], - [0xc3d, 1], - [0xc40, 0], - [0xc45, 1], - [0xc48, 0], - [0xc49, 1], - [0xc4d, 0], - [0xc54, 1], - [0xc56, 0], - [0xc61, 1], - [0xc63, 0], - [0xc80, 1], - [0xc81, 0], - [0xcbb, 1], - [0xcbc, 0], - [0xcbe, 1], - [0xcbf, 0], - [0xcc5, 1], - [0xcc6, 0], - [0xccb, 1], - [0xccd, 0], - [0xce1, 1], - [0xce3, 0], - [0xcff, 1], - [0xd01, 0], - [0xd3a, 1], - [0xd3c, 0], - [0xd40, 1], - [0xd44, 0], - [0xd4c, 1], - [0xd4d, 0], - [0xd61, 1], - [0xd63, 0], - [0xd80, 1], - [0xd81, 0], - [0xdc9, 1], - [0xdca, 0], - [0xdd1, 1], - [0xdd4, 0], - [0xdd5, 1], - [0xdd6, 0], - [0xe30, 1], - [0xe31, 0], - [0xe33, 1], - [0xe3a, 0], - [0xe46, 1], - [0xe4e, 0], - [0xeb0, 1], - [0xeb1, 0], - [0xeb3, 1], - [0xebc, 0], - [0xec7, 1], - [0xece, 0], - [0xf17, 1], - [0xf19, 0], - [0xf34, 1], - [0xf35, 0], - [0xf36, 1], - [0xf37, 0], - [0xf38, 1], - [0xf39, 0], - [0xf70, 1], - [0xf7e, 0], - [0xf7f, 1], - [0xf84, 0], - [0xf85, 1], - [0xf87, 0], - [0xf8c, 1], - [0xf97, 0], - [0xf98, 1], - [0xfbc, 0], - [0xfc5, 1], - [0xfc6, 0], - [0x102c, 1], - [0x1030, 0], - [0x1031, 1], - [0x1037, 0], - [0x1038, 1], - [0x103a, 0], - [0x103c, 1], - [0x103e, 0], - [0x1057, 1], - [0x1059, 0], - [0x105d, 1], - [0x1060, 0], - [0x1070, 1], - [0x1074, 0], - [0x1081, 1], - [0x1082, 0], - [0x1084, 1], - [0x1086, 0], - [0x108c, 1], - [0x108d, 0], - [0x109c, 1], - [0x109d, 0], - [0x10ff, 1], - [0x115f, 2], - [0x135c, 1], - [0x135f, 0], - [0x1711, 1], - [0x1714, 0], - [0x1731, 1], - [0x1733, 0], - [0x1751, 1], - [0x1753, 0], - [0x1771, 1], - [0x1773, 0], - [0x17b3, 1], - [0x17b5, 0], - [0x17b6, 1], - [0x17bd, 0], - [0x17c5, 1], - [0x17c6, 0], - [0x17c8, 1], - [0x17d3, 0], - [0x17dc, 1], - [0x17dd, 0], - [0x180a, 1], - [0x180d, 0], - [0x180e, 1], - [0x180f, 0], - [0x1884, 1], - [0x1886, 0], - [0x18a8, 1], - [0x18a9, 0], - [0x191f, 1], - [0x1922, 0], - [0x1926, 1], - [0x1928, 0], - [0x1931, 1], - [0x1932, 0], - [0x1938, 1], - [0x193b, 0], - [0x1a16, 1], - [0x1a18, 0], - [0x1a1a, 1], - [0x1a1b, 0], - [0x1a55, 1], - [0x1a56, 0], - [0x1a57, 1], - [0x1a5e, 0], - [0x1a5f, 1], - [0x1a60, 0], - [0x1a61, 1], - [0x1a62, 0], - [0x1a64, 1], - [0x1a6c, 0], - [0x1a72, 1], - [0x1a7c, 0], - [0x1a7e, 1], - [0x1a7f, 0], - [0x1aaf, 1], - [0x1abd, 0], - [0x1abe, 1], - [0x1ace, 0], - [0x1aff, 1], - [0x1b03, 0], - [0x1b33, 1], - [0x1b34, 0], - [0x1b35, 1], - [0x1b3a, 0], - [0x1b3b, 1], - [0x1b3c, 0], - [0x1b41, 1], - [0x1b42, 0], - [0x1b6a, 1], - [0x1b73, 0], - [0x1b7f, 1], - [0x1b81, 0], - [0x1ba1, 1], - [0x1ba5, 0], - [0x1ba7, 1], - [0x1ba9, 0], - [0x1baa, 1], - [0x1bad, 0], - [0x1be5, 1], - [0x1be6, 0], - [0x1be7, 1], - [0x1be9, 0], - [0x1bec, 1], - [0x1bed, 0], - [0x1bee, 1], - [0x1bf1, 0], - [0x1c2b, 1], - [0x1c33, 0], - [0x1c35, 1], - [0x1c37, 0], - [0x1ccf, 1], - [0x1cd2, 0], - [0x1cd3, 1], - [0x1ce0, 0], - [0x1ce1, 1], - [0x1ce8, 0], - [0x1cec, 1], - [0x1ced, 0], - [0x1cf3, 1], - [0x1cf4, 0], - [0x1cf7, 1], - [0x1cf9, 0], - [0x1dbf, 1], - [0x1dff, 0], - [0x200f, 1], - [0x2010, -1], - [0x2012, 1], - [0x2016, -1], - [0x2017, 1], - [0x2019, -1], - [0x201b, 1], - [0x201d, -1], - [0x201f, 1], - [0x2022, -1], - [0x2023, 1], - [0x2027, -1], - [0x202f, 1], - [0x2030, -1], - [0x2031, 1], - [0x2033, -1], - [0x2034, 1], - [0x2035, -1], - [0x203a, 1], - [0x203b, -1], - [0x203d, 1], - [0x203e, -1], - [0x2073, 1], - [0x2074, -1], - [0x207e, 1], - [0x207f, -1], - [0x2080, 1], - [0x2084, -1], - [0x20ab, 1], - [0x20ac, -1], - [0x20cf, 1], - [0x20dc, 0], - [0x20e0, 1], - [0x20e1, 0], - [0x20e4, 1], - [0x20f0, 0], - [0x2102, 1], - [0x2103, -1], - [0x2104, 1], - [0x2105, -1], - [0x2108, 1], - [0x2109, -1], - [0x2112, 1], - [0x2113, -1], - [0x2115, 1], - [0x2116, -1], - [0x2120, 1], - [0x2122, -1], - [0x2125, 1], - [0x2126, -1], - [0x212a, 1], - [0x212b, -1], - [0x2152, 1], - [0x2154, -1], - [0x215a, 1], - [0x215e, -1], - [0x215f, 1], - [0x216b, -1], - [0x216f, 1], - [0x2179, -1], - [0x2188, 1], - [0x2189, -1], - [0x218f, 1], - [0x2199, -1], - [0x21b7, 1], - [0x21b9, -1], - [0x21d1, 1], - [0x21d2, -1], - [0x21d3, 1], - [0x21d4, -1], - [0x21e6, 1], - [0x21e7, -1], - [0x21ff, 1], - [0x2200, -1], - [0x2201, 1], - [0x2203, -1], - [0x2206, 1], - [0x2208, -1], - [0x220a, 1], - [0x220b, -1], - [0x220e, 1], - [0x220f, -1], - [0x2210, 1], - [0x2211, -1], - [0x2214, 1], - [0x2215, -1], - [0x2219, 1], - [0x221a, -1], - [0x221c, 1], - [0x2220, -1], - [0x2222, 1], - [0x2223, -1], - [0x2224, 1], - [0x2225, -1], - [0x2226, 1], - [0x222c, -1], - [0x222d, 1], - [0x222e, -1], - [0x2233, 1], - [0x2237, -1], - [0x223b, 1], - [0x223d, -1], - [0x2247, 1], - [0x2248, -1], - [0x224b, 1], - [0x224c, -1], - [0x2251, 1], - [0x2252, -1], - [0x225f, 1], - [0x2261, -1], - [0x2263, 1], - [0x2267, -1], - [0x2269, 1], - [0x226b, -1], - [0x226d, 1], - [0x226f, -1], - [0x2281, 1], - [0x2283, -1], - [0x2285, 1], - [0x2287, -1], - [0x2294, 1], - [0x2295, -1], - [0x2298, 1], - [0x2299, -1], - [0x22a4, 1], - [0x22a5, -1], - [0x22be, 1], - [0x22bf, -1], - [0x2311, 1], - [0x2312, -1], - [0x2319, 1], - [0x231b, 2], - [0x2328, 1], - [0x232a, 2], - [0x23e8, 1], - [0x23ec, 2], - [0x23ef, 1], - [0x23f0, 2], - [0x23f2, 1], - [0x23f3, 2], - [0x245f, 1], - [0x24e9, -1], - [0x24ea, 1], - [0x254b, -1], - [0x254f, 1], - [0x2573, -1], - [0x257f, 1], - [0x258f, -1], - [0x2591, 1], - [0x2595, -1], - [0x259f, 1], - [0x25a1, -1], - [0x25a2, 1], - [0x25a9, -1], - [0x25b1, 1], - [0x25b3, -1], - [0x25b5, 1], - [0x25b7, -1], - [0x25bb, 1], - [0x25bd, -1], - [0x25bf, 1], - [0x25c1, -1], - [0x25c5, 1], - [0x25c8, -1], - [0x25ca, 1], - [0x25cb, -1], - [0x25cd, 1], - [0x25d1, -1], - [0x25e1, 1], - [0x25e5, -1], - [0x25ee, 1], - [0x25ef, -1], - [0x25fc, 1], - [0x25fe, 2], - [0x2604, 1], - [0x2606, -1], - [0x2608, 1], - [0x2609, -1], - [0x260d, 1], - [0x260f, -1], - [0x2613, 1], - [0x2615, 2], - [0x261b, 1], - [0x261c, -1], - [0x261d, 1], - [0x261e, -1], - [0x262f, 1], - [0x2637, 2], - [0x263f, 1], - [0x2640, -1], - [0x2641, 1], - [0x2642, -1], - [0x2647, 1], - [0x2653, 2], - [0x265f, 1], - [0x2661, -1], - [0x2662, 1], - [0x2665, -1], - [0x2666, 1], - [0x266a, -1], - [0x266b, 1], - [0x266d, -1], - [0x266e, 1], - [0x266f, -1], - [0x267e, 1], - [0x267f, 2], - [0x2689, 1], - [0x268f, 2], - [0x2692, 1], - [0x2693, 2], - [0x269d, 1], - [0x269f, -1], - [0x26a0, 1], - [0x26a1, 2], - [0x26a9, 1], - [0x26ab, 2], - [0x26bc, 1], - [0x26be, 2], - [0x26bf, -1], - [0x26c3, 1], - [0x26c5, 2], - [0x26cd, -1], - [0x26ce, 2], - [0x26d3, -1], - [0x26d4, 2], - [0x26e1, -1], - [0x26e2, 1], - [0x26e3, -1], - [0x26e7, 1], - [0x26e9, -1], - [0x26ea, 2], - [0x26f1, -1], - [0x26f3, 2], - [0x26f4, -1], - [0x26f5, 2], - [0x26f9, -1], - [0x26fa, 2], - [0x26fc, -1], - [0x26fd, 2], - [0x26ff, -1], - [0x2704, 1], - [0x2705, 2], - [0x2709, 1], - [0x270b, 2], - [0x2727, 1], - [0x2728, 2], - [0x273c, 1], - [0x273d, -1], - [0x274b, 1], - [0x274c, 2], - [0x274d, 1], - [0x274e, 2], - [0x2752, 1], - [0x2755, 2], - [0x2756, 1], - [0x2757, 2], - [0x2775, 1], - [0x277f, -1], - [0x2794, 1], - [0x2797, 2], - [0x27af, 1], - [0x27b0, 2], - [0x27be, 1], - [0x27bf, 2], - [0x2b1a, 1], - [0x2b1c, 2], - [0x2b4f, 1], - [0x2b50, 2], - [0x2b54, 1], - [0x2b55, 2], - [0x2b59, -1], - [0x2cee, 1], - [0x2cf1, 0], - [0x2d7e, 1], - [0x2d7f, 0], - [0x2ddf, 1], - [0x2dff, 0], - [0x2e7f, 1], - [0x2e99, 2], - [0x2e9a, 1], - [0x2ef3, 2], - [0x2eff, 1], - [0x2fd5, 2], - [0x2fef, 1], - [0x3029, 2], - [0x302d, 0], - [0x303e, 2], - [0x3040, 1], - [0x3096, 2], - [0x3098, 1], - [0x309a, 0], - [0x30ff, 2], - [0x3104, 1], - [0x312f, 2], - [0x3130, 1], - [0x318e, 2], - [0x318f, 1], - [0x31e5, 2], - [0x31ee, 1], - [0x321e, 2], - [0x321f, 1], - [0x3247, 2], - [0x324f, -1], - [0xa48c, 2], - [0xa48f, 1], - [0xa4c6, 2], - [0xa66e, 1], - [0xa66f, 0], - [0xa673, 1], - [0xa67d, 0], - [0xa69d, 1], - [0xa69f, 0], - [0xa6ef, 1], - [0xa6f1, 0], - [0xa801, 1], - [0xa802, 0], - [0xa805, 1], - [0xa806, 0], - [0xa80a, 1], - [0xa80b, 0], - [0xa824, 1], - [0xa826, 0], - [0xa82b, 1], - [0xa82c, 0], - [0xa8c3, 1], - [0xa8c5, 0], - [0xa8df, 1], - [0xa8f1, 0], - [0xa8fe, 1], - [0xa8ff, 0], - [0xa925, 1], - [0xa92d, 0], - [0xa946, 1], - [0xa951, 0], - [0xa95f, 1], - [0xa97c, 2], - [0xa97f, 1], - [0xa982, 0], - [0xa9b2, 1], - [0xa9b3, 0], - [0xa9b5, 1], - [0xa9b9, 0], - [0xa9bb, 1], - [0xa9bd, 0], - [0xa9e4, 1], - [0xa9e5, 0], - [0xaa28, 1], - [0xaa2e, 0], - [0xaa30, 1], - [0xaa32, 0], - [0xaa34, 1], - [0xaa36, 0], - [0xaa42, 1], - [0xaa43, 0], - [0xaa4b, 1], - [0xaa4c, 0], - [0xaa7b, 1], - [0xaa7c, 0], - [0xaaaf, 1], - [0xaab0, 0], - [0xaab1, 1], - [0xaab4, 0], - [0xaab6, 1], - [0xaab8, 0], - [0xaabd, 1], - [0xaabf, 0], - [0xaac0, 1], - [0xaac1, 0], - [0xaaeb, 1], - [0xaaed, 0], - [0xaaf5, 1], - [0xaaf6, 0], - [0xabe4, 1], - [0xabe5, 0], - [0xabe7, 1], - [0xabe8, 0], - [0xabec, 1], - [0xabed, 0], - [0xabff, 1], - [0xd7a3, 2], - [0xdfff, 1], - [0xf8ff, -1], - [0xfaff, 2], - [0xfb1d, 1], - [0xfb1e, 0], - [0xfdff, 1], - [0xfe0f, 0], - [0xfe19, 2], - [0xfe1f, 1], - [0xfe2f, 0], - [0xfe52, 2], - [0xfe53, 1], - [0xfe66, 2], - [0xfe67, 1], - [0xfe6b, 2], - [0xff00, 1], - [0xff60, 2], - [0xffdf, 1], - [0xffe6, 2], - [0xfffc, 1], - [0xfffd, -1], - [0x101fc, 1], - [0x101fd, 0], - [0x102df, 1], - [0x102e0, 0], - [0x10375, 1], - [0x1037a, 0], - [0x10a00, 1], - [0x10a03, 0], - [0x10a04, 1], - [0x10a06, 0], - [0x10a0b, 1], - [0x10a0f, 0], - [0x10a37, 1], - [0x10a3a, 0], - [0x10a3e, 1], - [0x10a3f, 0], - [0x10ae4, 1], - [0x10ae6, 0], - [0x10d23, 1], - [0x10d27, 0], - [0x10d68, 1], - [0x10d6d, 0], - [0x10eaa, 1], - [0x10eac, 0], - [0x10efb, 1], - [0x10eff, 0], - [0x10f45, 1], - [0x10f50, 0], - [0x10f81, 1], - [0x10f85, 0], - [0x11000, 1], - [0x11001, 0], - [0x11037, 1], - [0x11046, 0], - [0x1106f, 1], - [0x11070, 0], - [0x11072, 1], - [0x11074, 0], - [0x1107e, 1], - [0x11081, 0], - [0x110b2, 1], - [0x110b6, 0], - [0x110b8, 1], - [0x110ba, 0], - [0x110c1, 1], - [0x110c2, 0], - [0x110ff, 1], - [0x11102, 0], - [0x11126, 1], - [0x1112b, 0], - [0x1112c, 1], - [0x11134, 0], - [0x11172, 1], - [0x11173, 0], - [0x1117f, 1], - [0x11181, 0], - [0x111b5, 1], - [0x111be, 0], - [0x111c8, 1], - [0x111cc, 0], - [0x111ce, 1], - [0x111cf, 0], - [0x1122e, 1], - [0x11231, 0], - [0x11233, 1], - [0x11234, 0], - [0x11235, 1], - [0x11237, 0], - [0x1123d, 1], - [0x1123e, 0], - [0x11240, 1], - [0x11241, 0], - [0x112de, 1], - [0x112df, 0], - [0x112e2, 1], - [0x112ea, 0], - [0x112ff, 1], - [0x11301, 0], - [0x1133a, 1], - [0x1133c, 0], - [0x1133f, 1], - [0x11340, 0], - [0x11365, 1], - [0x1136c, 0], - [0x1136f, 1], - [0x11374, 0], - [0x113ba, 1], - [0x113c0, 0], - [0x113cd, 1], - [0x113ce, 0], - [0x113cf, 1], - [0x113d0, 0], - [0x113d1, 1], - [0x113d2, 0], - [0x113e0, 1], - [0x113e2, 0], - [0x11437, 1], - [0x1143f, 0], - [0x11441, 1], - [0x11444, 0], - [0x11445, 1], - [0x11446, 0], - [0x1145d, 1], - [0x1145e, 0], - [0x114b2, 1], - [0x114b8, 0], - [0x114b9, 1], - [0x114ba, 0], - [0x114be, 1], - [0x114c0, 0], - [0x114c1, 1], - [0x114c3, 0], - [0x115b1, 1], - [0x115b5, 0], - [0x115bb, 1], - [0x115bd, 0], - [0x115be, 1], - [0x115c0, 0], - [0x115db, 1], - [0x115dd, 0], - [0x11632, 1], - [0x1163a, 0], - [0x1163c, 1], - [0x1163d, 0], - [0x1163e, 1], - [0x11640, 0], - [0x116aa, 1], - [0x116ab, 0], - [0x116ac, 1], - [0x116ad, 0], - [0x116af, 1], - [0x116b5, 0], - [0x116b6, 1], - [0x116b7, 0], - [0x1171c, 1], - [0x1171d, 0], - [0x1171e, 1], - [0x1171f, 0], - [0x11721, 1], - [0x11725, 0], - [0x11726, 1], - [0x1172b, 0], - [0x1182e, 1], - [0x11837, 0], - [0x11838, 1], - [0x1183a, 0], - [0x1193a, 1], - [0x1193c, 0], - [0x1193d, 1], - [0x1193e, 0], - [0x11942, 1], - [0x11943, 0], - [0x119d3, 1], - [0x119d7, 0], - [0x119d9, 1], - [0x119db, 0], - [0x119df, 1], - [0x119e0, 0], - [0x11a00, 1], - [0x11a0a, 0], - [0x11a32, 1], - [0x11a38, 0], - [0x11a3a, 1], - [0x11a3e, 0], - [0x11a46, 1], - [0x11a47, 0], - [0x11a50, 1], - [0x11a56, 0], - [0x11a58, 1], - [0x11a5b, 0], - [0x11a89, 1], - [0x11a96, 0], - [0x11a97, 1], - [0x11a99, 0], - [0x11c2f, 1], - [0x11c36, 0], - [0x11c37, 1], - [0x11c3d, 0], - [0x11c3e, 1], - [0x11c3f, 0], - [0x11c91, 1], - [0x11ca7, 0], - [0x11ca9, 1], - [0x11cb0, 0], - [0x11cb1, 1], - [0x11cb3, 0], - [0x11cb4, 1], - [0x11cb6, 0], - [0x11d30, 1], - [0x11d36, 0], - [0x11d39, 1], - [0x11d3a, 0], - [0x11d3b, 1], - [0x11d3d, 0], - [0x11d3e, 1], - [0x11d45, 0], - [0x11d46, 1], - [0x11d47, 0], - [0x11d8f, 1], - [0x11d91, 0], - [0x11d94, 1], - [0x11d95, 0], - [0x11d96, 1], - [0x11d97, 0], - [0x11ef2, 1], - [0x11ef4, 0], - [0x11eff, 1], - [0x11f01, 0], - [0x11f35, 1], - [0x11f3a, 0], - [0x11f3f, 1], - [0x11f40, 0], - [0x11f41, 1], - [0x11f42, 0], - [0x11f59, 1], - [0x11f5a, 0], - [0x1343f, 1], - [0x13440, 0], - [0x13446, 1], - [0x13455, 0], - [0x1611d, 1], - [0x16129, 0], - [0x1612c, 1], - [0x1612f, 0], - [0x16aef, 1], - [0x16af4, 0], - [0x16b2f, 1], - [0x16b36, 0], - [0x16f4e, 1], - [0x16f4f, 0], - [0x16f8e, 1], - [0x16f92, 0], - [0x16fdf, 1], - [0x16fe3, 2], - [0x16fe4, 0], - [0x16fef, 1], - [0x16ff1, 2], - [0x16fff, 1], - [0x187f7, 2], - [0x187ff, 1], - [0x18cd5, 2], - [0x18cfe, 1], - [0x18d08, 2], - [0x1afef, 1], - [0x1aff3, 2], - [0x1aff4, 1], - [0x1affb, 2], - [0x1affc, 1], - [0x1affe, 2], - [0x1afff, 1], - [0x1b122, 2], - [0x1b131, 1], - [0x1b132, 2], - [0x1b14f, 1], - [0x1b152, 2], - [0x1b154, 1], - [0x1b155, 2], - [0x1b163, 1], - [0x1b167, 2], - [0x1b16f, 1], - [0x1b2fb, 2], - [0x1bc9c, 1], - [0x1bc9e, 0], - [0x1ceff, 1], - [0x1cf2d, 0], - [0x1cf2f, 1], - [0x1cf46, 0], - [0x1d166, 1], - [0x1d169, 0], - [0x1d17a, 1], - [0x1d182, 0], - [0x1d184, 1], - [0x1d18b, 0], - [0x1d1a9, 1], - [0x1d1ad, 0], - [0x1d241, 1], - [0x1d244, 0], - [0x1d2ff, 1], - [0x1d356, 2], - [0x1d35f, 1], - [0x1d376, 2], - [0x1d9ff, 1], - [0x1da36, 0], - [0x1da3a, 1], - [0x1da6c, 0], - [0x1da74, 1], - [0x1da75, 0], - [0x1da83, 1], - [0x1da84, 0], - [0x1da9a, 1], - [0x1da9f, 0], - [0x1daa0, 1], - [0x1daaf, 0], - [0x1dfff, 1], - [0x1e006, 0], - [0x1e007, 1], - [0x1e018, 0], - [0x1e01a, 1], - [0x1e021, 0], - [0x1e022, 1], - [0x1e024, 0], - [0x1e025, 1], - [0x1e02a, 0], - [0x1e08e, 1], - [0x1e08f, 0], - [0x1e12f, 1], - [0x1e136, 0], - [0x1e2ad, 1], - [0x1e2ae, 0], - [0x1e2eb, 1], - [0x1e2ef, 0], - [0x1e4eb, 1], - [0x1e4ef, 0], - [0x1e5ed, 1], - [0x1e5ef, 0], - [0x1e8cf, 1], - [0x1e8d6, 0], - [0x1e943, 1], - [0x1e94a, 0], - [0x1f003, 1], - [0x1f004, 2], - [0x1f0ce, 1], - [0x1f0cf, 2], - [0x1f0ff, 1], - [0x1f10a, -1], - [0x1f10f, 1], - [0x1f12d, -1], - [0x1f12f, 1], - [0x1f169, -1], - [0x1f16f, 1], - [0x1f18d, -1], - [0x1f18e, 2], - [0x1f190, -1], - [0x1f19a, 2], - [0x1f1ac, -1], - [0x1f1ff, 1], - [0x1f202, 2], - [0x1f20f, 1], - [0x1f23b, 2], - [0x1f23f, 1], - [0x1f248, 2], - [0x1f24f, 1], - [0x1f251, 2], - [0x1f25f, 1], - [0x1f265, 2], - [0x1f2ff, 1], - [0x1f320, 2], - [0x1f32c, 1], - [0x1f335, 2], - [0x1f336, 1], - [0x1f37c, 2], - [0x1f37d, 1], - [0x1f393, 2], - [0x1f39f, 1], - [0x1f3ca, 2], - [0x1f3ce, 1], - [0x1f3d3, 2], - [0x1f3df, 1], - [0x1f3f0, 2], - [0x1f3f3, 1], - [0x1f3f4, 2], - [0x1f3f7, 1], - [0x1f43e, 2], - [0x1f43f, 1], - [0x1f440, 2], - [0x1f441, 1], - [0x1f4fc, 2], - [0x1f4fe, 1], - [0x1f53d, 2], - [0x1f54a, 1], - [0x1f54e, 2], - [0x1f54f, 1], - [0x1f567, 2], - [0x1f579, 1], - [0x1f57a, 2], - [0x1f594, 1], - [0x1f596, 2], - [0x1f5a3, 1], - [0x1f5a4, 2], - [0x1f5fa, 1], - [0x1f64f, 2], - [0x1f67f, 1], - [0x1f6c5, 2], - [0x1f6cb, 1], - [0x1f6cc, 2], - [0x1f6cf, 1], - [0x1f6d2, 2], - [0x1f6d4, 1], - [0x1f6d7, 2], - [0x1f6db, 1], - [0x1f6df, 2], - [0x1f6ea, 1], - [0x1f6ec, 2], - [0x1f6f3, 1], - [0x1f6fc, 2], - [0x1f7df, 1], - [0x1f7eb, 2], - [0x1f7ef, 1], - [0x1f7f0, 2], - [0x1f90b, 1], - [0x1f93a, 2], - [0x1f93b, 1], - [0x1f945, 2], - [0x1f946, 1], - [0x1f9ff, 2], - [0x1fa6f, 1], - [0x1fa7c, 2], - [0x1fa7f, 1], - [0x1fa89, 2], - [0x1fa8e, 1], - [0x1fac6, 2], - [0x1facd, 1], - [0x1fadc, 2], - [0x1fade, 1], - [0x1fae9, 2], - [0x1faef, 1], - [0x1faf8, 2], - [0x1ffff, 1], - [0x2fffd, 2], - [0x2ffff, 1], - [0x3fffd, 2], - [0xe00ff, 1], - [0xe01ef, 0], - [0xeffff, 1], - [0xffffd, -1], - [0xfffff, 1], - [0x10fffd, -1], - [0x7fffffff, 1] - ].transpose.map(&:freeze) -end diff --git a/lib/reline/version.rb b/lib/reline/version.rb deleted file mode 100644 index 1d0f0d80a1..0000000000 --- a/lib/reline/version.rb +++ /dev/null @@ -1,3 +0,0 @@ -module Reline - VERSION = '0.6.0' -end diff --git a/test/irb/command/test_cd.rb b/test/irb/command/test_cd.rb deleted file mode 100644 index 10f77f6691..0000000000 --- a/test/irb/command/test_cd.rb +++ /dev/null @@ -1,84 +0,0 @@ -require "tempfile" -require_relative "../helper" - -module TestIRB - class CDTest < IntegrationTestCase - def setup - super - - write_ruby <<~'RUBY' - class Foo - class Bar - def bar - "this is bar" - end - end - - def foo - "this is foo" - end - end - - class BO < BasicObject - def baz - "this is baz" - end - end - - binding.irb - RUBY - end - - def test_cd - out = run_ruby_file do - type "cd Foo" - type "ls" - type "cd Bar" - type "ls" - type "cd .." - type "exit" - end - - assert_match(/irb\(Foo\):002>/, out) - assert_match(/Foo#methods: foo/, out) - assert_match(/irb\(Foo::Bar\):004>/, out) - assert_match(/Bar#methods: bar/, out) - assert_match(/irb\(Foo\):006>/, out) - end - - def test_cd_basic_object_or_frozen - out = run_ruby_file do - type "cd BO.new" - type "cd 1" - type "cd Object.new.freeze" - type "exit" - end - - assert_match(/irb\(#<BO:.+\):002>/, out) - assert_match(/irb\(1\):003>/, out) - assert_match(/irb\(#<Object:.+\):004>/, out) - end - - def test_cd_moves_top_level_with_no_args - out = run_ruby_file do - type "cd Foo" - type "cd Bar" - type "cd" - type "exit" - end - - assert_match(/irb\(Foo::Bar\):003>/, out) - assert_match(/irb\(main\):004>/, out) - end - - def test_cd_with_error - out = run_ruby_file do - type "cd Baz" - type "exit" - end - - assert_match(/Error: uninitialized constant Baz/, out) - assert_match(/irb\(main\):002>/, out) # the context should not change - end - end -end diff --git a/test/irb/command/test_command_aliasing.rb b/test/irb/command/test_command_aliasing.rb deleted file mode 100644 index 4ecc88c0aa..0000000000 --- a/test/irb/command/test_command_aliasing.rb +++ /dev/null @@ -1,50 +0,0 @@ -# frozen_string_literal: true - -require "tempfile" -require_relative "../helper" - -module TestIRB - class CommandAliasingTest < IntegrationTestCase - def setup - super - write_rc <<~RUBY - IRB.conf[:COMMAND_ALIASES] = { - :c => :conf, # alias to helper method - :f => :foo - } - RUBY - - write_ruby <<~'RUBY' - binding.irb - RUBY - end - - def test_aliasing_to_helper_method_triggers_warning - out = run_ruby_file do - type "c" - type "exit" - end - assert_include(out, "Using command alias `c` for helper method `conf` is not supported.") - assert_not_include(out, "Maybe IRB bug!") - end - - def test_alias_to_non_existent_command_triggers_warning - message = "You're trying to use command alias `f` for command `foo`, but `foo` does not exist." - out = run_ruby_file do - type "f" - type "exit" - end - assert_include(out, message) - assert_not_include(out, "Maybe IRB bug!") - - # Local variables take precedence over command aliases - out = run_ruby_file do - type "f = 123" - type "f" - type "exit" - end - assert_not_include(out, message) - assert_not_include(out, "Maybe IRB bug!") - end - end -end diff --git a/test/irb/command/test_copy.rb b/test/irb/command/test_copy.rb deleted file mode 100644 index 505812a1de..0000000000 --- a/test/irb/command/test_copy.rb +++ /dev/null @@ -1,70 +0,0 @@ -# frozen_string_literal: true - -require 'irb' - -require_relative "../helper" - -module TestIRB - class CopyTest < IntegrationTestCase - def setup - super - @envs['IRB_COPY_COMMAND'] = "#{EnvUtil.rubybin} -e \"puts 'foo' + STDIN.read\"" - end - - def test_copy_with_pbcopy - write_ruby <<~'ruby' - class Answer - def initialize(answer) - @answer = answer - end - end - - binding.irb - ruby - - output = run_ruby_file do - type "copy Answer.new(42)" - type "exit" - end - - assert_match(/foo#<Answer:0x[0-9a-f]+ @answer=42/, output) - assert_match(/Copied to system clipboard/, output) - end - - # copy puts 5 should: - # - Print value to the console - # - Copy nil to clipboard, since that is what the puts call evaluates to - def test_copy_when_expression_has_side_effects - write_ruby <<~'ruby' - binding.irb - ruby - - output = run_ruby_file do - type "copy puts 42" - type "exit" - end - - assert_match(/^42\r\n/, output) - assert_match(/foonil/, output) - assert_match(/Copied to system clipboard/, output) - refute_match(/foo42/, output) - end - - def test_copy_when_copy_command_is_invalid - @envs['IRB_COPY_COMMAND'] = "lulz" - - write_ruby <<~'ruby' - binding.irb - ruby - - output = run_ruby_file do - type "copy 42" - type "exit" - end - - assert_match(/No such file or directory - lulz/, output) - assert_match(/Is IRB\.conf\[:COPY_COMMAND\] set to a bad value/, output) - refute_match(/Copied to system clipboard/, output) - end - end -end diff --git a/test/irb/command/test_custom_command.rb b/test/irb/command/test_custom_command.rb deleted file mode 100644 index 13f412c210..0000000000 --- a/test/irb/command/test_custom_command.rb +++ /dev/null @@ -1,194 +0,0 @@ -# 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 - - def test_command_name_local_variable - write_ruby <<~RUBY - require "irb/command" - - class FooBarCommand < IRB::Command::Base - category 'CommandTest' - description 'test' - def execute(arg) - puts "arg=\#{arg.inspect}" - end - end - - IRB::Command.register(:foo_bar, FooBarCommand) - - binding.irb - RUBY - - output = run_ruby_file do - type "binding.irb" - type "foo_bar == 1 || 1" - type "foo_bar =~ /2/ || 2" - type "exit" - type "binding.irb" - type "foo_bar = '3'; foo_bar" - type "foo_bar == 4 || '4'" - type "foo_bar =~ /5/ || '5'" - type "exit" - type "binding.irb" - type "foo_bar ||= '6'; foo_bar" - type "foo_bar == 7 || '7'" - type "foo_bar =~ /8/ || '8'" - type "exit" - type "exit" - end - - assert_include(output, 'arg="== 1 || 1"') - assert_include(output, 'arg="=~ /2/ || 2"') - assert_include(output, '=> "3"') - assert_include(output, '=> "4"') - assert_include(output, '=> "5"') - assert_include(output, '=> "6"') - assert_include(output, '=> "7"') - assert_include(output, '=> "8"') - end - end -end diff --git a/test/irb/command/test_disable_irb.rb b/test/irb/command/test_disable_irb.rb deleted file mode 100644 index 14a20043d5..0000000000 --- a/test/irb/command/test_disable_irb.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: false -require 'irb' - -require_relative "../helper" - -module TestIRB - class DisableIRBTest < IntegrationTestCase - def test_disable_irb_disable_further_irb_breakpoints - 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 "disable_irb" - end - - assert_match(/First line\r\n/, output) - assert_match(/Second line\r\n/, output) - assert_match(/Third line\r\n/, output) - assert_match(/Fourth line\r\n/, output) - end - end -end diff --git a/test/irb/command/test_force_exit.rb b/test/irb/command/test_force_exit.rb deleted file mode 100644 index 191a786872..0000000000 --- a/test/irb/command/test_force_exit.rb +++ /dev/null @@ -1,51 +0,0 @@ -# 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 deleted file mode 100644 index b34832b022..0000000000 --- a/test/irb/command/test_help.rb +++ /dev/null @@ -1,75 +0,0 @@ -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 deleted file mode 100644 index e313c0c5d2..0000000000 --- a/test/irb/command/test_multi_irb_commands.rb +++ /dev/null @@ -1,50 +0,0 @@ -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/command/test_show_source.rb b/test/irb/command/test_show_source.rb deleted file mode 100644 index a4227231e4..0000000000 --- a/test/irb/command/test_show_source.rb +++ /dev/null @@ -1,410 +0,0 @@ -# frozen_string_literal: false -require 'irb' - -require_relative "../helper" - -module TestIRB - class ShowSourceTest < IntegrationTestCase - def setup - super - - write_rc <<~'RUBY' - IRB.conf[:USE_PAGER] = false - RUBY - end - - def test_show_source - write_ruby <<~'RUBY' - binding.irb - RUBY - - out = run_ruby_file do - type "show_source IRB.conf" - type "exit" - end - - assert_match(%r[/irb\/init\.rb], out) - end - - def test_show_source_alias - write_ruby <<~'RUBY' - binding.irb - RUBY - - out = run_ruby_file do - type "$ IRB.conf" - type "exit" - end - - assert_match(%r[/irb\/init\.rb], out) - end - - def test_show_source_with_missing_signature - 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_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_with_eval_error - write_ruby <<~'RUBY' - binding.irb - RUBY - - out = run_ruby_file do - type "show_source raise(Exception).itself" - type "exit" - end - - assert_match(%r[Couldn't locate a definition for raise\(Exception\)\.itself], out) - end - - def test_show_source_string - write_ruby <<~'RUBY' - binding.irb - RUBY - - out = run_ruby_file do - type "show_source 'IRB.conf'" - type "exit" - end - - assert_match(%r[/irb\/init\.rb], out) - end - - def test_show_source_method_s - write_ruby <<~RUBY - class Baz - def foo - end - end - - class Bar < Baz - def foo - super - end - end - - binding.irb - RUBY - - out = run_ruby_file do - type "show_source Bar#foo -s" - type "exit" - end - - assert_match(%r[#{@ruby_file.to_path}:2\s+def foo\r\n end\r\n], out) - end - - def test_show_source_method_s_with_incorrect_signature - write_ruby <<~RUBY - class Baz - def foo - end - end - - class Bar < Baz - def foo - super - end - end - - binding.irb - RUBY - - out = run_ruby_file do - type "show_source Bar#fooo -s" - type "exit" - end - - assert_match(%r[Error: Couldn't locate a super definition for Bar#fooo], out) - end - - def test_show_source_private_method - write_ruby <<~RUBY - class Bar - private def foo - end - end - binding.irb - RUBY - - out = run_ruby_file do - type "show_source Bar#foo" - type "exit" - end - - assert_match(%r[#{@ruby_file.to_path}:2\s+private def foo\r\n end\r\n], out) - end - - def test_show_source_private_singleton_method - write_ruby <<~RUBY - class Bar - private def foo - end - end - binding.irb - RUBY - - out = run_ruby_file do - type "bar = Bar.new" - type "show_source bar.foo" - type "exit" - end - - assert_match(%r[#{@ruby_file.to_path}:2\s+private def foo\r\n end\r\n], out) - end - - def test_show_source_method_multiple_s - write_ruby <<~RUBY - class Baz - def foo - end - end - - class Bar < Baz - def foo - super - end - end - - class Bob < Bar - def foo - super - end - end - - binding.irb - RUBY - - out = run_ruby_file do - type "show_source Bob#foo -ss" - type "exit" - end - - assert_match(%r[#{@ruby_file.to_path}:2\s+def foo\r\n end\r\n], out) - end - - def test_show_source_method_no_instance_method - write_ruby <<~RUBY - class Baz - end - - class Bar < Baz - def foo - super - end - end - - binding.irb - RUBY - - out = run_ruby_file do - type "show_source Bar#foo -s" - type "exit" - end - - assert_match(%r[Error: Couldn't locate a super definition for Bar#foo], out) - end - - def test_show_source_method_exceeds_super_chain - write_ruby <<~RUBY - class Baz - def foo - end - end - - class Bar < Baz - def foo - super - end - end - - binding.irb - RUBY - - out = run_ruby_file do - type "show_source Bar#foo -ss" - type "exit" - end - - assert_match(%r[Error: Couldn't locate a super definition for Bar#foo], out) - end - - def test_show_source_method_accidental_characters - write_ruby <<~'RUBY' - class Baz - def foo - end - end - - class Bar < Baz - def foo - super - end - end - - binding.irb - RUBY - - out = run_ruby_file do - type "show_source Bar#foo -sddddd" - type "exit" - end - - assert_match(%r[#{@ruby_file.to_path}:2\s+def foo\r\n end], out) - end - - def test_show_source_receiver_super - write_ruby <<~RUBY - class Baz - def foo - end - end - - class Bar < Baz - def foo - super - end - end - - binding.irb - RUBY - - out = run_ruby_file do - type "bar = Bar.new" - type "show_source bar.foo -s" - type "exit" - end - - 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 deleted file mode 100644 index ea2c6ef16a..0000000000 --- a/test/irb/helper.rb +++ /dev/null @@ -1,234 +0,0 @@ -require "test/unit" -require "pathname" -require "rubygems" - -begin - require_relative "../lib/helper" - require_relative "../lib/envutil" -rescue LoadError # ruby/ruby defines helpers differently -end - -begin - require "pty" -rescue LoadError # some platforms don't support PTY -end - -module IRB - class InputMethod; end -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 - - def initialize(list = []) - @line_no = 0 - @list = list - end - - def gets - @list[@line_no]&.tap {@line_no += 1} - end - - def eof? - @line_no >= @list.size - end - - def encoding - Encoding.default_external - end - - def reset - @line_no = 0 - end - end - - def ruby_core? - !Pathname(__dir__).join("../../", "irb.gemspec").exist? - end - - def setup_envs(home:) - @backup_home = ENV["HOME"] - ENV["HOME"] = home - @backup_xdg_config_home = ENV.delete("XDG_CONFIG_HOME") - @backup_irbrc = ENV.delete("IRBRC") - end - - def teardown_envs - ENV["HOME"] = @backup_home - ENV["XDG_CONFIG_HOME"] = @backup_xdg_config_home - ENV["IRBRC"] = @backup_irbrc - end - - def save_encodings - @default_encoding = [Encoding.default_external, Encoding.default_internal] - @stdio_encodings = [STDIN, STDOUT, STDERR].map {|io| [io.external_encoding, io.internal_encoding] } - end - - def restore_encodings - EnvUtil.suppress_warning do - Encoding.default_external, Encoding.default_internal = *@default_encoding - [STDIN, STDOUT, STDERR].zip(@stdio_encodings) do |io, encs| - io.set_encoding(*encs) - end - end - end - - def without_rdoc(&block) - ::Kernel.send(:alias_method, :irb_original_require, :require) - - ::Kernel.define_method(:require) do |name| - raise LoadError, "cannot load such file -- rdoc (test)" if name.match?("rdoc") || name.match?(/^rdoc\/.*/) - ::Kernel.send(:irb_original_require, name) - end - - yield - ensure - EnvUtil.suppress_warning { - ::Kernel.send(:alias_method, :require, :irb_original_require) - ::Kernel.undef_method :irb_original_require - } - end - end - - class IntegrationTestCase < TestCase - LIB = File.expand_path("../../lib", __dir__) - TIMEOUT_SEC = 3 - - def setup - @envs = {} - @tmpfiles = [] - - unless defined?(PTY) - omit "Integration tests require PTY." - end - - if ruby_core? - omit "This test works only under ruby/irb" - end - - write_rc <<~RUBY - IRB.conf[:USE_PAGER] = false - RUBY - end - - def teardown - @tmpfiles.each do |tmpfile| - File.unlink(tmpfile) - end - end - - def run_ruby_file(&block) - cmd = [EnvUtil.rubybin, "-I", LIB, @ruby_file.to_path] - tmp_dir = Dir.mktmpdir - - @commands = [] - lines = [] - - yield - - # Test should not depend on user's irbrc file - @envs["HOME"] ||= tmp_dir - @envs["XDG_CONFIG_HOME"] ||= tmp_dir - @envs["IRBRC"] = nil unless @envs.key?("IRBRC") - - 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 - - # means the breakpoint is triggered - if line.match?(/binding\.irb/) - while command = @commands.shift - write.puts(command) - end - end - end - end - ensure - read.close - write.close - kill_safely(pid) - end - - lines.join - rescue Timeout::Error - message = <<~MSG - Test timedout. - - #{'=' * 30} OUTPUT #{'=' * 30} - #{lines.map { |l| " #{l}" }.join} - #{'=' * 27} END OF OUTPUT #{'=' * 27} - MSG - assert_block(message) { false } - ensure - FileUtils.remove_entry tmp_dir - end - - # read.gets could raise exceptions on some platforms - # https://github.com/ruby/ruby/blob/master/ext/pty/pty.c#L721-L728 - def safe_gets(read) - read.gets - rescue Errno::EIO - nil - end - - def kill_safely pid - return if wait_pid pid, TIMEOUT_SEC - - Process.kill :TERM, pid - return if wait_pid pid, 0.2 - - Process.kill :KILL, pid - Process.waitpid(pid) - rescue Errno::EPERM, Errno::ESRCH - end - - def wait_pid pid, sec - total_sec = 0.0 - wait_sec = 0.001 # 1ms - - while total_sec < sec - if Process.waitpid(pid, Process::WNOHANG) == pid - return true - end - sleep wait_sec - total_sec += wait_sec - wait_sec *= 2 - end - - false - rescue Errno::ECHILD - true - end - - def type(command) - @commands << command - end - - def write_ruby(program) - @ruby_file = Tempfile.create(%w{irbtest- .rb}) - @tmpfiles << @ruby_file - @ruby_file.write(program) - @ruby_file.close - end - - def write_rc(content) - # Append irbrc content if a tempfile for it already exists - if @irbrc - @irbrc = File.open(@irbrc, "a") - else - @irbrc = Tempfile.new('irbrc') - @tmpfiles << @irbrc - end - - @irbrc.write(content) - @irbrc.close - @envs['IRBRC'] = @irbrc.path - end - end -end diff --git a/test/irb/test_color.rb b/test/irb/test_color.rb deleted file mode 100644 index 5529e29042..0000000000 --- a/test/irb/test_color.rb +++ /dev/null @@ -1,275 +0,0 @@ -# frozen_string_literal: false -require 'irb/color' -require 'stringio' - -require_relative "helper" - -module TestIRB - class ColorTest < TestCase - CLEAR = "\e[0m" - BOLD = "\e[1m" - UNDERLINE = "\e[4m" - REVERSE = "\e[7m" - RED = "\e[31m" - GREEN = "\e[32m" - YELLOW = "\e[33m" - BLUE = "\e[34m" - MAGENTA = "\e[35m" - CYAN = "\e[36m" - - def setup - super - if IRB.respond_to?(:conf) - @colorize, IRB.conf[:USE_COLORIZE] = IRB.conf[:USE_COLORIZE], true - end - end - - def teardown - if instance_variable_defined?(:@colorize) - IRB.conf[:USE_COLORIZE] = @colorize - end - super - end - - def test_colorize - text = "text" - { - [:BOLD] => "#{BOLD}#{text}#{CLEAR}", - [:UNDERLINE] => "#{UNDERLINE}#{text}#{CLEAR}", - [:REVERSE] => "#{REVERSE}#{text}#{CLEAR}", - [:RED] => "#{RED}#{text}#{CLEAR}", - [:GREEN] => "#{GREEN}#{text}#{CLEAR}", - [:YELLOW] => "#{YELLOW}#{text}#{CLEAR}", - [:BLUE] => "#{BLUE}#{text}#{CLEAR}", - [:MAGENTA] => "#{MAGENTA}#{text}#{CLEAR}", - [:CYAN] => "#{CYAN}#{text}#{CLEAR}", - }.each do |seq, result| - assert_equal_with_term(result, text, seq: seq) - - assert_equal_with_term(text, text, seq: seq, tty: false) - assert_equal_with_term(text, text, seq: seq, colorable: false) - assert_equal_with_term(result, text, seq: seq, tty: false, colorable: true) - end - end - - def test_colorize_code - # Common behaviors. Warn parser error, but do not warn compile error. - tests = { - "1" => "#{BLUE}#{BOLD}1#{CLEAR}", - "2.3" => "#{MAGENTA}#{BOLD}2.3#{CLEAR}", - "7r" => "#{BLUE}#{BOLD}7r#{CLEAR}", - "8i" => "#{BLUE}#{BOLD}8i#{CLEAR}", - "['foo', :bar]" => "[#{RED}#{BOLD}'#{CLEAR}#{RED}foo#{CLEAR}#{RED}#{BOLD}'#{CLEAR}, #{YELLOW}:#{CLEAR}#{YELLOW}bar#{CLEAR}]", - "class A; end" => "#{GREEN}class#{CLEAR} #{BLUE}#{BOLD}#{UNDERLINE}A#{CLEAR}; #{GREEN}end#{CLEAR}", - "def self.foo; bar; end" => "#{GREEN}def#{CLEAR} #{CYAN}#{BOLD}self#{CLEAR}.#{BLUE}#{BOLD}foo#{CLEAR}; bar; #{GREEN}end#{CLEAR}", - 'erb = ERB.new("a#{nil}b", trim_mode: "-")' => "erb = #{BLUE}#{BOLD}#{UNDERLINE}ERB#{CLEAR}.new(#{RED}#{BOLD}\"#{CLEAR}#{RED}a#{CLEAR}#{RED}\#{#{CLEAR}#{CYAN}#{BOLD}nil#{CLEAR}#{RED}}#{CLEAR}#{RED}b#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}, #{MAGENTA}trim_mode:#{CLEAR} #{RED}#{BOLD}\"#{CLEAR}#{RED}-#{CLEAR}#{RED}#{BOLD}\"#{CLEAR})", - "# comment" => "#{BLUE}#{BOLD}# comment#{CLEAR}", - "def f;yield(hello);end" => "#{GREEN}def#{CLEAR} #{BLUE}#{BOLD}f#{CLEAR};#{GREEN}yield#{CLEAR}(hello);#{GREEN}end#{CLEAR}", - '"##@var]"' => "#{RED}#{BOLD}\"#{CLEAR}#{RED}\##{CLEAR}#{RED}\##{CLEAR}@var#{RED}]#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}", - '"foo#{a} #{b}"' => "#{RED}#{BOLD}\"#{CLEAR}#{RED}foo#{CLEAR}#{RED}\#{#{CLEAR}a#{RED}}#{CLEAR}#{RED} #{CLEAR}#{RED}\#{#{CLEAR}b#{RED}}#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}", - '/r#{e}g/' => "#{RED}#{BOLD}/#{CLEAR}#{RED}r#{CLEAR}#{RED}\#{#{CLEAR}e#{RED}}#{CLEAR}#{RED}g#{CLEAR}#{RED}#{BOLD}/#{CLEAR}", - "'a\nb'" => "#{RED}#{BOLD}'#{CLEAR}#{RED}a#{CLEAR}\n#{RED}b#{CLEAR}#{RED}#{BOLD}'#{CLEAR}", - "%[str]" => "#{RED}#{BOLD}%[#{CLEAR}#{RED}str#{CLEAR}#{RED}#{BOLD}]#{CLEAR}", - "%Q[str]" => "#{RED}#{BOLD}%Q[#{CLEAR}#{RED}str#{CLEAR}#{RED}#{BOLD}]#{CLEAR}", - "%q[str]" => "#{RED}#{BOLD}%q[#{CLEAR}#{RED}str#{CLEAR}#{RED}#{BOLD}]#{CLEAR}", - "%x[cmd]" => "#{RED}#{BOLD}%x[#{CLEAR}#{RED}cmd#{CLEAR}#{RED}#{BOLD}]#{CLEAR}", - "%r[reg]" => "#{RED}#{BOLD}%r[#{CLEAR}#{RED}reg#{CLEAR}#{RED}#{BOLD}]#{CLEAR}", - "%w[a b]" => "#{RED}#{BOLD}%w[#{CLEAR}#{RED}a#{CLEAR} #{RED}b#{CLEAR}#{RED}#{BOLD}]#{CLEAR}", - "%W[a b]" => "#{RED}#{BOLD}%W[#{CLEAR}#{RED}a#{CLEAR} #{RED}b#{CLEAR}#{RED}#{BOLD}]#{CLEAR}", - "%s[a b]" => "#{YELLOW}%s[#{CLEAR}#{YELLOW}a b#{CLEAR}#{YELLOW}]#{CLEAR}", - "%i[c d]" => "#{YELLOW}%i[#{CLEAR}#{YELLOW}c#{CLEAR}#{YELLOW} #{CLEAR}#{YELLOW}d#{CLEAR}#{YELLOW}]#{CLEAR}", - "%I[c d]" => "#{YELLOW}%I[#{CLEAR}#{YELLOW}c#{CLEAR}#{YELLOW} #{CLEAR}#{YELLOW}d#{CLEAR}#{YELLOW}]#{CLEAR}", - "{'a': 1}" => "{#{RED}#{BOLD}'#{CLEAR}#{RED}a#{CLEAR}#{RED}#{BOLD}':#{CLEAR} #{BLUE}#{BOLD}1#{CLEAR}}", - ":Struct" => "#{YELLOW}:#{CLEAR}#{YELLOW}Struct#{CLEAR}", - '"#{}"' => "#{RED}#{BOLD}\"#{CLEAR}#{RED}\#{#{CLEAR}#{RED}}#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}", - ':"a#{}b"' => "#{YELLOW}:\"#{CLEAR}#{YELLOW}a#{CLEAR}#{YELLOW}\#{#{CLEAR}#{YELLOW}}#{CLEAR}#{YELLOW}b#{CLEAR}#{YELLOW}\"#{CLEAR}", - ':"a#{ def b; end; \'c\' + "#{ :d }" }e"' => "#{YELLOW}:\"#{CLEAR}#{YELLOW}a#{CLEAR}#{YELLOW}\#{#{CLEAR} #{GREEN}def#{CLEAR} #{BLUE}#{BOLD}b#{CLEAR}; #{GREEN}end#{CLEAR}; #{RED}#{BOLD}'#{CLEAR}#{RED}c#{CLEAR}#{RED}#{BOLD}'#{CLEAR} + #{RED}#{BOLD}\"#{CLEAR}#{RED}\#{#{CLEAR} #{YELLOW}:#{CLEAR}#{YELLOW}d#{CLEAR} #{RED}}#{CLEAR}#{RED}#{BOLD}\"#{CLEAR} #{YELLOW}}#{CLEAR}#{YELLOW}e#{CLEAR}#{YELLOW}\"#{CLEAR}", - "[__FILE__, __LINE__, __ENCODING__]" => "[#{CYAN}#{BOLD}__FILE__#{CLEAR}, #{CYAN}#{BOLD}__LINE__#{CLEAR}, #{CYAN}#{BOLD}__ENCODING__#{CLEAR}]", - ":self" => "#{YELLOW}:#{CLEAR}#{YELLOW}self#{CLEAR}", - ":class" => "#{YELLOW}:#{CLEAR}#{YELLOW}class#{CLEAR}", - "[:end, 2]" => "[#{YELLOW}:#{CLEAR}#{YELLOW}end#{CLEAR}, #{BLUE}#{BOLD}2#{CLEAR}]", - "[:>, 3]" => "[#{YELLOW}:#{CLEAR}#{YELLOW}>#{CLEAR}, #{BLUE}#{BOLD}3#{CLEAR}]", - "[:`, 4]" => "[#{YELLOW}:#{CLEAR}#{YELLOW}`#{CLEAR}, #{BLUE}#{BOLD}4#{CLEAR}]", - ":Hello ? world : nil" => "#{YELLOW}:#{CLEAR}#{YELLOW}Hello#{CLEAR} ? world : #{CYAN}#{BOLD}nil#{CLEAR}", - 'raise "foo#{bar}baz"' => "raise #{RED}#{BOLD}\"#{CLEAR}#{RED}foo#{CLEAR}#{RED}\#{#{CLEAR}bar#{RED}}#{CLEAR}#{RED}baz#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}", - '["#{obj.inspect}"]' => "[#{RED}#{BOLD}\"#{CLEAR}#{RED}\#{#{CLEAR}obj.inspect#{RED}}#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}]", - 'URI.parse "#{}"' => "#{BLUE}#{BOLD}#{UNDERLINE}URI#{CLEAR}.parse #{RED}#{BOLD}\"#{CLEAR}#{RED}\#{#{CLEAR}#{RED}}#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}", - "begin\nrescue\nend" => "#{GREEN}begin#{CLEAR}\n#{GREEN}rescue#{CLEAR}\n#{GREEN}end#{CLEAR}", - "foo %w[bar]" => "foo #{RED}#{BOLD}%w[#{CLEAR}#{RED}bar#{CLEAR}#{RED}#{BOLD}]#{CLEAR}", - "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" => 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}", - "$&" => "#{GREEN}#{BOLD}$&#{CLEAR}", - "__END__" => "#{GREEN}__END__#{CLEAR}", - "foo\n__END__\nbar" => "foo\n#{GREEN}__END__#{CLEAR}\nbar", - "foo\n<<A\0\0bar\nA\nbaz" => "foo\n#{RED}<<A#{CLEAR}^@^@bar\n#{RED}A#{CLEAR}\nbaz", - "<<A+1\nA" => "#{RED}<<A#{CLEAR}+#{BLUE}#{BOLD}1#{CLEAR}\n#{RED}A#{CLEAR}", - } - - tests.merge!({ - "4.5.6" => "#{MAGENTA}#{BOLD}4.5#{CLEAR}#{RED}#{REVERSE}.6#{CLEAR}", - "\e[0m\n" => "#{RED}#{REVERSE}^[#{CLEAR}[#{BLUE}#{BOLD}0#{CLEAR}#{RED}#{REVERSE}m#{CLEAR}\n", - "<<EOS\nhere\nEOS" => "#{RED}<<EOS#{CLEAR}\n#{RED}here#{CLEAR}\n#{RED}EOS#{CLEAR}", - }) - - # specific to Ruby 3.0+ - if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.0.0') - tests.merge!({ - "[1]]]\u0013" => "[#{BLUE}#{BOLD}1#{CLEAR}]#{RED}#{REVERSE}]#{CLEAR}#{RED}#{REVERSE}]#{CLEAR}#{RED}#{REVERSE}^S#{CLEAR}", - }) - tests.merge!({ - "def req(true) end" => "#{GREEN}def#{CLEAR} #{BLUE}#{BOLD}req#{CLEAR}(#{RED}#{REVERSE}true#{CLEAR}) #{RED}#{REVERSE}end#{CLEAR}", - "nil = 1" => "#{RED}#{REVERSE}nil#{CLEAR} = #{BLUE}#{BOLD}1#{CLEAR}", - "alias $x $1" => "#{GREEN}alias#{CLEAR} #{GREEN}#{BOLD}$x#{CLEAR} #{RED}#{REVERSE}$1#{CLEAR}", - "class bad; end" => "#{GREEN}class#{CLEAR} #{RED}#{REVERSE}bad#{CLEAR}; #{GREEN}end#{CLEAR}", - "def req(@a) end" => "#{GREEN}def#{CLEAR} #{BLUE}#{BOLD}req#{CLEAR}(#{RED}#{REVERSE}@a#{CLEAR}) #{GREEN}end#{CLEAR}", - }) - if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.2.0') - tests.merge!({ - "def req(true) end" => "#{GREEN}def#{CLEAR} #{BLUE}#{BOLD}req#{CLEAR}(#{RED}#{REVERSE}true#{CLEAR}#{RED}#{REVERSE})#{CLEAR} #{RED}#{REVERSE}end#{CLEAR}", - }) - end - else - tests.merge!({ - "[1]]]\u0013" => "[#{BLUE}#{BOLD}1#{CLEAR}]#{RED}#{REVERSE}]#{CLEAR}]^S", - "def req(true) end" => "#{GREEN}def#{CLEAR} #{BLUE}#{BOLD}req#{CLEAR}(#{RED}#{REVERSE}true#{CLEAR}) end", - "nil = 1" => "#{CYAN}#{BOLD}nil#{CLEAR} = #{BLUE}#{BOLD}1#{CLEAR}", - "alias $x $1" => "#{GREEN}alias#{CLEAR} #{GREEN}#{BOLD}$x#{CLEAR} #{GREEN}#{BOLD}$1#{CLEAR}", - "class bad; end" => "#{GREEN}class#{CLEAR} bad; #{GREEN}end#{CLEAR}", - "def req(@a) end" => "#{GREEN}def#{CLEAR} #{BLUE}#{BOLD}req#{CLEAR}(@a) #{GREEN}end#{CLEAR}", - }) - end - - tests.each do |code, result| - assert_equal_with_term(result, code, complete: true) - assert_equal_with_term(result, code, complete: false) - - assert_equal_with_term(code, code, complete: true, tty: false) - assert_equal_with_term(code, code, complete: false, tty: false) - - assert_equal_with_term(code, code, complete: true, colorable: false) - - assert_equal_with_term(code, code, complete: false, colorable: false) - - assert_equal_with_term(result, code, complete: true, tty: false, colorable: true) - - assert_equal_with_term(result, code, complete: false, tty: false, colorable: true) - end - end - - def test_colorize_code_with_local_variables - code = "a /(b +1)/i" - result_without_lvars = "a #{RED}#{BOLD}/#{CLEAR}#{RED}(b +1)#{CLEAR}#{RED}#{BOLD}/i#{CLEAR}" - result_with_lvar = "a /(b #{BLUE}#{BOLD}+1#{CLEAR})/i" - result_with_lvars = "a /(b +#{BLUE}#{BOLD}1#{CLEAR})/i" - - assert_equal_with_term(result_without_lvars, code) - assert_equal_with_term(result_with_lvar, code, local_variables: ['a']) - assert_equal_with_term(result_with_lvars, code, local_variables: ['a', 'b']) - end - - def test_colorize_code_complete_true - # `complete: true` behaviors. Warn end-of-file. - { - "'foo' + 'bar" => "#{RED}#{BOLD}'#{CLEAR}#{RED}foo#{CLEAR}#{RED}#{BOLD}'#{CLEAR} + #{RED}#{BOLD}'#{CLEAR}#{RED}#{REVERSE}bar#{CLEAR}", - "('foo" => "(#{RED}#{BOLD}'#{CLEAR}#{RED}#{REVERSE}foo#{CLEAR}", - }.each do |code, result| - assert_equal_with_term(result, code, complete: true) - - assert_equal_with_term(code, code, complete: true, tty: false) - - assert_equal_with_term(code, code, complete: true, colorable: false) - - assert_equal_with_term(result, code, complete: true, tty: false, colorable: true) - end - end - - def test_colorize_code_complete_false - # `complete: false` behaviors. Do not warn end-of-file. - { - "'foo' + 'bar" => "#{RED}#{BOLD}'#{CLEAR}#{RED}foo#{CLEAR}#{RED}#{BOLD}'#{CLEAR} + #{RED}#{BOLD}'#{CLEAR}#{RED}bar#{CLEAR}", - "('foo" => "(#{RED}#{BOLD}'#{CLEAR}#{RED}foo#{CLEAR}", - }.each do |code, result| - assert_equal_with_term(result, code, complete: false) - - assert_equal_with_term(code, code, complete: false, tty: false) - - assert_equal_with_term(code, code, complete: false, colorable: false) - - assert_equal_with_term(result, code, complete: false, tty: false, colorable: true) - end - end - - def test_inspect_colorable - { - 1 => true, - 2.3 => true, - ['foo', :bar] => true, - (a = []; a << a; a) => false, - (h = {}; h[h] = h; h) => false, - { a: 4 } => true, - /reg/ => true, - (1..3) => true, - Object.new => false, - Struct => true, - Test => true, - Struct.new(:a) => false, - Struct.new(:a).new(1) => false, - }.each do |object, result| - assert_equal(result, IRB::Color.inspect_colorable?(object), "Case: inspect_colorable?(#{object.inspect})") - end - end - - private - - def with_term(tty: true) - stdout = $stdout - io = StringIO.new - def io.tty?; true; end if tty - $stdout = io - - env = ENV.to_h.dup - ENV['TERM'] = 'xterm-256color' - - yield - ensure - $stdout = stdout - ENV.replace(env) if env - end - - def assert_equal_with_term(result, code, seq: nil, tty: true, **opts) - actual = with_term(tty: tty) do - if seq - IRB::Color.colorize(code, seq, **opts) - else - IRB::Color.colorize_code(code, **opts) - end - end - message = -> { - args = [code.dump] - args << seq.inspect if seq - opts.each {|kwd, val| args << "#{kwd}: #{val}"} - "Case: colorize#{seq ? "" : "_code"}(#{args.join(', ')})\nResult: #{humanized_literal(actual)}" - } - assert_equal(result, actual, message) - end - - def humanized_literal(str) - str - .gsub(CLEAR, '@@@{CLEAR}') - .gsub(BOLD, '@@@{BOLD}') - .gsub(UNDERLINE, '@@@{UNDERLINE}') - .gsub(REVERSE, '@@@{REVERSE}') - .gsub(RED, '@@@{RED}') - .gsub(GREEN, '@@@{GREEN}') - .gsub(YELLOW, '@@@{YELLOW}') - .gsub(BLUE, '@@@{BLUE}') - .gsub(MAGENTA, '@@@{MAGENTA}') - .gsub(CYAN, '@@@{CYAN}') - .dump.gsub(/@@@/, '#') - end - end -end diff --git a/test/irb/test_color_printer.rb b/test/irb/test_color_printer.rb deleted file mode 100644 index 95d08e19e3..0000000000 --- a/test/irb/test_color_printer.rb +++ /dev/null @@ -1,82 +0,0 @@ -# frozen_string_literal: false -require 'irb/color_printer' -require 'stringio' - -require_relative "helper" - -module TestIRB - class ColorPrinterTest < TestCase - CLEAR = "\e[0m" - BOLD = "\e[1m" - RED = "\e[31m" - GREEN = "\e[32m" - BLUE = "\e[34m" - CYAN = "\e[36m" - - def setup - super - if IRB.respond_to?(:conf) - @colorize, IRB.conf[:USE_COLORIZE] = IRB.conf[:USE_COLORIZE], true - end - @get_screen_size = Reline.method(:get_screen_size) - Reline.instance_eval { undef :get_screen_size } - def Reline.get_screen_size - [36, 80] - end - end - - def teardown - Reline.instance_eval { undef :get_screen_size } - Reline.define_singleton_method(:get_screen_size, @get_screen_size) - if instance_variable_defined?(:@colorize) - IRB.conf[:USE_COLORIZE] = @colorize - end - super - end - - IRBTestColorPrinter = Struct.new(:a) - - def test_color_printer - { - 1 => "#{BLUE}#{BOLD}1#{CLEAR}\n", - "a\nb" => %[#{RED}#{BOLD}"#{CLEAR}#{RED}a\\nb#{CLEAR}#{RED}#{BOLD}"#{CLEAR}\n], - IRBTestColorPrinter.new('test') => "#{GREEN}#<struct TestIRB::ColorPrinterTest::IRBTestColorPrinter#{CLEAR} a#{GREEN}=#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}#{RED}test#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}#{GREEN}>#{CLEAR}\n", - Ripper::Lexer.new('1').scan => "[#{GREEN}#<Ripper::Lexer::Elem:#{CLEAR} on_int@1:0 END token: #{RED}#{BOLD}\"#{CLEAR}#{RED}1#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}#{GREEN}>#{CLEAR}]\n", - Class.new{define_method(:pretty_print){|q| q.text("[__FILE__, __LINE__, __ENCODING__]")}}.new => "[#{CYAN}#{BOLD}__FILE__#{CLEAR}, #{CYAN}#{BOLD}__LINE__#{CLEAR}, #{CYAN}#{BOLD}__ENCODING__#{CLEAR}]\n", - }.each do |object, result| - actual = with_term { IRB::ColorPrinter.pp(object, '') } - assert_equal(result, actual, "Case: IRB::ColorPrinter.pp(#{object.inspect}, '')") - end - end - - def test_colorization_disabled - { - 1 => "1\n", - "a\nb" => %["a\\nb"\n], - IRBTestColorPrinter.new('test') => "#<struct TestIRB::ColorPrinterTest::IRBTestColorPrinter a=\"test\">\n", - Ripper::Lexer.new('1').scan => "[#<Ripper::Lexer::Elem: on_int@1:0 END token: \"1\">]\n", - Class.new{define_method(:pretty_print){|q| q.text("[__FILE__, __LINE__, __ENCODING__]")}}.new => "[__FILE__, __LINE__, __ENCODING__]\n", - }.each do |object, result| - actual = with_term { IRB::ColorPrinter.pp(object, '', colorize: false) } - assert_equal(result, actual, "Case: IRB::ColorPrinter.pp(#{object.inspect}, '')") - end - end - - private - - def with_term - stdout = $stdout - io = StringIO.new - def io.tty?; true; end - $stdout = io - - env = ENV.to_h.dup - ENV['TERM'] = 'xterm-256color' - - yield - ensure - $stdout = stdout - ENV.replace(env) if env - end - end -end diff --git a/test/irb/test_command.rb b/test/irb/test_command.rb deleted file mode 100644 index ec2d1f92df..0000000000 --- a/test/irb/test_command.rb +++ /dev/null @@ -1,1001 +0,0 @@ -# frozen_string_literal: false -require "irb" - -require_relative "helper" - -module TestIRB - # In case when RDoc becomes a bundled gem, we may not be able to load it when running tests - # in ruby/ruby - HAS_RDOC = begin - require "rdoc" - true - rescue LoadError - false - end - - class CommandTestCase < TestCase - def setup - @pwd = Dir.pwd - @tmpdir = File.join(Dir.tmpdir, "test_reline_config_#{$$}") - begin - Dir.mkdir(@tmpdir) - rescue Errno::EEXIST - FileUtils.rm_rf(@tmpdir) - Dir.mkdir(@tmpdir) - end - Dir.chdir(@tmpdir) - setup_envs(home: @tmpdir) - 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 - - def teardown - teardown_envs - Dir.chdir(@pwd) - FileUtils.rm_rf(@tmpdir) - restore_encodings - end - - def execute_lines(*lines, conf: {}, main: self, irb_path: nil) - 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 - end - - class FrozenObjectTest < CommandTestCase - def test_calling_command_on_a_frozen_main - main = Object.new.freeze - - out, err = execute_lines( - "irb_info", - main: main - ) - assert_empty(err) - assert_match(/RUBY_PLATFORM/, out) - end - end - - class InfoTest < CommandTestCase - def setup - super - @locals_backup = ENV.delete("LANG"), ENV.delete("LC_ALL") - end - - def teardown - super - ENV["LANG"], ENV["LC_ALL"] = @locals_backup - end - - def test_irb_info_multiline - FileUtils.touch("#{@tmpdir}/.inputrc") - FileUtils.touch("#{@tmpdir}/.irbrc") - FileUtils.touch("#{@tmpdir}/_irbrc") - - out, err = execute_lines( - "irb_info", - conf: { USE_MULTILINE: true, USE_SINGLELINE: false } - ) - - expected = %r{ - Ruby\sversion:\s.+\n - IRB\sversion:\sirb\s.+\n - InputMethod:\sAbstract\sInputMethod\n - Completion: .+\n - \.irbrc\spaths:.*\.irbrc.*_irbrc\n - RUBY_PLATFORM:\s.+\n - East\sAsian\sAmbiguous\sWidth:\s\d\n - #{@is_win ? 'Code\spage:\s\d+\n' : ''} - }x - - assert_empty err - assert_match expected, out - end - - def test_irb_info_singleline - FileUtils.touch("#{@tmpdir}/.inputrc") - FileUtils.touch("#{@tmpdir}/.irbrc") - - out, err = execute_lines( - "irb_info", - conf: { USE_MULTILINE: false, USE_SINGLELINE: true } - ) - - expected = %r{ - Ruby\sversion:\s.+\n - IRB\sversion:\sirb\s.+\n - InputMethod:\sAbstract\sInputMethod\n - Completion: .+\n - \.irbrc\spaths:\s.+\n - RUBY_PLATFORM:\s.+\n - East\sAsian\sAmbiguous\sWidth:\s\d\n - #{@is_win ? 'Code\spage:\s\d+\n' : ''} - }x - - assert_empty err - assert_match expected, out - end - - def test_irb_info_multiline_without_rc_files - inputrc_backup = ENV["INPUTRC"] - ENV["INPUTRC"] = "unknown_inpurc" - ext_backup = IRB::IRBRC_EXT - IRB.__send__(:remove_const, :IRBRC_EXT) - IRB.const_set(:IRBRC_EXT, "unknown_ext") - - out, err = execute_lines( - "irb_info", - conf: { USE_MULTILINE: true, USE_SINGLELINE: false } - ) - - expected = %r{ - Ruby\sversion:\s.+\n - IRB\sversion:\sirb\s.+\n - InputMethod:\sAbstract\sInputMethod\n - Completion: .+\n - RUBY_PLATFORM:\s.+\n - East\sAsian\sAmbiguous\sWidth:\s\d\n - #{@is_win ? 'Code\spage:\s\d+\n' : ''} - }x - - assert_empty err - assert_match expected, out - ensure - ENV["INPUTRC"] = inputrc_backup - IRB.__send__(:remove_const, :IRBRC_EXT) - IRB.const_set(:IRBRC_EXT, ext_backup) - end - - def test_irb_info_singleline_without_rc_files - inputrc_backup = ENV["INPUTRC"] - ENV["INPUTRC"] = "unknown_inpurc" - ext_backup = IRB::IRBRC_EXT - IRB.__send__(:remove_const, :IRBRC_EXT) - IRB.const_set(:IRBRC_EXT, "unknown_ext") - - out, err = execute_lines( - "irb_info", - conf: { USE_MULTILINE: false, USE_SINGLELINE: true } - ) - - expected = %r{ - Ruby\sversion:\s.+\n - IRB\sversion:\sirb\s.+\n - InputMethod:\sAbstract\sInputMethod\n - Completion: .+\n - RUBY_PLATFORM:\s.+\n - East\sAsian\sAmbiguous\sWidth:\s\d\n - #{@is_win ? 'Code\spage:\s\d+\n' : ''} - }x - - assert_empty err - assert_match expected, out - ensure - ENV["INPUTRC"] = inputrc_backup - IRB.__send__(:remove_const, :IRBRC_EXT) - IRB.const_set(:IRBRC_EXT, ext_backup) - end - - def test_irb_info_lang - FileUtils.touch("#{@tmpdir}/.inputrc") - FileUtils.touch("#{@tmpdir}/.irbrc") - ENV["LANG"] = "ja_JP.UTF-8" - ENV["LC_ALL"] = "en_US.UTF-8" - - out, err = execute_lines( - "irb_info", - conf: { USE_MULTILINE: true, USE_SINGLELINE: false } - ) - - expected = %r{ - Ruby\sversion: .+\n - IRB\sversion:\sirb .+\n - InputMethod:\sAbstract\sInputMethod\n - Completion: .+\n - \.irbrc\spaths: .+\n - RUBY_PLATFORM: .+\n - LANG\senv:\sja_JP\.UTF-8\n - LC_ALL\senv:\sen_US\.UTF-8\n - East\sAsian\sAmbiguous\sWidth:\s\d\n - }x - - assert_empty err - assert_match expected, out - end - end - - class MeasureTest < CommandTestCase - def test_measure - conf = { - PROMPT: { - DEFAULT: { - PROMPT_I: '> ', - PROMPT_S: '> ', - PROMPT_C: '> ' - } - }, - PROMPT_MODE: :DEFAULT, - MEASURE: false - } - - c = Class.new(Object) - out, err = execute_lines( - "measure\n", - "3\n", - "measure :off\n", - "3\n", - "measure :on\n", - "3\n", - "measure :off\n", - "3\n", - conf: conf, - main: c - ) - - assert_empty err - assert_match(/\A(TIME is added\.\nprocessing time: .+\n=> 3\n=> 3\n){2}/, out) - assert_empty(c.class_variables) - end - - def test_measure_keeps_previous_value - conf = { - PROMPT: { - DEFAULT: { - PROMPT_I: '> ', - PROMPT_S: '> ', - PROMPT_C: '> ' - } - }, - PROMPT_MODE: :DEFAULT, - MEASURE: false - } - - c = Class.new(Object) - out, err = execute_lines( - "measure\n", - "3\n", - "_\n", - conf: conf, - main: c - ) - - assert_empty err - assert_match(/\ATIME is added\.\nprocessing time: .+\n=> 3\nprocessing time: .+\n=> 3/, out) - assert_empty(c.class_variables) - end - - def test_measure_enabled_by_rc - conf = { - PROMPT: { - DEFAULT: { - PROMPT_I: '> ', - PROMPT_S: '> ', - PROMPT_C: '> ' - } - }, - PROMPT_MODE: :DEFAULT, - MEASURE: true - } - - out, err = execute_lines( - "3\n", - "measure :off\n", - "3\n", - conf: conf, - ) - - assert_empty err - assert_match(/\Aprocessing time: .+\n=> 3\n=> 3\n/, out) - end - - def test_measure_enabled_by_rc_with_custom - measuring_proc = proc { |line, line_no, &block| - time = Time.now - result = block.() - puts 'custom processing time: %fs' % (Time.now - time) if IRB.conf[:MEASURE] - result - } - conf = { - PROMPT: { - DEFAULT: { - PROMPT_I: '> ', - PROMPT_S: '> ', - PROMPT_C: '> ' - } - }, - PROMPT_MODE: :DEFAULT, - MEASURE: true, - MEASURE_PROC: { CUSTOM: measuring_proc } - } - - out, err = execute_lines( - "3\n", - "measure :off\n", - "3\n", - conf: conf, - ) - assert_empty err - assert_match(/\Acustom processing time: .+\n=> 3\n=> 3\n/, out) - end - - def test_measure_with_custom - measuring_proc = proc { |line, line_no, &block| - time = Time.now - result = block.() - puts 'custom processing time: %fs' % (Time.now - time) if IRB.conf[:MEASURE] - result - } - conf = { - PROMPT: { - DEFAULT: { - PROMPT_I: '> ', - PROMPT_S: '> ', - PROMPT_C: '> ' - } - }, - PROMPT_MODE: :DEFAULT, - MEASURE: false, - MEASURE_PROC: { CUSTOM: measuring_proc } - } - out, err = execute_lines( - "3\n", - "measure\n", - "3\n", - "measure :off\n", - "3\n", - conf: conf - ) - - assert_empty err - assert_match(/\A=> 3\nCUSTOM is added\.\ncustom processing time: .+\n=> 3\n=> 3\n/, out) - end - - def test_measure_toggle - conf = { - PROMPT: { - DEFAULT: { - PROMPT_I: '> ', - PROMPT_S: '> ', - PROMPT_C: '> ' - } - }, - PROMPT_MODE: :DEFAULT, - MEASURE: false, - MEASURE_PROC: { - FOO: proc { |&block| puts 'foo'; block.call }, - BAR: proc { |&block| puts 'bar'; block.call } - } - } - out, err = execute_lines( - "measure :foo\n", - "1\n", - "measure :on, :bar\n", - "2\n", - "measure :off, :foo\n", - "3\n", - "measure :off, :bar\n", - "4\n", - conf: conf - ) - - assert_empty err - assert_match(/\AFOO is added\.\nfoo\n=> 1\nBAR is added\.\nbar\nfoo\n=> 2\nbar\n=> 3\n=> 4\n/, out) - end - - def test_measure_with_proc_warning - conf = { - PROMPT: { - DEFAULT: { - PROMPT_I: '> ', - PROMPT_S: '> ', - PROMPT_C: '> ' - } - }, - PROMPT_MODE: :DEFAULT, - MEASURE: false, - } - c = Class.new(Object) - out, err = execute_lines( - "3\n", - "measure do\n", - "3\n", - conf: conf, - main: c - ) - - assert_match(/to add custom measure/, err) - assert_match(/\A=> 3\n=> 3\n/, out) - assert_empty(c.class_variables) - end - end - - class IrbSourceTest < CommandTestCase - def test_irb_source - File.write("#{@tmpdir}/a.rb", "a = 'hi'\n") - out, err = execute_lines( - "a = 'bug17564'\n", - "a\n", - "irb_source '#{@tmpdir}/a.rb'\n", - "a\n", - ) - assert_empty err - assert_pattern_list([ - /=> "bug17564"\n/, - /=> "bug17564"\n/, - / => "hi"\n/, - / => "hi"\n/, - ], out) - end - - def test_irb_source_without_argument - out, err = execute_lines( - "irb_source\n", - ) - assert_empty err - assert_match(/Please specify the file name./, out) - end - end - - class IrbLoadTest < CommandTestCase - def test_irb_load - File.write("#{@tmpdir}/a.rb", "a = 'hi'\n") - out, err = execute_lines( - "a = 'bug17564'\n", - "a\n", - "irb_load '#{@tmpdir}/a.rb'\n", - "a\n", - ) - assert_empty err - assert_pattern_list([ - /=> "bug17564"\n/, - /=> "bug17564"\n/, - / => "hi"\n/, - / => "bug17564"\n/, - ], out) - end - - def test_irb_load_without_argument - out, err = execute_lines( - "irb_load\n", - ) - - assert_empty err - assert_match(/Please specify the file name./, out) - end - end - - class WorkspaceCommandTestCase < CommandTestCase - def setup - super - # create Foo under the test class's namespace so it doesn't pollute global namespace - self.class.class_eval <<~RUBY - class Foo; end - RUBY - end - end - - class CwwsTest < WorkspaceCommandTestCase - def test_cwws_returns_the_current_workspace_object - out, err = execute_lines( - "cwws" - ) - - assert_empty err - 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", - "self.class", - "popws", - "self.class" - ) - assert_empty err - - 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", - "self.singleton_class.ancestors" - ) - assert_empty err - assert_include(out, "IRB::ExtendCommandBundle") - end - - def test_pushws_prints_workspace_stack_when_no_arg_is_given - out, err = execute_lines( - "pushws", - ) - assert_empty err - assert_include(out, "[#<TestIRB::PushwsTe...>]") - end - - def test_pushws_without_argument_swaps_the_top_two_workspaces - out, err = execute_lines( - "pushws #{self.class}::Foo.new", - "self.class", - "pushws", - "self.class" - ) - assert_empty err - assert_match(/=> #{self.class}::Foo\n/, out) - assert_match(/=> #{self.class}\n$/, out) - end - end - - class WorkspacesTest < WorkspaceCommandTestCase - def test_workspaces_returns_the_stack_of_workspaces - out, err = execute_lines( - "pushws #{self.class}::Foo.new\n", - "workspaces", - ) - - assert_empty err - assert_match(/\[#<TestIRB::Workspac...>, #<TestIRB::Workspac...>\]\n/, out) - end - end - - class PopwsTest < WorkspaceCommandTestCase - def test_popws_replaces_the_current_workspace_with_the_previous_one - out, err = execute_lines( - "pushws Foo.new\n", - "popws\n", - "cwws\n", - "self.class", - ) - assert_empty err - assert_include(out, "=> #{self.class}") - end - - def test_popws_prints_help_message_if_the_workspace_is_empty - out, err = execute_lines( - "popws\n", - ) - assert_empty err - assert_match(/\[#<TestIRB::PopwsTes...>\]\n/, out) - end - end - - class ChwsTest < WorkspaceCommandTestCase - def test_chws_replaces_the_current_workspace - out, err = execute_lines( - "chws #{self.class}::Foo.new\n", - "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", - ) - assert_empty err - assert_include(out, "Current workspace: #{self}") - end - end - - class WhereamiTest < CommandTestCase - def test_whereami - out, err = execute_lines( - "whereami\n", - ) - assert_empty err - assert_match(/^From: .+ @ line \d+ :\n/, out) - end - - def test_whereami_alias - out, err = execute_lines( - "@\n", - ) - assert_empty err - assert_match(/^From: .+ @ line \d+ :\n/, out) - end - end - - class LsTest < CommandTestCase - def test_ls - out, err = execute_lines( - "class P\n", - " def m() end\n", - " def m2() end\n", - "end\n", - - "class C < P\n", - " def m1() end\n", - " def m2() end\n", - "end\n", - - "module M\n", - " def m1() end\n", - " def m3() end\n", - "end\n", - - "module M2\n", - " include M\n", - " def m4() end\n", - "end\n", - - "obj = C.new\n", - "obj.instance_variable_set(:@a, 1)\n", - "obj.extend M2\n", - "def obj.m5() end\n", - "ls obj\n", - ) - - assert_empty err - assert_match(/^instance variables:\s+@a\n/m, out) - assert_match(/P#methods:\s+m\n/m, out) - assert_match(/C#methods:\s+m2\n/m, out) - assert_match(/M#methods:\s+m1\s+m3\n/m, out) - assert_match(/M2#methods:\s+m4\n/m, out) - assert_match(/C.methods:\s+m5\n/m, out) - end - - def test_ls_class - out, err = execute_lines( - "module M1\n", - " def m2; end\n", - " def m3; end\n", - "end\n", - - "class C1\n", - " def m1; end\n", - " def m2; end\n", - "end\n", - - "class C2 < C1\n", - " include M1\n", - " def m3; end\n", - " def m4; end\n", - " def self.m3; end\n", - " def self.m5; end\n", - "end\n", - "ls C2" - ) - - assert_empty err - assert_match(/C2.methods:\s+m3\s+m5\n/, out) - assert_match(/C2#methods:\s+m3\s+m4\n.*M1#methods:\s+m2\n.*C1#methods:\s+m1\n/, out) - assert_not_match(/Module#methods/, out) - assert_not_match(/Class#methods/, out) - end - - def test_ls_module - out, err = execute_lines( - "module M1\n", - " def m1; end\n", - " def m2; end\n", - "end\n", - - "module M2\n", - " include M1\n", - " def m1; end\n", - " def m3; end\n", - " def self.m4; end\n", - "end\n", - "ls M2" - ) - - assert_empty err - assert_match(/M2\.methods:\s+m4\n/, out) - assert_match(/M2#methods:\s+m1\s+m3\n.*M1#methods:\s+m2\n/, out) - assert_not_match(/Module#methods/, out) - end - - def test_ls_instance - out, err = execute_lines( - "class Foo; def bar; end; end\n", - "ls Foo.new" - ) - - assert_empty err - assert_match(/Foo#methods:\s+bar/, out) - # don't duplicate - assert_not_match(/Foo#methods:\s+bar\n.*Foo#methods/, out) - end - - def test_ls_grep - out, err = execute_lines("ls 42\n") - assert_empty err - assert_match(/times/, out) - assert_match(/polar/, out) - - [ - "ls 42, grep: /times/\n", - "ls 42 -g times\n", - "ls 42 -G times\n", - ].each do |line| - out, err = execute_lines(line) - assert_empty err - assert_match(/times/, out) - assert_not_match(/polar/, out) - end - end - - def test_ls_grep_empty - out, err = execute_lines("ls\n") - assert_empty err - assert_match(/assert/, out) - assert_match(/refute/, out) - - [ - "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(/assert/, out) - assert_not_match(/refute/, out) - end - end - - def test_ls_with_eval_error - [ - "ls raise(Exception,'foo')\n", - "ls raise(Exception,'foo'), grep: /./\n", - "ls Integer, grep: raise(Exception,'foo')\n", - ].each do |line| - out, err = execute_lines(line) - assert_empty err - assert_match(/Exception: foo/, out) - assert_not_match(/Maybe IRB bug!/, out) - end - end - - def test_ls_with_no_singleton_class - out, err = execute_lines( - "ls 42", - ) - assert_empty err - assert_match(/Comparable#methods:\s+/, out) - assert_match(/Numeric#methods:\s+/, out) - assert_match(/Integer#methods:\s+/, out) - end - end - - class ShowDocTest < CommandTestCase - if HAS_RDOC - def test_show_doc - out, err = execute_lines("show_doc String#gsub") - - # 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_not_include err, "[Deprecation]" - 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/command/help.rb" } - end - - def test_ri - out, err = execute_lines("ri String#gsub") - - # 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_not_include err, "[Deprecation]" - assert(possible_rdoc_output.any? { |output| output.match?(out) }, "Expect the `ri` 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/command/help.rb" } - end - end - - def test_show_doc_without_rdoc - _, err = without_rdoc do - execute_lines("show_doc String#gsub") - end - - 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/command/help.rb" } - end - end - - class EditTest < CommandTestCase - def setup - @original_visual = ENV["VISUAL"] - @original_editor = ENV["EDITOR"] - # noop the command so nothing gets executed - ENV["VISUAL"] = ": code" - ENV["EDITOR"] = ": code2" - end - - def teardown - ENV["VISUAL"] = @original_visual - ENV["EDITOR"] = @original_editor - end - - def test_edit_without_arg - out, err = execute_lines( - "edit", - irb_path: __FILE__ - ) - - assert_empty err - assert_match("path: #{__FILE__}", out) - 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__}" - ) - - assert_empty err - assert_match("path: #{__FILE__}", out) - assert_match("command: ': code'", out) - end - - def test_edit_with_non_existing_path - out, err = execute_lines( - "edit test_cmd_non_existing_path.rb" - ) - - assert_empty err - assert_match(/Can not find file: test_cmd_non_existing_path\.rb/, out) - end - - def test_edit_with_constant - out, err = execute_lines( - "edit IRB::Irb" - ) - - assert_empty err - assert_match(/path: .*\/lib\/irb\.rb/, out) - assert_match("command: ': code'", out) - end - - def test_edit_with_class_method - out, err = execute_lines( - "edit IRB.start" - ) - - assert_empty err - assert_match(/path: .*\/lib\/irb\.rb/, out) - assert_match("command: ': code'", out) - end - - def test_edit_with_instance_method - out, err = execute_lines( - "edit IRB::Irb#run" - ) - - assert_empty err - assert_match(/path: .*\/lib\/irb\.rb/, out) - assert_match("command: ': code'", out) - end - - def test_edit_with_editor_env_var - ENV.delete("VISUAL") - - out, err = execute_lines( - "edit", - irb_path: __FILE__ - ) - - assert_empty err - assert_match("path: #{__FILE__}", out) - assert_match("command: ': code2'", out) - end - end - - class HistoryCmdTest < CommandTestCase - def teardown - TestInputMethod.send(:remove_const, "HISTORY") if defined?(TestInputMethod::HISTORY) - super - end - - def test_history - TestInputMethod.const_set("HISTORY", %w[foo bar baz]) - - out, err = without_rdoc do - execute_lines("history") - end - - assert_include(out, <<~EOF) - 2: baz - 1: bar - 0: foo - EOF - assert_empty err - end - - def test_multiline_history_with_truncation - TestInputMethod.const_set("HISTORY", ["foo", "bar", <<~INPUT]) - [].each do |x| - puts x - end - INPUT - - out, err = without_rdoc do - execute_lines("hist") - end - - assert_include(out, <<~EOF) - 2: [].each do |x| - puts x - ... - 1: bar - 0: foo - EOF - assert_empty err - end - - def test_history_grep - TestInputMethod.const_set("HISTORY", ["foo", "bar", <<~INPUT]) - [].each do |x| - puts x - end - INPUT - - out, err = without_rdoc do - execute_lines("hist -g each\n") - end - - assert_include(out, <<~EOF) - 2: [].each do |x| - puts x - ... - EOF - assert_not_include(out, <<~EOF) - foo - EOF - assert_not_include(out, <<~EOF) - bar - EOF - assert_empty err - end - - 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 deleted file mode 100644 index c9a0eafa3d..0000000000 --- a/test/irb/test_completion.rb +++ /dev/null @@ -1,317 +0,0 @@ -# frozen_string_literal: false -require "pathname" -require "irb" - -require_relative "helper" - -module TestIRB - class CompletionTest < TestCase - def completion_candidates(target, bind) - IRB::RegexpCompletor.new.completion_candidates('', target, '', bind: bind) - end - - def doc_namespace(target, bind) - IRB::RegexpCompletor.new.doc_namespace('', target, '', bind: bind) - end - - class CommandCompletionTest < CompletionTest - def test_command_completion - completor = IRB::RegexpCompletor.new - binding.eval("some_var = 1") - # completion for help command's argument should only include command names - assert_include(completor.completion_candidates('help ', 's', '', bind: binding), 'show_source') - assert_not_include(completor.completion_candidates('help ', 's', '', bind: binding), 'some_var') - - 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 MethodCompletionTest < CompletionTest - def test_complete_string - assert_include(completion_candidates("'foo'.up", binding), "'foo'.upcase") - # completing 'foo bar'.up - assert_include(completion_candidates("bar'.up", binding), "bar'.upcase") - assert_equal("String.upcase", doc_namespace("'foo'.upcase", binding)) - end - - def test_complete_regexp - assert_include(completion_candidates("/foo/.ma", binding), "/foo/.match") - # completing /foo bar/.ma - assert_include(completion_candidates("bar/.ma", binding), "bar/.match") - assert_equal("Regexp.match", doc_namespace("/foo/.match", binding)) - end - - def test_complete_array - assert_include(completion_candidates("[].an", binding), "[].any?") - assert_equal("Array.any?", doc_namespace("[].any?", binding)) - end - - def test_complete_hash_and_proc - # hash - assert_include(completion_candidates("{}.an", binding), "{}.any?") - assert_equal(["Hash.any?", "Proc.any?"], doc_namespace("{}.any?", binding)) - - # proc - assert_include(completion_candidates("{}.bin", binding), "{}.binding") - assert_equal(["Hash.binding", "Proc.binding"], doc_namespace("{}.binding", binding)) - end - - def test_complete_numeric - assert_include(completion_candidates("1.positi", binding), "1.positive?") - assert_equal("Integer.positive?", doc_namespace("1.positive?", binding)) - - assert_include(completion_candidates("1r.positi", binding), "1r.positive?") - assert_equal("Rational.positive?", doc_namespace("1r.positive?", binding)) - - assert_include(completion_candidates("0xFFFF.positi", binding), "0xFFFF.positive?") - assert_equal("Integer.positive?", doc_namespace("0xFFFF.positive?", binding)) - - assert_empty(completion_candidates("1i.positi", binding)) - end - - def test_complete_symbol - assert_include(completion_candidates(":foo.to_p", binding), ":foo.to_proc") - assert_equal("Symbol.to_proc", doc_namespace(":foo.to_proc", binding)) - end - - def test_complete_class - assert_include(completion_candidates("String.ne", binding), "String.new") - assert_equal("String.new", doc_namespace("String.new", binding)) - end - end - - class RequireComepletionTest < CompletionTest - def test_complete_require - candidates = IRB::RegexpCompletor.new.completion_candidates("require ", "'irb", "", bind: binding) - %w['irb/init 'irb/ruby-lex].each do |word| - assert_include candidates, word - end - # Test cache - candidates = IRB::RegexpCompletor.new.completion_candidates("require ", "'irb", "", bind: binding) - %w['irb/init 'irb/ruby-lex].each do |word| - assert_include candidates, word - end - # Test string completion not disturbed by require completion - candidates = IRB::RegexpCompletor.new.completion_candidates("'string ", "'.", "", bind: binding) - assert_include candidates, "'.upcase" - end - - def test_complete_require_with_pathname_in_load_path - temp_dir = Dir.mktmpdir - File.write(File.join(temp_dir, "foo.rb"), "test") - test_path = Pathname.new(temp_dir) - $LOAD_PATH << test_path - - candidates = IRB::RegexpCompletor.new.completion_candidates("require ", "'foo", "", bind: binding) - assert_include candidates, "'foo" - ensure - $LOAD_PATH.pop if test_path - FileUtils.remove_entry(temp_dir) if temp_dir - end - - def test_complete_require_with_string_convertable_in_load_path - temp_dir = Dir.mktmpdir - File.write(File.join(temp_dir, "foo.rb"), "test") - object = Object.new - object.define_singleton_method(:to_s) { temp_dir } - $LOAD_PATH << object - - candidates = IRB::RegexpCompletor.new.completion_candidates("require ", "'foo", "", bind: binding) - assert_include candidates, "'foo" - ensure - $LOAD_PATH.pop if object - FileUtils.remove_entry(temp_dir) if temp_dir - end - - def test_complete_require_with_malformed_object_in_load_path - object = Object.new - def object.to_s; raise; end - $LOAD_PATH << object - - assert_nothing_raised do - IRB::RegexpCompletor.new.completion_candidates("require ", "'foo", "", bind: binding) - end - ensure - $LOAD_PATH.pop if object - end - - def test_complete_require_library_name_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 - candidates = Dir.chdir(__dir__ + "/../..") do - IRB::RegexpCompletor.new.completion_candidates("require_relative ", "'lib/irb", "", bind: binding) - end - %w['lib/irb/init 'lib/irb/ruby-lex].each do |word| - assert_include candidates, word - end - # Test cache - candidates = Dir.chdir(__dir__ + "/../..") do - IRB::RegexpCompletor.new.completion_candidates("require_relative ", "'lib/irb", "", bind: binding) - end - %w['lib/irb/init 'lib/irb/ruby-lex].each do |word| - assert_include candidates, word - end - end - end - - class VariableCompletionTest < CompletionTest - def test_complete_variable - # Bug fix issues https://github.com/ruby/irb/issues/368 - # Variables other than `str_example` and `@str_example` are defined to ensure that irb completion does not cause unintended behavior - str_example = '' - @str_example = '' - private_methods = '' - methods = '' - global_variables = '' - local_variables = '' - instance_variables = '' - - # suppress "assigned but unused variable" warning - str_example.clear - @str_example.clear - private_methods.clear - methods.clear - global_variables.clear - local_variables.clear - instance_variables.clear - - assert_include(completion_candidates("str_examp", binding), "str_example") - assert_equal("String", doc_namespace("str_example", binding)) - assert_equal("String.to_s", doc_namespace("str_example.to_s", binding)) - - assert_include(completion_candidates("@str_examp", binding), "@str_example") - assert_equal("String", doc_namespace("@str_example", binding)) - assert_equal("String.to_s", doc_namespace("@str_example.to_s", binding)) - end - - def test_complete_sort_variables - xzy, xzy_1, xzy2 = '', '', '' - - xzy.clear - xzy_1.clear - xzy2.clear - - candidates = completion_candidates("xz", binding) - assert_equal(%w[xzy xzy2 xzy_1], candidates) - end - end - - class ConstantCompletionTest < CompletionTest - class Foo - B3 = 1 - B1 = 1 - B2 = 1 - end - - def test_complete_constants - assert_equal(["Foo"], completion_candidates("Fo", binding)) - assert_equal(["Foo::B1", "Foo::B2", "Foo::B3"], completion_candidates("Foo::B", binding)) - assert_equal(["Foo::B1.positive?"], completion_candidates("Foo::B1.pos", binding)) - - assert_equal(["::Forwardable"], completion_candidates("::Fo", binding)) - assert_equal("Forwardable", doc_namespace("::Forwardable", binding)) - 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 - rescue - end - symbols += [:aiueo, :"aiu eo"] - candidates = completion_candidates(":a", binding) - assert_include(candidates, ":aiueo") - assert_not_include(candidates, ":aiu eo") - assert_empty(completion_candidates(":irb_unknown_symbol_abcdefg", binding)) - # Do not complete empty symbol for performance reason - assert_empty(completion_candidates(":", binding)) - end - - def test_complete_invalid_three_colons - assert_empty(completion_candidates(":::A", binding)) - assert_empty(completion_candidates(":::", binding)) - end - - def test_complete_absolute_constants_with_special_characters - assert_empty(completion_candidates("::A:", binding)) - assert_empty(completion_candidates("::A.", binding)) - assert_empty(completion_candidates("::A(", binding)) - assert_empty(completion_candidates("::A)", binding)) - assert_empty(completion_candidates("::A[", binding)) - end - - def test_complete_reserved_words - candidates = completion_candidates("de", binding) - %w[def defined?].each do |word| - assert_include candidates, word - end - - candidates = completion_candidates("__", binding) - %w[__ENCODING__ __LINE__ __FILE__].each do |word| - assert_include candidates, word - end - end - - def test_complete_methods - obj = Object.new - obj.singleton_class.class_eval { - def public_hoge; end - private def private_hoge; end - - # Support for overriding #methods etc. - def methods; end - def private_methods; end - def global_variables; end - def local_variables; end - def instance_variables; end - } - bind = obj.instance_exec { binding } - - assert_include(completion_candidates("public_hog", bind), "public_hoge") - assert_include(doc_namespace("public_hoge", bind), "public_hoge") - - assert_include(completion_candidates("private_hog", bind), "private_hoge") - assert_include(doc_namespace("private_hoge", bind), "private_hoge") - end - end - - class DeprecatedInputCompletorTest < TestCase - def setup - save_encodings - @verbose, $VERBOSE = $VERBOSE, nil - IRB.init_config(nil) - IRB.conf[:VERBOSE] = false - IRB.conf[:MAIN_CONTEXT] = IRB::Context.new(IRB::WorkSpace.new(binding)) - end - - def teardown - restore_encodings - $VERBOSE = @verbose - end - - def test_completion_proc - assert_include(IRB::InputCompletor::CompletionProc.call('1.ab'), '1.abs') - assert_include(IRB::InputCompletor::CompletionProc.call('1.ab', '', ''), '1.abs') - end - - def test_retrieve_completion_data - assert_include(IRB::InputCompletor.retrieve_completion_data('1.ab'), '1.abs') - assert_equal(IRB::InputCompletor.retrieve_completion_data('1.abs', doc_namespace: true), 'Integer.abs') - bind = eval('a = 1; binding') - assert_include(IRB::InputCompletor.retrieve_completion_data('a.ab', bind: bind), 'a.abs') - assert_equal(IRB::InputCompletor.retrieve_completion_data('a.abs', bind: bind, doc_namespace: true), 'Integer.abs') - end - end -end diff --git a/test/irb/test_context.rb b/test/irb/test_context.rb deleted file mode 100644 index c44c8e0573..0000000000 --- a/test/irb/test_context.rb +++ /dev/null @@ -1,737 +0,0 @@ -# frozen_string_literal: false -require 'tempfile' -require 'irb' - -require_relative "helper" - -module TestIRB - class ContextTest < TestCase - def setup - IRB.init_config(nil) - IRB.conf[:USE_SINGLELINE] = false - IRB.conf[:VERBOSE] = false - IRB.conf[:USE_PAGER] = false - workspace = IRB::WorkSpace.new(Object.new) - @context = IRB::Context.new(nil, workspace, TestInputMethod.new) - - @get_screen_size = Reline.method(:get_screen_size) - Reline.instance_eval { undef :get_screen_size } - def Reline.get_screen_size - [36, 80] - end - save_encodings - end - - def teardown - Reline.instance_eval { undef :get_screen_size } - Reline.define_singleton_method(:get_screen_size, @get_screen_size) - restore_encodings - end - - def test_eval_input - verbose, $VERBOSE = $VERBOSE, nil - input = TestInputMethod.new([ - "raise 'Foo'\n", - "_\n", - "0\n", - "_\n", - ]) - irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input) - out, err = capture_output do - irb.eval_input - end - assert_empty err - - 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 - - def test_eval_input_raise2x - input = TestInputMethod.new([ - "raise 'Foo'\n", - "raise 'Bar'\n", - "_\n", - ]) - irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input) - out, err = capture_output do - irb.eval_input - end - assert_empty err - 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 - irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), TestInputMethod.new) - - _, err = capture_output do - irb.context.prompt_n = "foo" - irb.context.prompt_n - end - - assert_include err, "IRB::Context#prompt_n is deprecated" - assert_include err, "IRB::Context#prompt_n= is deprecated" - end - - def test_output_to_pipe - require 'stringio' - input = TestInputMethod.new(["n=1"]) - input.instance_variable_set(:@stdout, StringIO.new) - irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input) - irb.context.echo_on_assignment = :truncate - irb.context.prompt_mode = :DEFAULT - out, err = capture_output do - irb.eval_input - end - assert_empty err - assert_equal "=> 1\n", out - end - - { - successful: [ - [false, "class Foo < Struct.new(:bar); end; Foo.new(123)\n", /#<struct bar=123>/], - [:p, "class Foo < Struct.new(:bar); end; Foo.new(123)\n", /#<struct bar=123>/], - [true, "class Foo < Struct.new(:bar); end; Foo.new(123)\n", /#<struct #<Class:.*>::Foo bar=123>/], - [:yaml, "123", /--- 123\n/], - [: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/], - [:marshal, "[Object.new, Class.new]", /#<TypeError: can't dump anonymous class #<Class:/] - ] - }.each do |scenario, cases| - cases.each do |inspect_mode, input, expected| - define_method "test_#{inspect_mode}_inspect_mode_#{scenario}" do - verbose, $VERBOSE = $VERBOSE, nil - irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), TestInputMethod.new([input])) - irb.context.inspect_mode = inspect_mode - out, err = capture_output do - irb.eval_input - end - assert_empty err - assert_match(expected, out) - ensure - $VERBOSE = verbose - end - end - end - - def test_object_inspection_handles_basic_object - verbose, $VERBOSE = $VERBOSE, nil - irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), TestInputMethod.new(["BasicObject.new"])) - out, err = capture_output do - irb.eval_input - end - assert_empty err - assert_not_match(/NoMethodError/, out) - assert_match(/#<BasicObject:.*>/, out) - ensure - $VERBOSE = verbose - end - - def test_object_inspection_falls_back_to_kernel_inspect_when_errored - verbose, $VERBOSE = $VERBOSE, nil - main = Object.new - main.singleton_class.module_eval <<~RUBY - class Foo - def inspect - raise "foo" - end - end - RUBY - - irb = IRB::Irb.new(IRB::WorkSpace.new(main), TestInputMethod.new(["Foo.new"])) - out, err = capture_output do - irb.eval_input - end - assert_empty err - assert_match(/An error occurred when inspecting the object: #<RuntimeError: foo>/, out) - assert_match(/Result of Kernel#inspect: #<#<Class:.*>::Foo:/, out) - ensure - $VERBOSE = verbose - end - - def test_object_inspection_prints_useful_info_when_kernel_inspect_also_errored - verbose, $VERBOSE = $VERBOSE, nil - main = Object.new - main.singleton_class.module_eval <<~RUBY - class Foo - def initialize - # Kernel#inspect goes through instance variables with #inspect - # So this will cause Kernel#inspect to fail - @foo = BasicObject.new - end - - def inspect - raise "foo" - end - end - RUBY - - irb = IRB::Irb.new(IRB::WorkSpace.new(main), TestInputMethod.new(["Foo.new"])) - out, err = capture_output do - irb.eval_input - 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) - ensure - $VERBOSE = verbose - end - - def test_default_config - assert_equal(true, @context.use_autocomplete?) - end - - def test_echo_on_assignment - input = TestInputMethod.new([ - "a = 1\n", - "a\n", - "a, b = 2, 3\n", - "a\n", - "b\n", - "b = 4\n", - "_\n" - ]) - irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input) - irb.context.return_format = "=> %s\n" - - # The default - irb.context.echo = true - irb.context.echo_on_assignment = false - out, err = capture_output do - irb.eval_input - end - assert_empty err - assert_equal("=> 1\n=> 2\n=> 3\n=> 4\n", out) - - # Everything is output, like before echo_on_assignment was introduced - input.reset - irb.context.echo = true - irb.context.echo_on_assignment = true - out, err = capture_output do - irb.eval_input - end - assert_empty err - assert_equal("=> 1\n=> 1\n=> [2, 3]\n=> 2\n=> 3\n=> 4\n=> 4\n", out) - - # Nothing is output when echo is false - input.reset - irb.context.echo = false - irb.context.echo_on_assignment = false - out, err = capture_output do - irb.eval_input - end - assert_empty err - assert_equal("", out) - - # Nothing is output when echo is false even if echo_on_assignment is true - input.reset - irb.context.echo = false - irb.context.echo_on_assignment = true - out, err = capture_output do - irb.eval_input - end - assert_empty err - assert_equal("", out) - end - - def test_omit_on_assignment - input = TestInputMethod.new([ - "a = [1] * 100\n", - "a\n", - ]) - value = [1] * 100 - irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input) - irb.context.return_format = "=> %s\n" - - irb.context.echo = true - irb.context.echo_on_assignment = false - out, err = capture_output do - irb.eval_input - end - assert_empty err - assert_equal("=> \n#{value.pretty_inspect}", out) - - input.reset - irb.context.echo = true - irb.context.echo_on_assignment = :truncate - out, err = capture_output do - irb.eval_input - end - assert_empty err - assert_equal("=> \n#{value.pretty_inspect[0..3]}...\n=> \n#{value.pretty_inspect}", out) - - input.reset - irb.context.echo = true - irb.context.echo_on_assignment = true - out, err = capture_output do - irb.eval_input - end - assert_empty err - assert_equal("=> \n#{value.pretty_inspect}=> \n#{value.pretty_inspect}", out) - - input.reset - irb.context.echo = false - irb.context.echo_on_assignment = false - out, err = capture_output do - irb.eval_input - end - assert_empty err - assert_equal("", out) - - input.reset - irb.context.echo = false - irb.context.echo_on_assignment = :truncate - out, err = capture_output do - irb.eval_input - end - assert_empty err - assert_equal("", out) - - input.reset - irb.context.echo = false - irb.context.echo_on_assignment = true - out, err = capture_output do - irb.eval_input - end - assert_empty err - assert_equal("", out) - end - - def test_omit_multiline_on_assignment - without_colorize do - input = TestInputMethod.new([ - "class A; def inspect; ([?* * 1000] * 3).join(%{\\n}); end; end; a = A.new\n", - "a\n" - ]) - value = ([?* * 1000] * 3).join(%{\n}) - value_first_line = (?* * 1000).to_s - irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input) - irb.context.return_format = "=> %s\n" - - irb.context.echo = true - irb.context.echo_on_assignment = false - out, err = capture_output do - irb.eval_input - end - assert_empty err - assert_equal("=> \n#{value}\n", out) - irb.context.evaluate_expression('A.remove_method(:inspect)', 0) - - input.reset - irb.context.echo = true - irb.context.echo_on_assignment = :truncate - out, err = capture_output do - irb.eval_input - end - assert_empty err - assert_equal("=> \n#{value_first_line[0, input.winsize.last]}...\n=> \n#{value}\n", out) - irb.context.evaluate_expression('A.remove_method(:inspect)', 0) - - input.reset - irb.context.echo = true - irb.context.echo_on_assignment = true - out, err = capture_output do - irb.eval_input - end - assert_empty err - assert_equal("=> \n#{value}\n=> \n#{value}\n", out) - irb.context.evaluate_expression('A.remove_method(:inspect)', 0) - - input.reset - irb.context.echo = false - irb.context.echo_on_assignment = false - out, err = capture_output do - irb.eval_input - end - assert_empty err - assert_equal("", out) - irb.context.evaluate_expression('A.remove_method(:inspect)', 0) - - input.reset - irb.context.echo = false - irb.context.echo_on_assignment = :truncate - out, err = capture_output do - irb.eval_input - end - assert_empty err - assert_equal("", out) - irb.context.evaluate_expression('A.remove_method(:inspect)', 0) - - input.reset - irb.context.echo = false - irb.context.echo_on_assignment = true - out, err = capture_output do - irb.eval_input - end - assert_empty err - assert_equal("", out) - irb.context.evaluate_expression('A.remove_method(:inspect)', 0) - end - end - - def test_echo_on_assignment_conf - # Default - IRB.conf[:ECHO] = nil - IRB.conf[:ECHO_ON_ASSIGNMENT] = nil - without_colorize do - input = TestInputMethod.new() - irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input) - - assert(irb.context.echo?, "echo? should be true by default") - assert_equal(:truncate, irb.context.echo_on_assignment?, "echo_on_assignment? should be :truncate by default") - - # Explicitly set :ECHO to false - IRB.conf[:ECHO] = false - irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input) - - refute(irb.context.echo?, "echo? should be false when IRB.conf[:ECHO] is set to false") - assert_equal(:truncate, irb.context.echo_on_assignment?, "echo_on_assignment? should be :truncate by default") - - # Explicitly set :ECHO_ON_ASSIGNMENT to true - IRB.conf[:ECHO] = nil - IRB.conf[:ECHO_ON_ASSIGNMENT] = false - irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input) - - assert(irb.context.echo?, "echo? should be true by default") - refute(irb.context.echo_on_assignment?, "echo_on_assignment? should be false when IRB.conf[:ECHO_ON_ASSIGNMENT] is set to false") - end - end - - def test_multiline_output_on_default_inspector - main = Object.new - def main.inspect - "abc\ndef" - end - - without_colorize do - input = TestInputMethod.new([ - "self" - ]) - irb = IRB::Irb.new(IRB::WorkSpace.new(main), input) - irb.context.return_format = "=> %s\n" - - # The default - irb.context.newline_before_multiline_output = true - out, err = capture_output do - irb.eval_input - end - assert_empty err - assert_equal("=> \nabc\ndef\n", - out) - - # No newline before multiline output - input.reset - irb.context.newline_before_multiline_output = false - out, err = capture_output do - irb.eval_input - end - assert_empty err - assert_equal("=> abc\ndef\n", out) - end - end - - def test_default_return_format - IRB.conf[:PROMPT][:MY_PROMPT] = { - :PROMPT_I => "%03n> ", - :PROMPT_S => "%03n> ", - :PROMPT_C => "%03n> " - # without :RETURN - # :RETURN => "%s\n" - } - IRB.conf[:PROMPT_MODE] = :MY_PROMPT - input = TestInputMethod.new([ - "3" - ]) - irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input) - out, err = capture_output do - irb.eval_input - end - assert_empty err - assert_equal("3\n", - out) - end - - def test_eval_input_with_exception - pend if RUBY_ENGINE == 'truffleruby' - verbose, $VERBOSE = $VERBOSE, nil - input = TestInputMethod.new([ - "def hoge() fuga; end; def fuga() raise; end; hoge\n", - ]) - irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input) - out, err = capture_output do - irb.eval_input - end - assert_empty err - 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 - - def test_eval_input_with_invalid_byte_sequence_exception - verbose, $VERBOSE = $VERBOSE, nil - input = TestInputMethod.new([ - %Q{def hoge() fuga; end; def fuga() raise "A\\xF3B"; end; hoge\n}, - ]) - irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input) - out, err = capture_output do - irb.eval_input - end - assert_empty err - 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 - - def test_eval_input_with_long_exception - pend if RUBY_ENGINE == 'truffleruby' - verbose, $VERBOSE = $VERBOSE, nil - nesting = 20 - generated_code = '' - nesting.times do |i| - generated_code << "def a#{i}() a#{i + 1}; end; " - end - generated_code << "def a#{nesting}() raise; end; a0\n" - input = TestInputMethod.new([ - generated_code - ]) - irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input) - out, err = capture_output do - irb.eval_input - end - assert_empty err - if RUBY_VERSION < '3.0.0' && STDOUT.tty? - 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/, - ] - 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/, - :*, /\t... \d+ levels...\n/, - ] - end - assert_pattern_list(expected, out) - ensure - $VERBOSE = verbose - end - - def test_prompt_main_escape - main = Struct.new(:to_s).new("main\a\t\r\n") - irb = IRB::Irb.new(IRB::WorkSpace.new(main), TestInputMethod.new) - assert_equal("irb(main )>", irb.send(:format_prompt, 'irb(%m)>', nil, 1, 1)) - end - - def test_prompt_main_inspect_escape - main = Struct.new(:inspect).new("main\\n\nmain") - irb = IRB::Irb.new(IRB::WorkSpace.new(main), TestInputMethod.new) - assert_equal("irb(main\\n main)>", irb.send(:format_prompt, 'irb(%M)>', nil, 1, 1)) - end - - def test_prompt_main_truncate - main = Struct.new(:to_s).new("a" * 100) - def main.inspect; to_s.inspect; end - irb = IRB::Irb.new(IRB::WorkSpace.new(main), TestInputMethod.new) - assert_equal('irb(aaaaaaaaaaaaaaaaaaaaaaaaaaaaa...)>', irb.send(:format_prompt, 'irb(%m)>', nil, 1, 1)) - assert_equal('irb("aaaaaaaaaaaaaaaaaaaaaaaaaaaa...)>', irb.send(:format_prompt, 'irb(%M)>', nil, 1, 1)) - end - - def test_prompt_main_basic_object - main = BasicObject.new - irb = IRB::Irb.new(IRB::WorkSpace.new(main), TestInputMethod.new) - assert_match(/irb\(#<BasicObject:.+\)/, irb.send(:format_prompt, 'irb(%m)>', nil, 1, 1)) - assert_match(/irb\(#<BasicObject:.+\)/, irb.send(:format_prompt, 'irb(%M)>', nil, 1, 1)) - end - - def test_prompt_main_raise - main = Object.new - def main.to_s; raise TypeError; end - def main.inspect; raise ArgumentError; end - irb = IRB::Irb.new(IRB::WorkSpace.new(main), TestInputMethod.new) - assert_equal("irb(!TypeError)>", irb.send(:format_prompt, 'irb(%m)>', nil, 1, 1)) - 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", - "__LINE__\n", - "__LINE__\n", - "\n", - "\n", - "__LINE__\n", - ]) - irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input) - out, err = capture_output do - irb.eval_input - end - assert_empty err - assert_pattern_list([ - :*, /\b2\n/, - :*, /\b3\n/, - :*, /\b6\n/, - ], 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] - IRB.conf[:COMPLETOR] = nil - assert_match(/IRB::(Regexp|Type)Completor/, @context.send(:build_completor).class.name) - IRB.conf[:COMPLETOR] = :regexp - assert_equal 'IRB::RegexpCompletor', @context.send(:build_completor).class.name - IRB.conf[:COMPLETOR] = :unknown - assert_equal 'IRB::RegexpCompletor', @context.send(:build_completor).class.name - # :type is tested in test_type_completor.rb - ensure - $VERBOSE = verbose - IRB.conf[:COMPLETOR] = original_completor - end - - private - - def without_colorize - original_value = IRB.conf[:USE_COLORIZE] - IRB.conf[:USE_COLORIZE] = false - yield - ensure - IRB.conf[:USE_COLORIZE] = original_value - end - end -end diff --git a/test/irb/test_debugger_integration.rb b/test/irb/test_debugger_integration.rb deleted file mode 100644 index 45ffb2a52e..0000000000 --- a/test/irb/test_debugger_integration.rb +++ /dev/null @@ -1,513 +0,0 @@ -# frozen_string_literal: true - -require "tempfile" -require "tmpdir" - -require_relative "helper" - -module TestIRB - class DebuggerIntegrationTest < IntegrationTestCase - def setup - super - - if RUBY_ENGINE == 'truffleruby' - omit "This test runs with ruby/debug, which doesn't work with truffleruby" - end - - @envs.merge!("NO_COLOR" => "true", "RUBY_DEBUG_HISTORY_FILE" => '') - end - - def test_backtrace - write_ruby <<~'RUBY' - def foo - binding.irb - end - foo - RUBY - - output = run_ruby_file do - type "backtrace" - type "exit!" - end - - assert_match(/irb\(main\):001> backtrace/, output) - assert_match(/Object#foo at #{@ruby_file.to_path}/, output) - end - - def test_debug - write_ruby <<~'ruby' - binding.irb - puts "hello" - ruby - - output = run_ruby_file do - type "debug" - type "next" - type "continue" - end - - assert_match(/irb\(main\):001> debug/, output) - assert_match(/irb:rdbg\(main\):002> next/, output) - assert_match(/=> 2\| puts "hello"/, output) - end - - def test_debug_command_only_runs_once - write_ruby <<~'ruby' - binding.irb - ruby - - output = run_ruby_file do - type "debug" - type "debug" - type "continue" - end - - assert_match(/irb\(main\):001> debug/, output) - assert_match(/irb:rdbg\(main\):002> debug/, output) - 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 - puts "hello" - ruby - - output = run_ruby_file do - type "next" - type "continue" - end - - assert_match(/irb\(main\):001> next/, output) - assert_match(/=> 2\| puts "hello"/, output) - end - - def test_break - write_ruby <<~'RUBY' - binding.irb - puts "Hello" - RUBY - - output = run_ruby_file do - type "break 2" - type "continue" - type "continue" - end - - assert_match(/irb\(main\):001> break/, output) - assert_match(/=> 2\| puts "Hello"/, output) - end - - def test_delete - write_ruby <<~'RUBY' - binding.irb - puts "Hello" - binding.irb - puts "World" - RUBY - - output = run_ruby_file do - type "break 4" - type "continue" - type "delete 0" - type "continue" - end - - assert_match(/irb:rdbg\(main\):003> delete/, output) - assert_match(/deleted: #0 BP - Line/, output) - end - - def test_step - write_ruby <<~'RUBY' - def foo - puts "Hello" - end - binding.irb - foo - RUBY - - output = run_ruby_file do - type "step" - type "step" - type "continue" - end - - assert_match(/irb\(main\):001> step/, output) - assert_match(/=> 5\| foo/, output) - assert_match(/=> 2\| puts "Hello"/, output) - end - - def test_long_stepping - write_ruby <<~'RUBY' - class Foo - def foo(num) - bar(num + 10) - end - - def bar(num) - num - end - end - - binding.irb - Foo.new.foo(100) - RUBY - - output = run_ruby_file do - type "step" - type "step" - type "step" - type "step" - type "num" - type "continue" - end - - assert_match(/irb\(main\):001> step/, output) - assert_match(/irb:rdbg\(main\):002> step/, output) - assert_match(/irb:rdbg\(#<Foo:.*>\):003> step/, output) - assert_match(/irb:rdbg\(#<Foo:.*>\):004> step/, output) - assert_match(/irb:rdbg\(#<Foo:.*>\):005> num/, output) - assert_match(/=> 110/, output) - end - - def test_continue - write_ruby <<~'RUBY' - binding.irb - puts "Hello" - binding.irb - puts "World" - RUBY - - output = run_ruby_file do - type "continue" - type "continue" - end - - assert_match(/irb\(main\):001> continue/, output) - assert_match(/=> 3: binding.irb/, output) - assert_match(/irb:rdbg\(main\):002> continue/, output) - end - - def test_finish - write_ruby <<~'RUBY' - def foo - binding.irb - puts "Hello" - end - foo - RUBY - - output = run_ruby_file do - type "finish" - type "continue" - end - - assert_match(/irb\(main\):001> finish/, output) - assert_match(/=> 4\| end/, output) - end - - def test_info - write_ruby <<~'RUBY' - def foo - a = "He" + "llo" - binding.irb - end - foo - RUBY - - output = run_ruby_file do - type "info" - type "continue" - end - - assert_match(/irb\(main\):001> info/, output) - assert_match(/%self = main/, output) - assert_match(/a = "Hello"/, output) - end - - def test_catch - write_ruby <<~'RUBY' - binding.irb - 1 / 0 - RUBY - - output = run_ruby_file do - type "catch ZeroDivisionError" - type "continue" - type "continue" - end - - assert_match(/irb\(main\):001> catch/, output) - assert_match(/Stop by #0 BP - Catch "ZeroDivisionError"/, output) - end - - def test_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_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 "debug" - type "quit!" - end - - assert_match(/irb:rdbg\(main\):002>/, output) - assert_not_match(/hello/, output) - end - - def test_prompt_line_number_continues - write_ruby <<~'ruby' - binding.irb - puts "Hello" - puts "World" - ruby - - output = run_ruby_file do - type "123" - type "456" - type "next" - type "info" - type "next" - type "continue" - end - - assert_match(/irb\(main\):003> next/, output) - assert_match(/irb:rdbg\(main\):004> info/, output) - assert_match(/irb:rdbg\(main\):005> next/, output) - end - - def test_prompt_irb_name_is_kept - write_rc <<~RUBY - IRB.conf[:IRB_NAME] = "foo" - RUBY - - write_ruby <<~'ruby' - binding.irb - puts "Hello" - ruby - - output = run_ruby_file do - type "next" - type "continue" - end - - assert_match(/foo\(main\):001> next/, output) - assert_match(/foo:rdbg\(main\):002> continue/, output) - end - - def test_irb_commands_are_available_after_moving_around_with_the_debugger - write_ruby <<~'ruby' - class Foo - def bar - puts "bar" - end - end - - binding.irb - Foo.new.bar - ruby - - output = run_ruby_file do - # Due to the way IRB defines its commands, moving into the Foo instance from main is necessary for proper testing. - type "next" - type "step" - type "irb_info" - type "continue" - end - - assert_include(output, "InputMethod: RelineInputMethod") - end - - def test_irb_command_can_check_local_variables - write_ruby <<~'ruby' - binding.irb - ruby - - output = run_ruby_file do - type "debug" - type 'foobar = IRB' - type "show_source foobar.start" - type "show_source = 'Foo'" - type "show_source + 'Bar'" - type "continue" - end - assert_include(output, "def start(ap_path = nil)") - assert_include(output, '"FooBar"') - end - - def test_help_command_is_delegated_to_the_debugger - write_ruby <<~'ruby' - binding.irb - ruby - - output = run_ruby_file do - type "debug" - type "help" - type "continue" - end - - assert_include(output, "### Frame control") - end - - def test_help_display_different_content_when_debugger_is_enabled - write_ruby <<~'ruby' - binding.irb - ruby - - output = run_ruby_file do - type "debug" - type "help" - type "continue" - end - - # IRB's commands should still be listed - 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 - - def test_input_is_evaluated_in_the_context_of_the_current_thread - write_ruby <<~'ruby' - current_thread = Thread.current - binding.irb - ruby - - output = run_ruby_file do - type "debug" - type '"Threads match: #{current_thread == Thread.current}"' - type "continue" - end - - assert_match(/irb\(main\):001> debug/, output) - assert_match(/Threads match: true/, output) - end - - def test_irb_switches_debugger_interface_if_debug_was_already_activated - write_ruby <<~'ruby' - require 'debug' - class Foo - def bar - puts "bar" - end - end - - binding.irb - Foo.new.bar - ruby - - output = run_ruby_file do - # Due to the way IRB defines its commands, moving into the Foo instance from main is necessary for proper testing. - type "next" - type "step" - type 'irb_info' - type "continue" - end - - assert_match(/irb\(main\):001> next/, output) - assert_include(output, "InputMethod: RelineInputMethod") - end - - def test_debugger_cant_be_activated_while_multi_irb_is_active - write_ruby <<~'ruby' - binding.irb - a = 1 - ruby - - output = run_ruby_file do - type "jobs" - type "next" - type "exit" - end - - assert_match(/irb\(main\):001> jobs/, output) - assert_include(output, "Can't start the debugger when IRB is running in a multi-IRB session.") - end - - def test_multi_irb_commands_are_not_available_after_activating_the_debugger - write_ruby <<~'ruby' - binding.irb - a = 1 - ruby - - output = run_ruby_file do - type "next" - type "jobs" - type "continue" - end - - 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 deleted file mode 100644 index 54913ceff5..0000000000 --- a/test/irb/test_eval_history.rb +++ /dev/null @@ -1,69 +0,0 @@ -# frozen_string_literal: true -require "irb" - -require_relative "helper" - -module TestIRB - class EvalHistoryTest < TestCase - def setup - save_encodings - IRB.instance_variable_get(:@CONF).clear - end - - def teardown - 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[: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.eval_input - end - end - - 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) - end - - def test_eval_history_can_be_retrieved_with_double_underscore - out, err = execute_lines( - "a = 1", - "__", - conf: { EVAL_HISTORY: 5 } - ) - - assert_empty(err) - assert_match("=> 1\n" + "=> 1 1\n", out) - end - - def test_eval_history_respects_given_limit - out, err = execute_lines( - "'foo'\n", - "'bar'\n", - "'baz'\n", - "'xyz'\n", - "__", - conf: { EVAL_HISTORY: 4 } - ) - - assert_empty(err) - # Because eval_history injects `__` into the history AND decide to ignore it, we only get <limit> - 1 results - assert_match("2 \"bar\"\n" + "3 \"baz\"\n" + "4 \"xyz\"\n", out) - end - end -end diff --git a/test/irb/test_evaluation.rb b/test/irb/test_evaluation.rb deleted file mode 100644 index adb69b2067..0000000000 --- a/test/irb/test_evaluation.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -require "tempfile" - -require_relative "helper" - -module TestIRB - class EchoingTest < IntegrationTestCase - def test_irb_echos_by_default - write_ruby <<~'RUBY' - binding.irb - RUBY - - output = run_ruby_file do - type "123123" - type "exit" - end - - assert_include(output, "=> 123123") - end - - def test_irb_doesnt_echo_line_with_semicolon - write_ruby <<~'RUBY' - binding.irb - RUBY - - output = run_ruby_file do - type "123123;" - type "123123 ;" - type "123123; " - type <<~RUBY - if true - 123123 - end; - RUBY - type "'evaluation ends'" - type "exit" - end - - assert_include(output, "=> \"evaluation ends\"") - assert_not_include(output, "=> 123123") - end - end -end diff --git a/test/irb/test_helper_method.rb b/test/irb/test_helper_method.rb deleted file mode 100644 index a3e2c43b2f..0000000000 --- a/test/irb/test_helper_method.rb +++ /dev/null @@ -1,135 +0,0 @@ -# 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 - - optional = {a: 1, b: 2} - assert_include(output, %[["required", "optional", ["splat"], "required", "optional", #{optional.inspect}, "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 deleted file mode 100644 index 0171bb0eca..0000000000 --- a/test/irb/test_history.rb +++ /dev/null @@ -1,573 +0,0 @@ -# frozen_string_literal: false -require 'irb' -require 'readline' -require "tempfile" - -require_relative "helper" - -return if RUBY_PLATFORM.match?(/solaris|mswin|mingw/i) - -module TestIRB - class HistoryTest < TestCase - def setup - @conf_backup = IRB.conf.dup - @original_verbose, $VERBOSE = $VERBOSE, nil - @tmpdir = Dir.mktmpdir("test_irb_history_") - setup_envs(home: @tmpdir) - IRB.conf[:LC_MESSAGES] = IRB::Locale.new - save_encodings - IRB.instance_variable_set(:@existing_rc_name_generators, nil) - end - - def teardown - IRB.conf.replace(@conf_backup) - IRB.instance_variable_set(:@existing_rc_name_generators, nil) - teardown_envs - restore_encodings - $VERBOSE = @original_verbose - FileUtils.rm_rf(@tmpdir) - end - - class TestInputMethodWithRelineHistory < TestInputMethod - # When IRB.conf[:USE_MULTILINE] is true, IRB::RelineInputMethod uses Reline::History - HISTORY = Reline::History.new(Reline.core.config) - - include IRB::HistorySavingAbility - end - - class TestInputMethodWithReadlineHistory < TestInputMethod - # When IRB.conf[:USE_MULTILINE] is false, IRB::ReadlineInputMethod uses Readline::HISTORY - HISTORY = Readline::HISTORY - - include IRB::HistorySavingAbility - end - - def test_history_dont_save - omit "Skip Editline" if /EditLine/n.match(Readline::VERSION) - IRB.conf[:SAVE_HISTORY] = nil - assert_history(<<~EXPECTED_HISTORY, <<~INITIAL_HISTORY, <<~INPUT) - 1 - 2 - EXPECTED_HISTORY - 1 - 2 - INITIAL_HISTORY - 3 - exit - INPUT - end - - def test_history_save_1 - omit "Skip Editline" if /EditLine/n.match(Readline::VERSION) - IRB.conf[:SAVE_HISTORY] = 1 - assert_history(<<~EXPECTED_HISTORY, <<~INITIAL_HISTORY, <<~INPUT) - exit - EXPECTED_HISTORY - 1 - 2 - 3 - 4 - INITIAL_HISTORY - 5 - exit - INPUT - end - - def test_history_save_100 - omit "Skip Editline" if /EditLine/n.match(Readline::VERSION) - IRB.conf[:SAVE_HISTORY] = 100 - assert_history(<<~EXPECTED_HISTORY, <<~INITIAL_HISTORY, <<~INPUT) - 1 - 2 - 3 - 4 - 5 - exit - EXPECTED_HISTORY - 1 - 2 - 3 - 4 - INITIAL_HISTORY - 5 - exit - INPUT - end - - def test_history_save_bignum - omit "Skip Editline" if /EditLine/n.match(Readline::VERSION) - IRB.conf[:SAVE_HISTORY] = 10 ** 19 - assert_history(<<~EXPECTED_HISTORY, <<~INITIAL_HISTORY, <<~INPUT) - 1 - 2 - 3 - 4 - 5 - exit - EXPECTED_HISTORY - 1 - 2 - 3 - 4 - INITIAL_HISTORY - 5 - exit - INPUT - end - - def test_history_save_minus_as_infinity - omit "Skip Editline" if /EditLine/n.match(Readline::VERSION) - IRB.conf[:SAVE_HISTORY] = -1 # infinity - assert_history(<<~EXPECTED_HISTORY, <<~INITIAL_HISTORY, <<~INPUT) - 1 - 2 - 3 - 4 - 5 - exit - EXPECTED_HISTORY - 1 - 2 - 3 - 4 - INITIAL_HISTORY - 5 - exit - INPUT - end - - def test_history_concurrent_use_reline - omit "Skip Editline" if /EditLine/n.match(Readline::VERSION) - IRB.conf[:SAVE_HISTORY] = 1 - history_concurrent_use_for_input_method(TestInputMethodWithRelineHistory) - end - - def test_history_concurrent_use_readline - omit "Skip Editline" if /EditLine/n.match(Readline::VERSION) - IRB.conf[:SAVE_HISTORY] = 1 - history_concurrent_use_for_input_method(TestInputMethodWithReadlineHistory) - end - - def test_history_concurrent_use_not_present - IRB.conf[:SAVE_HISTORY] = 1 - 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 - IRB.conf[:SAVE_HISTORY] = 2 - IRB.conf[:LC_MESSAGES] = IRB::Locale.new("en_US.ASCII") - IRB.__send__(:set_encoding, Encoding::US_ASCII.name, override: false) - assert_history(<<~EXPECTED_HISTORY.encode(Encoding::US_ASCII), <<~INITIAL_HISTORY.encode(Encoding::UTF_8), <<~INPUT) - ???? - exit - EXPECTED_HISTORY - 😀 - 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(/ensure the folder exists/i) 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 - 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 - - def history_concurrent_use_for_input_method(input_method) - assert_history(<<~EXPECTED_HISTORY, <<~INITIAL_HISTORY, <<~INPUT, input_method) do |history_file| - exit - 5 - exit - EXPECTED_HISTORY - 1 - 2 - 3 - 4 - INITIAL_HISTORY - 5 - exit - INPUT - assert_history(<<~EXPECTED_HISTORY2, <<~INITIAL_HISTORY2, <<~INPUT2, input_method) - exit - EXPECTED_HISTORY2 - 1 - 2 - 3 - 4 - INITIAL_HISTORY2 - 5 - exit - INPUT2 - File.utime(File.atime(history_file), File.mtime(history_file) + 2, history_file) - end - end - - def assert_history(expected_history, initial_irb_history, input, input_method = TestInputMethodWithRelineHistory) - actual_history = nil - 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.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 - 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(history_file, "r") do |f| - actual_history = f.read - end - assert_equal(expected_history, actual_history, <<~MESSAGE) - expected: - #{expected_history} - but actual: - #{actual_history} - MESSAGE - end - - def with_temp_stdio - Tempfile.create("test_readline_stdin") do |stdin| - Tempfile.create("test_readline_stdout") do |stdout| - yield stdin, stdout - end - end - end - end - - class IRBHistoryIntegrationTest < IntegrationTestCase - def test_history_saving_can_be_disabled_with_false - write_history "" - write_rc <<~RUBY - IRB.conf[:SAVE_HISTORY] = false - RUBY - - write_ruby <<~'RUBY' - binding.irb - RUBY - - output = run_ruby_file do - type "puts 'foo' + 'bar'" - type "exit" - end - - assert_include(output, "foobar") - assert_equal "", @history_file.open.read - end - - def test_history_saving_accepts_true - write_history "" - write_rc <<~RUBY - IRB.conf[:SAVE_HISTORY] = true - RUBY - - write_ruby <<~'RUBY' - binding.irb - RUBY - - output = run_ruby_file do - type "puts 'foo' + 'bar'" - type "exit" - end - - assert_include(output, "foobar") - assert_equal <<~HISTORY, @history_file.open.read - puts 'foo' + 'bar' - exit - HISTORY - end - - def test_history_saving_with_debug - write_history "" - - 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, @history_file.open.read - 'irb session' - next - 'irb:debug session' - step - irb_info - puts Reline::HISTORY.to_a.to_s - q! - 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 "" - - 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_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 - old_history_2 - old_history_3 - 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 - old_history_1 - old_history_2 - old_history_3 - 'outer session' - foo - 'inner session' - exit - 'outer session again' - exit - HISTORY - end - - def test_direct_debug_session_loads_history - @envs['RUBY_DEBUG_IRB_CONSOLE'] = "1" - write_history <<~HISTORY - old_history_1 - old_history_2 - old_history_3 - HISTORY - - write_ruby <<~'RUBY' - require 'debug' - debugger - binding.irb # needed to satisfy run_ruby_file - RUBY - - output = run_ruby_file do - type "history" - type "puts 'foo'" - type "history" - type "exit!" - end - - assert_include(output, "irb:rdbg(main):002") # assert that we're in an irb:rdbg session - assert_include(output, "5: history") - assert_include(output, "4: puts 'foo'") - assert_include(output, "3: history") - assert_include(output, "2: old_history_3") - assert_include(output, "1: old_history_2") - assert_include(output, "0: old_history_1") - end - - private - - def write_history(history) - @history_file = Tempfile.new('irb_history') - @history_file.write(history) - @history_file.close - write_rc <<~RUBY - IRB.conf[:HISTORY_FILE] = "#{@history_file.path}" - RUBY - end - end -end diff --git a/test/irb/test_init.rb b/test/irb/test_init.rb deleted file mode 100644 index f34f692f09..0000000000 --- a/test/irb/test_init.rb +++ /dev/null @@ -1,408 +0,0 @@ -# frozen_string_literal: false -require "irb" -require "fileutils" - -require_relative "helper" - -module TestIRB - class InitTest < TestCase - def setup - # IRBRC is for RVM... - @backup_env = %w[HOME XDG_CONFIG_HOME IRBRC].each_with_object({}) do |env, hash| - hash[env] = ENV.delete(env) - end - 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 - argv = ["foo", "bar"] - with_argv(argv) do - IRB.setup(eval("__FILE__"), argv: %w[-f]) - assert_equal argv, ARGV - end - end - - def test_setup_with_minimum_argv_does_not_change_dollar0 - orig = $0.dup - IRB.setup(eval("__FILE__"), argv: %w[-f]) - assert_equal orig, $0 - end - - def test_rc_files - tmpdir = @tmpdir - Dir.chdir(tmpdir) do - 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_duplicated_rc_files - tmpdir = @tmpdir - Dir.chdir(tmpdir) do - 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_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'] : [] - # 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 - - def test_no_color_environment_variable - orig_no_color = ENV['NO_COLOR'] - orig_use_colorize = IRB.conf[:USE_COLORIZE] - IRB.conf[:USE_COLORIZE] = true - - assert IRB.conf[:USE_COLORIZE] - - ENV['NO_COLOR'] = 'true' - IRB.setup(__FILE__) - refute IRB.conf[:USE_COLORIZE] - - ENV['NO_COLOR'] = '' - IRB.setup(__FILE__) - assert IRB.conf[:USE_COLORIZE] - - ENV['NO_COLOR'] = nil - IRB.setup(__FILE__) - assert IRB.conf[:USE_COLORIZE] - ensure - ENV['NO_COLOR'] = orig_no_color - IRB.conf[:USE_COLORIZE] = orig_use_colorize - end - - def test_use_autocomplete_environment_variable - orig_use_autocomplete_env = ENV['IRB_USE_AUTOCOMPLETE'] - orig_use_autocomplete_conf = IRB.conf[:USE_AUTOCOMPLETE] - - ENV['IRB_USE_AUTOCOMPLETE'] = nil - IRB.setup(__FILE__) - assert IRB.conf[:USE_AUTOCOMPLETE] - - ENV['IRB_USE_AUTOCOMPLETE'] = '' - IRB.setup(__FILE__) - assert IRB.conf[:USE_AUTOCOMPLETE] - - ENV['IRB_USE_AUTOCOMPLETE'] = 'false' - IRB.setup(__FILE__) - refute IRB.conf[:USE_AUTOCOMPLETE] - - ENV['IRB_USE_AUTOCOMPLETE'] = 'true' - IRB.setup(__FILE__) - assert IRB.conf[:USE_AUTOCOMPLETE] - ensure - ENV["IRB_USE_AUTOCOMPLETE"] = orig_use_autocomplete_env - IRB.conf[:USE_AUTOCOMPLETE] = orig_use_autocomplete_conf - end - - def test_copy_command_environment_variable - orig_copy_command_env = ENV['IRB_COPY_COMMAND'] - orig_copy_command_conf = IRB.conf[:COPY_COMMAND] - - ENV['IRB_COPY_COMMAND'] = nil - IRB.setup(__FILE__) - refute IRB.conf[:COPY_COMMAND] - - ENV['IRB_COPY_COMMAND'] = '' - IRB.setup(__FILE__) - assert_equal('', IRB.conf[:COPY_COMMAND]) - - ENV['IRB_COPY_COMMAND'] = 'blah' - IRB.setup(__FILE__) - assert_equal('blah', IRB.conf[:COPY_COMMAND]) - ensure - ENV['IRB_COPY_COMMAND'] = orig_copy_command_env - IRB.conf[:COPY_COMMAND] = orig_copy_command_conf - end - - def test_completor_environment_variable - orig_use_autocomplete_env = ENV['IRB_COMPLETOR'] - orig_use_autocomplete_conf = IRB.conf[:COMPLETOR] - - # Default value is nil: auto-detect - ENV['IRB_COMPLETOR'] = nil - IRB.setup(__FILE__) - assert_equal(nil, IRB.conf[:COMPLETOR]) - - ENV['IRB_COMPLETOR'] = 'regexp' - IRB.setup(__FILE__) - assert_equal(:regexp, IRB.conf[:COMPLETOR]) - - ENV['IRB_COMPLETOR'] = 'type' - IRB.setup(__FILE__) - assert_equal(:type, IRB.conf[:COMPLETOR]) - - ENV['IRB_COMPLETOR'] = 'regexp' - IRB.setup(__FILE__, argv: ['--type-completor']) - assert_equal :type, IRB.conf[:COMPLETOR] - - ENV['IRB_COMPLETOR'] = 'type' - IRB.setup(__FILE__, argv: ['--regexp-completor']) - assert_equal :regexp, IRB.conf[:COMPLETOR] - ensure - ENV['IRB_COMPLETOR'] = orig_use_autocomplete_env - IRB.conf[:COMPLETOR] = orig_use_autocomplete_conf - end - - def test_completor_setup_with_argv - orig_completor_conf = IRB.conf[:COMPLETOR] - orig_completor_env = ENV['IRB_COMPLETOR'] - ENV['IRB_COMPLETOR'] = nil - - # Default value is nil: auto-detect - IRB.setup(__FILE__, argv: []) - assert_equal nil, IRB.conf[:COMPLETOR] - - IRB.setup(__FILE__, argv: ['--type-completor']) - assert_equal :type, IRB.conf[:COMPLETOR] - - IRB.setup(__FILE__, argv: ['--regexp-completor']) - assert_equal :regexp, IRB.conf[:COMPLETOR] - ensure - IRB.conf[:COMPLETOR] = orig_completor_conf - ENV['IRB_COMPLETOR'] = orig_completor_env - end - - def test_noscript - argv = %w[--noscript -- -f] - IRB.setup(eval("__FILE__"), argv: argv) - assert_nil IRB.conf[:SCRIPT] - assert_equal(['-f'], argv) - - argv = %w[--noscript -- a] - IRB.setup(eval("__FILE__"), argv: argv) - assert_nil IRB.conf[:SCRIPT] - assert_equal(['a'], argv) - - argv = %w[--noscript a] - IRB.setup(eval("__FILE__"), argv: argv) - assert_nil IRB.conf[:SCRIPT] - assert_equal(['a'], argv) - - argv = %w[--script --noscript a] - IRB.setup(eval("__FILE__"), argv: argv) - assert_nil IRB.conf[:SCRIPT] - assert_equal(['a'], argv) - - argv = %w[--noscript --script a] - IRB.setup(eval("__FILE__"), argv: argv) - assert_equal('a', IRB.conf[:SCRIPT]) - assert_equal([], argv) - end - - def test_dash - argv = %w[-] - IRB.setup(eval("__FILE__"), argv: argv) - assert_equal('-', IRB.conf[:SCRIPT]) - assert_equal([], argv) - - argv = %w[-- -] - IRB.setup(eval("__FILE__"), argv: argv) - assert_equal('-', IRB.conf[:SCRIPT]) - assert_equal([], argv) - - argv = %w[-- - -f] - IRB.setup(eval("__FILE__"), argv: argv) - assert_equal('-', IRB.conf[:SCRIPT]) - 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) - orig = ARGV.dup - ARGV.replace(argv) - yield - ensure - ARGV.replace(orig) - end - end - - class ConfigValidationTest < TestCase - def setup - # To prevent the test from using the user's .irbrc file - @home = Dir.mktmpdir - setup_envs(home: @home) - super - end - - def teardown - super - teardown_envs - File.unlink(@irbrc) - Dir.rmdir(@home) - IRB.instance_variable_set(:@existing_rc_name_generators, nil) - end - - def test_irb_name_converts_non_string_values_to_string - assert_no_irb_validation_error(<<~'RUBY') - IRB.conf[:IRB_NAME] = :foo - RUBY - - assert_equal "foo", IRB.conf[:IRB_NAME] - end - - def test_irb_rc_name_only_takes_callable_objects - assert_irb_validation_error(<<~'RUBY', "IRB.conf[:IRB_RC] should be a callable object. Got :foo.") - IRB.conf[:IRB_RC] = :foo - RUBY - end - - def test_back_trace_limit_only_accepts_integers - assert_irb_validation_error(<<~'RUBY', "IRB.conf[:BACK_TRACE_LIMIT] should be an integer. Got \"foo\".") - IRB.conf[:BACK_TRACE_LIMIT] = "foo" - RUBY - end - - def test_prompt_only_accepts_hash - assert_irb_validation_error(<<~'RUBY', "IRB.conf[:PROMPT] should be a Hash. Got \"foo\".") - IRB.conf[:PROMPT] = "foo" - RUBY - end - - def test_eval_history_only_accepts_integers - assert_irb_validation_error(<<~'RUBY', "IRB.conf[:EVAL_HISTORY] should be an integer. Got \"foo\".") - IRB.conf[:EVAL_HISTORY] = "foo" - RUBY - end - - private - - def assert_irb_validation_error(rc_content, error_message) - write_rc rc_content - - assert_raise_with_message(TypeError, error_message) do - IRB.setup(__FILE__) - end - end - - def assert_no_irb_validation_error(rc_content) - write_rc rc_content - - assert_nothing_raised do - IRB.setup(__FILE__) - end - end - - def write_rc(content) - @irbrc = Tempfile.new('irbrc') - @irbrc.write(content) - @irbrc.close - ENV['IRBRC'] = @irbrc.path - end - end - - class InitIntegrationTest < IntegrationTestCase - def setup - super - - write_ruby <<~'RUBY' - binding.irb - RUBY - end - - def test_load_error_in_rc_file_is_warned - write_rc <<~'IRBRC' - require "file_that_does_not_exist" - IRBRC - - output = run_ruby_file do - type "'foobar'" - type "exit" - end - - # IRB session should still be started - assert_includes output, "foobar" - assert_includes output, 'cannot load such file -- file_that_does_not_exist (LoadError)' - end - - def test_normal_errors_in_rc_file_is_warned - write_rc <<~'IRBRC' - raise "I'm an error" - IRBRC - - output = run_ruby_file do - type "'foobar'" - type "exit" - end - - # IRB session should still be started - assert_includes output, "foobar" - assert_includes output, 'I\'m an error (RuntimeError)' - end - end -end diff --git a/test/irb/test_input_method.rb b/test/irb/test_input_method.rb deleted file mode 100644 index bd107551df..0000000000 --- a/test/irb/test_input_method.rb +++ /dev/null @@ -1,195 +0,0 @@ -# frozen_string_literal: false - -require "irb" -begin - require "rdoc" -rescue LoadError -end -require_relative "helper" - -module TestIRB - class InputMethodTest < TestCase - def setup - @conf_backup = IRB.conf.dup - IRB.init_config(nil) - IRB.conf[:LC_MESSAGES] = IRB::Locale.new - save_encodings - end - - def teardown - IRB.conf.replace(@conf_backup) - restore_encodings - # Reset Reline configuration overridden by RelineInputMethod. - Reline.instance_variable_set(:@core, nil) - end - end - - class RelineInputMethodTest < InputMethodTest - def test_initialization - Reline.completion_proc = nil - Reline.dig_perfect_match_proc = nil - IRB::RelineInputMethod.new(IRB::RegexpCompletor.new) - - assert_nil Reline.completion_append_character - assert_equal '', Reline.completer_quote_characters - assert_equal IRB::InputMethod::BASIC_WORD_BREAK_CHARACTERS, Reline.basic_word_break_characters - assert_not_nil Reline.completion_proc - assert_not_nil Reline.dig_perfect_match_proc - end - - def test_colorize - IRB.conf[:USE_COLORIZE] = true - IRB.conf[:VERBOSE] = false - original_colorable = IRB::Color.method(:colorable?) - IRB::Color.instance_eval { undef :colorable? } - IRB::Color.define_singleton_method(:colorable?) { true } - workspace = IRB::WorkSpace.new(binding) - input_method = IRB::RelineInputMethod.new(IRB::RegexpCompletor.new) - IRB.conf[:MAIN_CONTEXT] = IRB::Irb.new(workspace, input_method).context - assert_equal "\e[1m$\e[0m\e[m", Reline.output_modifier_proc.call('$', complete: false) - assert_equal "\e[1m$\e[0m\e[m \e[34m\e[1m1\e[0m + \e[34m\e[1m2\e[0m", Reline.output_modifier_proc.call('$ 1 + 2', complete: false) - assert_equal "\e[32m\e[1m$a\e[0m", Reline.output_modifier_proc.call('$a', complete: false) - ensure - IRB::Color.instance_eval { undef :colorable? } - IRB::Color.define_singleton_method(:colorable?, original_colorable) - end - - def test_initialization_without_use_autocomplete - original_show_doc_proc = Reline.dialog_proc(:show_doc)&.dialog_proc - empty_proc = Proc.new {} - Reline.add_dialog_proc(:show_doc, empty_proc) - - IRB.conf[:USE_AUTOCOMPLETE] = false - - IRB::RelineInputMethod.new(IRB::RegexpCompletor.new) - - refute Reline.autocompletion - assert_equal empty_proc, Reline.dialog_proc(:show_doc).dialog_proc - ensure - Reline.add_dialog_proc(:show_doc, original_show_doc_proc, Reline::DEFAULT_DIALOG_CONTEXT) - end - - def test_initialization_with_use_autocomplete - omit 'This test requires RDoc' unless defined?(RDoc) - original_show_doc_proc = Reline.dialog_proc(:show_doc)&.dialog_proc - empty_proc = Proc.new {} - Reline.add_dialog_proc(:show_doc, empty_proc) - - IRB.conf[:USE_AUTOCOMPLETE] = true - - IRB::RelineInputMethod.new(IRB::RegexpCompletor.new) - - assert Reline.autocompletion - assert_not_equal empty_proc, Reline.dialog_proc(:show_doc).dialog_proc - ensure - Reline.add_dialog_proc(:show_doc, original_show_doc_proc, Reline::DEFAULT_DIALOG_CONTEXT) - end - - def test_initialization_with_use_autocomplete_but_without_rdoc - original_show_doc_proc = Reline.dialog_proc(:show_doc)&.dialog_proc - empty_proc = Proc.new {} - Reline.add_dialog_proc(:show_doc, empty_proc) - - IRB.conf[:USE_AUTOCOMPLETE] = true - - without_rdoc do - IRB::RelineInputMethod.new(IRB::RegexpCompletor.new) - end - - assert Reline.autocompletion - # doesn't register show_doc dialog - assert_equal empty_proc, Reline.dialog_proc(:show_doc).dialog_proc - ensure - Reline.add_dialog_proc(:show_doc, original_show_doc_proc, Reline::DEFAULT_DIALOG_CONTEXT) - end - end - - class DisplayDocumentTest < InputMethodTest - def setup - super - @driver = RDoc::RI::Driver.new(use_stdout: true) - end - - def display_document(target, bind, driver = nil) - input_method = IRB::RelineInputMethod.new(IRB::RegexpCompletor.new) - 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, @driver) - end - - assert_empty(err) - - assert_include(out, " S\bSt\btr\bri\bin\bng\bg") - end - - def test_perfectly_matched_multiple_namespaces_triggers_document_display - result = nil - out, err = capture_output do - result = display_document("{}.nil?", binding, @driver) - end - - assert_empty(err) - - # check if there're rdoc contents (e.g. CI doesn't generate them) - if has_rdoc_content? - # if there's rdoc content, we can verify by checking stdout - # rdoc generates control characters for formatting method names - assert_include(out, "P\bPr\bro\boc\bc.\b.n\bni\bil\bl?\b?") # Proc.nil? - assert_include(out, "H\bHa\bas\bsh\bh.\b.n\bni\bil\bl?\b?") # Hash.nil? - else - # this is a hacky way to verify the rdoc rendering code path because CI doesn't have rdoc content - # if there are multiple namespaces to be rendered, PerfectMatchedProc renders the result with a document - # which always returns the bytes rendered, even if it's 0 - assert_equal(0, result) - end - end - - def test_not_matched_namespace_triggers_nothing - result = nil - out, err = capture_output do - result = display_document("Stri", binding, @driver) - end - - assert_empty(err) - assert_empty(out) - assert_nil(result) - end - - def test_perfect_matching_stops_without_rdoc - result = nil - - out, err = capture_output do - without_rdoc do - result = display_document("String", binding) - end - end - - assert_empty(err) - assert_not_match(/from ruby core/, out) - assert_nil(result) - end - - 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, @driver)) - end - - assert_empty(err) - assert_empty(out) - end - - private - - def has_rdoc_content? - File.exist?(RDoc::RI::Paths::BASE) - end - end if defined?(RDoc) -end diff --git a/test/irb/test_irb.rb b/test/irb/test_irb.rb deleted file mode 100644 index 617e9c9614..0000000000 --- a/test/irb/test_irb.rb +++ /dev/null @@ -1,936 +0,0 @@ -# frozen_string_literal: true -require "irb" - -require_relative "helper" - -module TestIRB - class InputTest < IntegrationTestCase - def test_symbol_aliases_are_handled_correctly - write_ruby <<~'RUBY' - class Foo - end - binding.irb - RUBY - - output = run_ruby_file do - type "$ Foo" - type "exit!" - end - - assert_include output, "From: #{@ruby_file.path}:1" - end - - def test_symbol_aliases_are_handled_correctly_with_singleline_mode - write_rc <<~RUBY - IRB.conf[:USE_SINGLELINE] = true - RUBY - - write_ruby <<~'RUBY' - class Foo - end - binding.irb - RUBY - - output = run_ruby_file do - type "irb_info" - type "$ Foo" - type "exit!" - end - - # Make sure it's tested in singleline mode - assert_include output, "InputMethod: ReadlineInputMethod" - 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_commands_dont_override_stored_last_result - write_ruby <<~'RUBY' - binding.irb - RUBY - - output = run_ruby_file do - type "1 + 1" - type "ls" - 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.3." - 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" - @bar = "It's a bar" - binding.irb - RUBY - - output = run_ruby_file do - type "$foo" - type "@bar" - type "exit!" - end - - 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}.inspect - assert_include output, {context_restored: true}.inspect - end - end - - class IrbIOConfigurationTest < TestCase - Row = Struct.new(:content, :current_line_spaces, :new_line_spaces, :indent_level) - - class MockIO_AutoIndent - attr_reader :calculated_indent - - def initialize(*params) - @params = params - end - - def auto_indent(&block) - @calculated_indent = block.call(*@params) - end - end - - class MockIO_DynamicPrompt - attr_reader :prompt_list - - def initialize(params, &assertion) - @params = params - end - - def dynamic_prompt(&block) - @prompt_list = block.call(@params) - end - end - - def setup - save_encodings - @irb = build_irb - end - - def teardown - restore_encodings - end - - class AutoIndentationTest < IrbIOConfigurationTest - def test_auto_indent - input_with_correct_indents = [ - [%q(def each_top_level_statement), 0, 2], - [%q( initialize_input), 2, 2], - [%q( catch(:TERM_INPUT) do), 2, 4], - [%q( loop do), 4, 6], - [%q( begin), 6, 8], - [%q( prompt), 8, 8], - [%q( unless l = lex), 8, 10], - [%q( throw :TERM_INPUT if @line == ''), 10, 10], - [%q( else), 8, 10], - [%q( @line_no += l.count("\n")), 10, 10], - [%q( next if l == "\n"), 10, 10], - [%q( @line.concat l), 10, 10], - [%q( if @code_block_open or @ltype or @continue or @indent > 0), 10, 12], - [%q( next), 12, 12], - [%q( end), 10, 10], - [%q( end), 8, 8], - [%q( if @line != "\n"), 8, 10], - [%q( @line.force_encoding(@io.encoding)), 10, 10], - [%q( yield @line, @exp_line_no), 10, 10], - [%q( end), 8, 8], - [%q( break if @io.eof?), 8, 8], - [%q( @line = ''), 8, 8], - [%q( @exp_line_no = @line_no), 8, 8], - [%q( ), nil, 8], - [%q( @indent = 0), 8, 8], - [%q( rescue TerminateLineInput), 6, 8], - [%q( initialize_input), 8, 8], - [%q( prompt), 8, 8], - [%q( end), 6, 6], - [%q( end), 4, 4], - [%q( end), 2, 2], - [%q(end), 0, 0], - ] - - assert_rows_with_correct_indents(input_with_correct_indents) - end - - def test_braces_on_their_own_line - input_with_correct_indents = [ - [%q(if true), 0, 2], - [%q( [), 2, 4], - [%q( ]), 2, 2], - [%q(end), 0, 0], - ] - - assert_rows_with_correct_indents(input_with_correct_indents) - end - - def test_multiple_braces_in_a_line - input_with_correct_indents = [ - [%q([[[), 0, 6], - [%q( ]), 4, 4], - [%q( ]), 2, 2], - [%q(]), 0, 0], - [%q([<<FOO]), 0, 0], - [%q(hello), 0, 0], - [%q(FOO), 0, 0], - ] - - assert_rows_with_correct_indents(input_with_correct_indents) - end - - def test_a_closed_brace_and_not_closed_brace_in_a_line - input_with_correct_indents = [ - [%q(p() {), 0, 2], - [%q(}), 0, 0], - ] - - assert_rows_with_correct_indents(input_with_correct_indents) - end - - def test_symbols - input_with_correct_indents = [ - [%q(:a), 0, 0], - [%q(:A), 0, 0], - [%q(:+), 0, 0], - [%q(:@@a), 0, 0], - [%q(:@a), 0, 0], - [%q(:$a), 0, 0], - [%q(:def), 0, 0], - [%q(:`), 0, 0], - ] - - assert_rows_with_correct_indents(input_with_correct_indents) - end - - def test_incomplete_coding_magic_comment - input_with_correct_indents = [ - [%q(#coding:u), 0, 0], - ] - - assert_rows_with_correct_indents(input_with_correct_indents) - end - - def test_incomplete_encoding_magic_comment - input_with_correct_indents = [ - [%q(#encoding:u), 0, 0], - ] - - assert_rows_with_correct_indents(input_with_correct_indents) - end - - def test_incomplete_emacs_coding_magic_comment - input_with_correct_indents = [ - [%q(# -*- coding: u), 0, 0], - ] - - assert_rows_with_correct_indents(input_with_correct_indents) - end - - def test_incomplete_vim_coding_magic_comment - input_with_correct_indents = [ - [%q(# vim:set fileencoding=u), 0, 0], - ] - - assert_rows_with_correct_indents(input_with_correct_indents) - end - - def test_mixed_rescue - input_with_correct_indents = [ - [%q(def m), 0, 2], - [%q( begin), 2, 4], - [%q( begin), 4, 6], - [%q( x = a rescue 4), 6, 6], - [%q( y = [(a rescue 5)]), 6, 6], - [%q( [x, y]), 6, 6], - [%q( rescue => e), 4, 6], - [%q( raise e rescue 8), 6, 6], - [%q( end), 4, 4], - [%q( rescue), 2, 4], - [%q( raise rescue 11), 4, 4], - [%q( end), 2, 2], - [%q(rescue => e), 0, 2], - [%q( raise e rescue 14), 2, 2], - [%q(end), 0, 0], - ] - - assert_rows_with_correct_indents(input_with_correct_indents) - end - - def test_oneliner_method_definition - input_with_correct_indents = [ - [%q(class A), 0, 2], - [%q( def foo0), 2, 4], - [%q( 3), 4, 4], - [%q( end), 2, 2], - [%q( def foo1()), 2, 4], - [%q( 3), 4, 4], - [%q( end), 2, 2], - [%q( def foo2(a, b)), 2, 4], - [%q( a + b), 4, 4], - [%q( end), 2, 2], - [%q( def foo3 a, b), 2, 4], - [%q( a + b), 4, 4], - [%q( end), 2, 2], - [%q( def bar0() = 3), 2, 2], - [%q( def bar1(a) = a), 2, 2], - [%q( def bar2(a, b) = a + b), 2, 2], - [%q( def bar3() = :s), 2, 2], - [%q( def bar4() = Time.now), 2, 2], - [%q(end), 0, 0], - ] - - assert_rows_with_correct_indents(input_with_correct_indents) - end - - def test_tlambda - input_with_correct_indents = [ - [%q(if true), 0, 2, 1], - [%q( -> {), 2, 4, 2], - [%q( }), 2, 2, 1], - [%q(end), 0, 0, 0], - ] - - assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true) - end - - def test_corresponding_syntax_to_keyword_do_in_class - input_with_correct_indents = [ - [%q(class C), 0, 2, 1], - [%q( while method_name do), 2, 4, 2], - [%q( 3), 4, 4, 2], - [%q( end), 2, 2, 1], - [%q( foo do), 2, 4, 2], - [%q( 3), 4, 4, 2], - [%q( end), 2, 2, 1], - [%q(end), 0, 0, 0], - ] - - assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true) - end - - def test_corresponding_syntax_to_keyword_do - input_with_correct_indents = [ - [%q(while i > 0), 0, 2, 1], - [%q( 3), 2, 2, 1], - [%q(end), 0, 0, 0], - [%q(while true), 0, 2, 1], - [%q( 3), 2, 2, 1], - [%q(end), 0, 0, 0], - [%q(while ->{i > 0}.call), 0, 2, 1], - [%q( 3), 2, 2, 1], - [%q(end), 0, 0, 0], - [%q(while ->{true}.call), 0, 2, 1], - [%q( 3), 2, 2, 1], - [%q(end), 0, 0, 0], - [%q(while i > 0 do), 0, 2, 1], - [%q( 3), 2, 2, 1], - [%q(end), 0, 0, 0], - [%q(while true do), 0, 2, 1], - [%q( 3), 2, 2, 1], - [%q(end), 0, 0, 0], - [%q(while ->{i > 0}.call do), 0, 2, 1], - [%q( 3), 2, 2, 1], - [%q(end), 0, 0, 0], - [%q(while ->{true}.call do), 0, 2, 1], - [%q( 3), 2, 2, 1], - [%q(end), 0, 0, 0], - [%q(foo do), 0, 2, 1], - [%q( 3), 2, 2, 1], - [%q(end), 0, 0, 0], - [%q(foo true do), 0, 2, 1], - [%q( 3), 2, 2, 1], - [%q(end), 0, 0, 0], - [%q(foo ->{true} do), 0, 2, 1], - [%q( 3), 2, 2, 1], - [%q(end), 0, 0, 0], - [%q(foo ->{i > 0} do), 0, 2, 1], - [%q( 3), 2, 2, 1], - [%q(end), 0, 0, 0], - ] - - assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true) - end - - def test_corresponding_syntax_to_keyword_for - input_with_correct_indents = [ - [%q(for i in [1]), 0, 2, 1], - [%q( puts i), 2, 2, 1], - [%q(end), 0, 0, 0], - ] - - assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true) - end - - def test_corresponding_syntax_to_keyword_for_with_do - input_with_correct_indents = [ - [%q(for i in [1] do), 0, 2, 1], - [%q( puts i), 2, 2, 1], - [%q(end), 0, 0, 0], - ] - - assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true) - end - - def test_typing_incomplete_include_interpreted_as_keyword_in - input_with_correct_indents = [ - [%q(module E), 0, 2, 1], - [%q(end), 0, 0, 0], - [%q(class A), 0, 2, 1], - [%q( in), 2, 2, 1] # scenario typing `include E` - ] - - assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true) - - end - - def test_bracket_corresponding_to_times - input_with_correct_indents = [ - [%q(3.times { |i|), 0, 2, 1], - [%q( puts i), 2, 2, 1], - [%q(}), 0, 0, 0], - ] - - assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true) - end - - def test_do_corresponding_to_times - input_with_correct_indents = [ - [%q(3.times do |i|), 0, 2, 1], - [%q( puts i), 2, 2, 1], - [%q(end), 0, 0, 0], - ] - - assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true) - end - - def test_bracket_corresponding_to_loop - input_with_correct_indents = [ - ['loop {', 0, 2, 1], - [' 3', 2, 2, 1], - ['}', 0, 0, 0], - ] - - assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true) - end - - def test_do_corresponding_to_loop - input_with_correct_indents = [ - [%q(loop do), 0, 2, 1], - [%q( 3), 2, 2, 1], - [%q(end), 0, 0, 0], - ] - - assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true) - end - - def test_embdoc_indent - input_with_correct_indents = [ - [%q(=begin), 0, 0, 0], - [%q(a), 0, 0, 0], - [%q( b), 1, 1, 0], - [%q(=end), 0, 0, 0], - [%q(if 1), 0, 2, 1], - [%q( 2), 2, 2, 1], - [%q(=begin), 0, 0, 0], - [%q(a), 0, 0, 0], - [%q( b), 1, 1, 0], - [%q(=end), 0, 2, 1], - [%q( 3), 2, 2, 1], - [%q(end), 0, 0, 0], - ] - - assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true) - end - - def test_heredoc_with_indent - input_with_correct_indents = [ - [%q(<<~Q+<<~R), 0, 2, 1], - [%q(a), 2, 2, 1], - [%q(a), 2, 2, 1], - [%q( b), 2, 2, 1], - [%q( b), 2, 2, 1], - [%q( Q), 0, 2, 1], - [%q( c), 4, 4, 1], - [%q( c), 4, 4, 1], - [%q( R), 0, 0, 0], - ] - - assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true) - end - - def test_oneliner_def_in_multiple_lines - input_with_correct_indents = [ - [%q(def a()=[), 0, 2, 1], - [%q( 1,), 2, 2, 1], - [%q(].), 0, 0, 0], - [%q(to_s), 0, 0, 0], - ] - - assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true) - end - - def test_broken_heredoc - input_with_correct_indents = [ - [%q(def foo), 0, 2, 1], - [%q( <<~Q), 2, 4, 2], - [%q( Qend), 4, 4, 2], - ] - - assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true) - end - - def test_pasted_code_keep_base_indent_spaces - input_with_correct_indents = [ - [%q( def foo), 0, 6, 1], - [%q( if bar), 6, 10, 2], - [%q( [1), 10, 12, 3], - [%q( ]+[["a), 10, 14, 4], - [%q(b" + `c), 0, 14, 4], - [%q(d` + /e), 0, 14, 4], - [%q(f/ + :"g), 0, 14, 4], - [%q(h".tap do), 0, 16, 5], - [%q( 1), 16, 16, 5], - [%q( end), 14, 14, 4], - [%q( ]), 12, 12, 3], - [%q( ]), 10, 10, 2], - [%q( end), 8, 6, 1], - [%q( end), 4, 0, 0], - ] - - assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true) - end - - def test_pasted_code_keep_base_indent_spaces_with_heredoc - input_with_correct_indents = [ - [%q( def foo), 0, 6, 1], - [%q( if bar), 6, 10, 2], - [%q( [1), 10, 12, 3], - [%q( ]+[["a), 10, 14, 4], - [%q(b" + <<~A + <<-B + <<C), 0, 16, 5], - [%q( a#{), 16, 18, 6], - [%q( 1), 18, 18, 6], - [%q( }), 16, 16, 5], - [%q( A), 14, 16, 5], - [%q( b#{), 16, 18, 6], - [%q( 1), 18, 18, 6], - [%q( }), 16, 16, 5], - [%q( B), 14, 0, 0], - [%q(c#{), 0, 2, 1], - [%q(1), 2, 2, 1], - [%q(}), 0, 0, 0], - [%q(C), 0, 14, 4], - [%q( ]), 12, 12, 3], - [%q( ]), 10, 10, 2], - [%q( end), 8, 6, 1], - [%q( end), 4, 0, 0], - ] - - assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true) - end - - def test_heredoc_keep_indent_spaces - (1..4).each do |indent| - row = Row.new(' ' * indent, nil, [4, indent].max, 2) - lines = ['def foo', ' <<~Q', row.content] - assert_row_indenting(lines, row) - assert_indent_level(lines, row.indent_level) - end - end - - private - - def assert_row_indenting(lines, row) - actual_current_line_spaces = calculate_indenting(lines, false) - - error_message = <<~MSG - Incorrect spaces calculation for line: - - ``` - > #{lines.last} - ``` - - All lines: - - ``` - #{lines.join("\n")} - ``` - MSG - assert_equal(row.current_line_spaces, actual_current_line_spaces, error_message) - - error_message = <<~MSG - Incorrect spaces calculation for line after the current line: - - ``` - #{lines.last} - > - ``` - - All lines: - - ``` - #{lines.join("\n")} - ``` - MSG - actual_next_line_spaces = calculate_indenting(lines, true) - assert_equal(row.new_line_spaces, actual_next_line_spaces, error_message) - end - - def assert_rows_with_correct_indents(rows_with_spaces, assert_indent_level: false) - lines = [] - rows_with_spaces.map do |row| - row = Row.new(*row) - lines << row.content - assert_row_indenting(lines, row) - - if assert_indent_level - assert_indent_level(lines, row.indent_level) - end - end - end - - def assert_indent_level(lines, expected) - code = lines.map { |l| "#{l}\n" }.join # code should end with "\n" - _tokens, opens, _ = @irb.scanner.check_code_state(code, local_variables: []) - indent_level = @irb.scanner.calc_indent_level(opens) - error_message = "Calculated the wrong number of indent level for:\n #{lines.join("\n")}" - assert_equal(expected, indent_level, error_message) - end - - def calculate_indenting(lines, add_new_line) - lines = lines + [""] if add_new_line - last_line_index = lines.length - 1 - byte_pointer = lines.last.length - - mock_io = MockIO_AutoIndent.new(lines, last_line_index, byte_pointer, add_new_line) - @irb.context.auto_indent_mode = true - @irb.context.io = mock_io - @irb.configure_io - - mock_io.calculated_indent - end - end - - class DynamicPromptTest < IrbIOConfigurationTest - def test_endless_range_at_end_of_line - input_with_prompt = [ - ['001:0: :> ', %q(a = 3..)], - ['002:0: :> ', %q()], - ] - - assert_dynamic_prompt(input_with_prompt) - end - - def test_heredoc_with_embexpr - input_with_prompt = [ - ['001:0:":* ', %q(<<A+%W[#{<<B)], - ['002:0:":* ', %q(#{<<C+%W[)], - ['003:0:":* ', %q(a)], - ['004:2:]:* ', %q(C)], - ['005:2:]:* ', %q(a)], - ['006:0:":* ', %q(]})], - ['007:0:":* ', %q(})], - ['008:0:":* ', %q(A)], - ['009:2:]:* ', %q(B)], - ['010:1:]:* ', %q(})], - ['011:0: :> ', %q(])], - ['012:0: :> ', %q()], - ] - - assert_dynamic_prompt(input_with_prompt) - end - - def test_heredoc_prompt_with_quotes - input_with_prompt = [ - ["001:1:':* ", %q(<<~'A')], - ["002:1:':* ", %q(#{foobar})], - ["003:0: :> ", %q(A)], - ["004:1:`:* ", %q(<<~`A`)], - ["005:1:`:* ", %q(whoami)], - ["006:0: :> ", %q(A)], - ['007:1:":* ', %q(<<~"A")], - ['008:1:":* ', %q(foobar)], - ['009:0: :> ', %q(A)], - ] - - assert_dynamic_prompt(input_with_prompt) - end - - def test_backtick_method - input_with_prompt = [ - ['001:0: :> ', %q(self.`(arg))], - ['002:0: :> ', %q()], - ['003:0: :> ', %q(def `(); end)], - ['004:0: :> ', %q()], - ] - - assert_dynamic_prompt(input_with_prompt) - end - - def test_dynamic_prompt - input_with_prompt = [ - ['001:1: :* ', %q(def hoge)], - ['002:1: :* ', %q( 3)], - ['003:0: :> ', %q(end)], - ] - - assert_dynamic_prompt(input_with_prompt) - end - - def test_dynamic_prompt_with_double_newline_breaking_code - input_with_prompt = [ - ['001:1: :* ', %q(if true)], - ['002:2: :* ', %q(%)], - ['003:1: :* ', %q(;end)], - ['004:1: :* ', %q(;hello)], - ['005:0: :> ', %q(end)], - ] - - assert_dynamic_prompt(input_with_prompt) - end - - def test_dynamic_prompt_with_multiline_literal - input_with_prompt = [ - ['001:1: :* ', %q(if true)], - ['002:2:]:* ', %q( %w[)], - ['003:2:]:* ', %q( a)], - ['004:1: :* ', %q( ])], - ['005:1: :* ', %q( b)], - ['006:2:]:* ', %q( %w[)], - ['007:2:]:* ', %q( c)], - ['008:1: :* ', %q( ])], - ['009:0: :> ', %q(end)], - ] - - assert_dynamic_prompt(input_with_prompt) - end - - def test_dynamic_prompt_with_blank_line - input_with_prompt = [ - ['001:1:]:* ', %q(%w[)], - ['002:1:]:* ', %q()], - ['003:0: :> ', %q(])], - ] - - assert_dynamic_prompt(input_with_prompt) - end - - def assert_dynamic_prompt(input_with_prompt) - expected_prompt_list, lines = input_with_prompt.transpose - def @irb.generate_prompt(opens, continue, line_offset) - ltype = @scanner.ltype_from_open_tokens(opens) - indent = @scanner.calc_indent_level(opens) - continue = opens.any? || continue - line_no = @line_no + line_offset - '%03d:%01d:%1s:%s ' % [line_no, indent, ltype, continue ? '*' : '>'] - end - io = MockIO_DynamicPrompt.new(lines) - @irb.context.io = io - @irb.configure_io - - error_message = <<~EOM - Expected dynamic prompt: - #{expected_prompt_list.join("\n")} - - Actual dynamic prompt: - #{io.prompt_list.join("\n")} - EOM - assert_equal(expected_prompt_list, io.prompt_list, error_message) - end - end - - private - - def build_binding - Object.new.instance_eval { binding } - end - - def build_irb - IRB.init_config(nil) - workspace = IRB::WorkSpace.new(build_binding) - - IRB.conf[:VERBOSE] = false - IRB::Irb.new(workspace, TestInputMethod.new) - end - end - - class BacktraceFilteringTest < TestIRB::IntegrationTestCase - def setup - super - # These tests are sensitive to warnings, so we disable them - original_rubyopt = [ENV["RUBYOPT"], @envs["RUBYOPT"]].compact.join(" ") - @envs["RUBYOPT"] = original_rubyopt + " -W0" - end - - 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_locale.rb b/test/irb/test_locale.rb deleted file mode 100644 index 930a38834c..0000000000 --- a/test/irb/test_locale.rb +++ /dev/null @@ -1,118 +0,0 @@ -require "irb" -require "stringio" - -require_relative "helper" - -module TestIRB - class LocaleTestCase < TestCase - def test_initialize_with_en - locale = IRB::Locale.new("en_US.UTF-8") - - assert_equal("en", locale.lang) - assert_equal("US", locale.territory) - assert_equal("UTF-8", locale.encoding.name) - assert_equal(nil, locale.modifier) - end - - def test_initialize_with_ja - locale = IRB::Locale.new("ja_JP.UTF-8") - - assert_equal("ja", locale.lang) - assert_equal("JP", locale.territory) - assert_equal("UTF-8", locale.encoding.name) - assert_equal(nil, locale.modifier) - end - - def test_initialize_with_legacy_ja_encoding_ujis - original_stderr = $stderr - $stderr = StringIO.new - - locale = IRB::Locale.new("ja_JP.ujis") - - assert_equal("ja", locale.lang) - assert_equal("JP", locale.territory) - assert_equal(Encoding::EUC_JP, locale.encoding) - assert_equal(nil, locale.modifier) - - assert_include $stderr.string, "ja_JP.ujis is obsolete. use ja_JP.EUC-JP" - ensure - $stderr = original_stderr - end - - def test_initialize_with_legacy_ja_encoding_euc - original_stderr = $stderr - $stderr = StringIO.new - - locale = IRB::Locale.new("ja_JP.euc") - - assert_equal("ja", locale.lang) - assert_equal("JP", locale.territory) - assert_equal(Encoding::EUC_JP, locale.encoding) - assert_equal(nil, locale.modifier) - - assert_include $stderr.string, "ja_JP.euc is obsolete. use ja_JP.EUC-JP" - ensure - $stderr = original_stderr - end - - %w(IRB_LANG LC_MESSAGES LC_ALL LANG).each do |env_var| - define_method "test_initialize_with_#{env_var.downcase}" do - original_values = { - "IRB_LANG" => ENV["IRB_LANG"], - "LC_MESSAGES" => ENV["LC_MESSAGES"], - "LC_ALL" => ENV["LC_ALL"], - "LANG" => ENV["LANG"], - } - - ENV["IRB_LANG"] = ENV["LC_MESSAGES"] = ENV["LC_ALL"] = ENV["LANG"] = nil - ENV[env_var] = "zh_TW.UTF-8" - - locale = IRB::Locale.new - - assert_equal("zh", locale.lang) - assert_equal("TW", locale.territory) - assert_equal("UTF-8", locale.encoding.name) - assert_equal(nil, locale.modifier) - ensure - original_values.each do |key, value| - ENV[key] = value - end - end - end - - def test_load - # reset Locale's internal cache - IRB::Locale.class_variable_set(:@@loaded, []) - # Because error.rb files define the same class, loading them causes method redefinition warnings. - original_verbose = $VERBOSE - $VERBOSE = nil - - jp_local = IRB::Locale.new("ja_JP.UTF-8") - jp_local.load("irb/error.rb") - msg = IRB::CantReturnToNormalMode.new.message - assert_equal("Normalモードに戻れません.", msg) - - # reset Locale's internal cache - IRB::Locale.class_variable_set(:@@loaded, []) - - en_local = IRB::Locale.new("en_US.UTF-8") - en_local.load("irb/error.rb") - msg = IRB::CantReturnToNormalMode.new.message - assert_equal("Can't return to normal mode.", msg) - ensure - # before turning warnings back on, load the error.rb file again to avoid warnings in other tests - IRB::Locale.new.load("irb/error.rb") - $VERBOSE = original_verbose - end - - def test_find - jp_local = IRB::Locale.new("ja_JP.UTF-8") - path = jp_local.find("irb/error.rb") - assert_include(path, "/lib/irb/lc/ja/error.rb") - - en_local = IRB::Locale.new("en_US.UTF-8") - path = en_local.find("irb/error.rb") - assert_include(path, "/lib/irb/lc/error.rb") - end - end -end diff --git a/test/irb/test_nesting_parser.rb b/test/irb/test_nesting_parser.rb deleted file mode 100644 index 6b4f54ee21..0000000000 --- a/test/irb/test_nesting_parser.rb +++ /dev/null @@ -1,339 +0,0 @@ -# frozen_string_literal: false -require 'irb' - -require_relative "helper" - -module TestIRB - class NestingParserTest < TestCase - def setup - save_encodings - end - - def teardown - restore_encodings - end - - def parse_by_line(code) - IRB::NestingParser.parse_by_line(IRB::RubyLex.ripper_lex_without_warning(code)) - end - - def test_open_tokens - code = <<~'EOS' - class A - def f - if true - tap do - { - x: " - #{p(1, 2, 3 - EOS - opens = IRB::NestingParser.open_tokens(IRB::RubyLex.ripper_lex_without_warning(code)) - assert_equal(%w[class def if do { " #{ (], opens.map(&:tok)) - end - - def test_parse_by_line - code = <<~EOS - (((((1+2 - ).to_s())).tap do ((( - EOS - _tokens, prev_opens, next_opens, min_depth = parse_by_line(code).last - assert_equal(%w[( ( ( ( (], prev_opens.map(&:tok)) - assert_equal(%w[( ( do ( ( (], next_opens.map(&:tok)) - assert_equal(2, min_depth) - end - - def test_ruby_syntax - code = <<~'EOS' - class A - 1 if 2 - 1 while 2 - 1 until 2 - 1 unless 2 - 1 rescue 2 - begin; rescue; ensure; end - tap do; rescue; ensure; end - class B; end - module C; end - def f; end - def `; end - def f() = 1 - %(); %w[]; %q(); %r{}; %i[] - "#{1}"; ''; /#{1}/; `#{1}` - p(``); p ``; p x: ``; p 1, ``; - :sym; :"sym"; :+; :`; :if - [1, 2, 3] - { x: 1, y: 2 } - (a, (*b, c), d), e = 1, 2, 3 - ->(a){}; ->(a) do end - -> a = -> b = :do do end do end - if 1; elsif 2; else; end - unless 1; end - while 1; end - until 1; end - for i in j; end - case 1; when 2; end - puts(1, 2, 3) - loop{|i|} - loop do |i| end - end - EOS - line_results = parse_by_line(code) - assert_equal(code.lines.size, line_results.size) - class_open, *inner_line_results, class_close = line_results - assert_equal(['class'], class_open[2].map(&:tok)) - inner_line_results.each {|result| assert_equal(['class'], result[2].map(&:tok)) } - assert_equal([], class_close[2].map(&:tok)) - end - - def test_multiline_string - code = <<~EOS - " - aaa - bbb - " - <<A - aaa - bbb - A - EOS - line_results = parse_by_line(code) - assert_equal(code.lines.size, line_results.size) - string_content_line, string_opens = line_results[1] - assert_equal("\naaa\nbbb\n", string_content_line.first.first.tok) - assert_equal("aaa\n", string_content_line.first.last) - assert_equal(['"'], string_opens.map(&:tok)) - heredoc_content_line, heredoc_opens = line_results[6] - assert_equal("aaa\nbbb\n", heredoc_content_line.first.first.tok) - assert_equal("bbb\n", heredoc_content_line.first.last) - assert_equal(['<<A'], heredoc_opens.map(&:tok)) - _line, _prev_opens, next_opens, _min_depth = line_results.last - assert_equal([], next_opens) - end - - def test_backslash_continued_nested_symbol - code = <<~'EOS' - x = <<A, :\ - heredoc #{ - here - } - A - =begin - embdoc - =end - # comment - - if # this is symbol :if - while - EOS - line_results = parse_by_line(code) - assert_equal(%w[: <<A #{], line_results[2][2].map(&:tok)) - assert_equal(%w[while], line_results.last[2].map(&:tok)) - end - - def test_oneliner_def - code = <<~EOC - if true - # normal oneliner def - def f = 1 - def f() = 1 - def f(*) = 1 - # keyword, backtick, op - def * = 1 - def ` = 1 - def if = 1 - def *() = 1 - def `() = 1 - def if() = 1 - # oneliner def with receiver - def a.* = 1 - def $a.* = 1 - def @a.` = 1 - def A.` = 1 - def ((a;b;c)).*() = 1 - def ((a;b;c)).if() = 1 - def ((a;b;c)).end() = 1 - # multiline oneliner def - def f = - 1 - def f() - = - 1 - # oneliner def with comment and embdoc - def # comment - =begin - embdoc - =end - ((a;b;c)) - . # comment - =begin - embdoc - =end - f (*) # comment - =begin - embdoc - =end - = - 1 - # nested oneliner def - def f(x = def f() = 1) = def f() = 1 - EOC - _tokens, _prev_opens, next_opens, min_depth = parse_by_line(code).last - assert_equal(['if'], next_opens.map(&:tok)) - assert_equal(1, min_depth) - end - - def test_heredoc_embexpr - code = <<~'EOS' - <<A+<<B+<<C+(<<D+(<<E) - #{ - <<~F+"#{<<~G} - #{ - here - } - F - G - " - } - A - B - C - D - E - ) - EOS - line_results = parse_by_line(code) - last_opens = line_results.last[-2] - assert_equal([], last_opens) - _tokens, _prev_opens, next_opens, _min_depth = line_results[4] - assert_equal(%w[( <<E <<D <<C <<B <<A #{ " <<~G <<~F #{], next_opens.map(&:tok)) - end - - def test_for_in - code = <<~EOS - for i in j - here - end - for i in j do - here - end - for i in - j do - here - end - for - # comment - i in j do - here - end - for (a;b;c).d in (a;b;c) do - here - end - for i in :in + :do do - here - end - for i in -> do end do - here - end - EOS - line_results = parse_by_line(code).select { |tokens,| tokens.map(&:last).include?('here') } - assert_equal(7, line_results.size) - line_results.each do |_tokens, _prev_opens, next_opens, _min_depth| - assert_equal(['for'], next_opens.map(&:tok)) - end - end - - def test_while_until - base_code = <<~'EOS' - while_or_until true - here - end - while_or_until a < c - here - end - while_or_until true do - here - end - while_or_until - # comment - (a + b) < - # comment - c do - here - end - while_or_until :\ - do do - here - end - while_or_until def do; end == :do do - here - end - while_or_until -> do end do - here - end - EOS - %w[while until].each do |keyword| - code = base_code.gsub('while_or_until', keyword) - line_results = parse_by_line(code).select { |tokens,| tokens.map(&:last).include?('here') } - assert_equal(7, line_results.size) - line_results.each do |_tokens, _prev_opens, next_opens, _min_depth| - assert_equal([keyword], next_opens.map(&:tok) ) - end - 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 - code = <<~EOS - case 1 - in 1 - here - in - 2 - here - end - EOS - line_results = parse_by_line(code).select { |tokens,| tokens.map(&:last).include?('here') } - assert_equal(2, line_results.size) - line_results.each do |_tokens, _prev_opens, next_opens, _min_depth| - assert_equal(['in'], next_opens.map(&:tok)) - end - end - end -end diff --git a/test/irb/test_option.rb b/test/irb/test_option.rb deleted file mode 100644 index fec31f384f..0000000000 --- a/test/irb/test_option.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: false -require_relative "helper" - -module TestIRB - class OptionTest < TestCase - def test_end_of_option - bug4117 = '[ruby-core:33574]' - bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : [] - status = assert_in_out_err(bundle_exec + %w[-W0 -rirb -e IRB.start(__FILE__) -- -f --], "", //, [], bug4117) - assert(status.success?, bug4117) - end - end -end diff --git a/test/irb/test_pager.rb b/test/irb/test_pager.rb deleted file mode 100644 index 0fad94da30..0000000000 --- a/test/irb/test_pager.rb +++ /dev/null @@ -1,74 +0,0 @@ -# frozen_string_literal: false -require 'irb/pager' - -require_relative 'helper' - -module TestIRB - class PagerTest < TestCase - def test_take_first_page - assert_equal ['a' * 40, true], IRB::Pager.take_first_page(10, 4) {|io| io.puts 'a' * 41; raise 'should not reach here' } - assert_equal ["a\nb\na\nb\n", true], IRB::Pager.take_first_page(10, 4) {|io| 10.times { io.puts "a\nb\n" } } - assert_equal ["a\n\n\na\n", true], IRB::Pager.take_first_page(10, 4) {|io| 10.times { io.puts "a\n\n\n" } } - assert_equal ["11\n" * 4, true], IRB::Pager.take_first_page(10, 4) {|io| 10.times { io.write 1; io.puts 1 } } - assert_equal ["\n" * 4, true], IRB::Pager.take_first_page(10, 4) {|io| 10.times { io.write nil; io.puts nil } } - assert_equal ['a' * 39, false], IRB::Pager.take_first_page(10, 4) {|io| io.write 'a' * 39 } - assert_equal ['a' * 39 + 'b', false], IRB::Pager.take_first_page(10, 4) {|io| io.write 'a' * 39 + 'b' } - assert_equal ['a' * 39 + 'b', true], IRB::Pager.take_first_page(10, 4) {|io| io.write 'a' * 39 + 'bc' } - assert_equal ["a\nb\nc\nd\n", false], IRB::Pager.take_first_page(10, 4) {|io| io.write "a\nb\nc\nd\n" } - assert_equal ["a\nb\nc\nd\n", true], IRB::Pager.take_first_page(10, 4) {|io| io.write "a\nb\nc\nd\ne" } - assert_equal ['a' * 15 + "\n" + 'b' * 20, true], IRB::Pager.take_first_page(10, 4) {|io| io.puts 'a' * 15; io.puts 'b' * 30 } - assert_equal ["\e[31mA\e[0m" * 10 + 'x' * 30, true], IRB::Pager.take_first_page(10, 4) {|io| io.puts "\e[31mA\e[0m" * 10 + 'x' * 31 } - text, overflow = IRB::Pager.take_first_page(10, 4) {|io| 41.times { io.write "\e[31mA\e[0m" } } - assert_equal ['A' * 40, true], [text.gsub(/\e\[\d+m/, ''), overflow] - text, overflow = IRB::Pager.take_first_page(10, 4) {|io| 41.times { io.write "\e[31mAAA\e[0m" } } - assert_equal ['A' * 40, true], [text.gsub(/\e\[\d+m/, ''), overflow] - end - end - - class PageOverflowIOTest < TestCase - def test_overflow - actual_events = [] - overflow_callback = ->(lines) do - actual_events << [:callback_called, lines] - end - out = IRB::Pager::PageOverflowIO.new(10, 4, overflow_callback) - out.puts 'a' * 15 - out.write 'b' * 15 - - actual_events << :before_write - out.write 'c' * 1000 - actual_events << :after_write - - out.puts 'd' * 1000 - out.write 'e' * 1000 - - expected_events = [ - :before_write, - [:callback_called, ['a' * 10, 'a' * 5 + "\n", 'b' * 10, 'b' * 5 + 'c' * 5]], - :after_write, - ] - assert_equal expected_events, actual_events - - expected_whole_content = 'a' * 15 + "\n" + 'b' * 15 + 'c' * 1000 + 'd' * 1000 + "\n" + 'e' * 1000 - assert_equal expected_whole_content, out.string - end - - def test_callback_delay - actual_events = [] - overflow_callback = ->(lines) do - actual_events << [:callback_called, lines] - end - out = IRB::Pager::PageOverflowIO.new(10, 4, overflow_callback, delay: 0.2) - out.write 'a' * 1000 - assert_equal ['a' * 10] * 4, out.first_page_lines - out.write 'b' - actual_events << :before_delay - sleep 0.2 - out.write 'c' - actual_events << :after_delay - out.write 'd' - assert_equal 'a' * 1000 + 'bcd', out.string - assert_equal [:before_delay, [:callback_called, ['a' * 10] * 4], :after_delay], actual_events - end - end -end diff --git a/test/irb/test_raise_exception.rb b/test/irb/test_raise_exception.rb deleted file mode 100644 index 44a5ae87e1..0000000000 --- a/test/irb/test_raise_exception.rb +++ /dev/null @@ -1,74 +0,0 @@ -# frozen_string_literal: false -require "tmpdir" - -require_relative "helper" - -module TestIRB - class RaiseExceptionTest < TestCase - def test_raise_exception_with_nil_backtrace - bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : [] - assert_in_out_err(bundle_exec + %w[-rirb -W0 -e IRB.start(__FILE__) -- -f --], <<-IRB, /#<Exception: foo>/, []) - raise Exception.new("foo").tap {|e| def e.backtrace; nil; end } -IRB - end - - def test_raise_exception_with_message_exception - bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : [] - expected = /#<Exception: foo>\nbacktraces are hidden because bar was raised when processing them/ - assert_in_out_err(bundle_exec + %w[-rirb -W0 -e IRB.start(__FILE__) -- -f --], <<-IRB, expected, []) - e = Exception.new("foo") - def e.message; raise 'bar'; end - raise e -IRB - end - - def test_raise_exception_with_message_inspect_exception - bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : [] - expected = /Uninspectable exception occurred/ - assert_in_out_err(bundle_exec + %w[-rirb -W0 -e IRB.start(__FILE__) -- -f --], <<-IRB, expected, []) - e = Exception.new("foo") - def e.message; raise; end - def e.inspect; raise; end - raise e -IRB - end - - def test_raise_exception_with_invalid_byte_sequence - pend if RUBY_ENGINE == 'truffleruby' || /mswin|mingw/ =~ RUBY_PLATFORM - bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : [] - assert_in_out_err(bundle_exec + %w[-rirb -W0 -e IRB.start(__FILE__) -- -f --], <<~IRB, /A\\xF3B \(StandardError\)/, []) - raise StandardError, "A\\xf3B" - IRB - end - - def test_raise_exception_with_different_encoding_containing_invalid_byte_sequence - backup_home = ENV["HOME"] - Dir.mktmpdir("test_irb_raise_no_backtrace_exception_#{$$}") do |tmpdir| - ENV["HOME"] = tmpdir - - bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : [] - File.open("#{tmpdir}/euc.rb", 'w') do |f| - f.write(<<~EOF) - # encoding: euc-jp - - def raise_euc_with_invalid_byte_sequence - raise "\xA4\xA2\\xFF" - end - EOF - end - env = {} - %w(LC_MESSAGES LC_ALL LC_CTYPE LANG).each {|n| env[n] = "ja_JP.UTF-8" } - # 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\)/ - assert_in_out_err(args, <<~IRB, error, [], encoding: "UTF-8") - require_relative 'euc' - raise_euc_with_invalid_byte_sequence - IRB - end - ensure - ENV["HOME"] = backup_home - end - end -end diff --git a/test/irb/test_ruby_lex.rb b/test/irb/test_ruby_lex.rb deleted file mode 100644 index 4e406a8ce0..0000000000 --- a/test/irb/test_ruby_lex.rb +++ /dev/null @@ -1,242 +0,0 @@ -# frozen_string_literal: true -require "irb" - -require_relative "helper" - -module TestIRB - class RubyLexTest < TestCase - def setup - save_encodings - end - - def teardown - restore_encodings - end - - def test_interpolate_token_with_heredoc_and_unclosed_embexpr - code = <<~'EOC' - ①+<<A-② - #{③*<<B/④ - #{⑤&<<C|⑥ - EOC - ripper_tokens = Ripper.tokenize(code) - rubylex_tokens = IRB::RubyLex.ripper_lex_without_warning(code) - # Assert no missing part - assert_equal(code, rubylex_tokens.map(&:tok).join) - # Assert ripper tokens are not removed - ripper_tokens.each do |tok| - assert(rubylex_tokens.any? { |t| t.tok == tok && t.tok != :on_ignored_by_ripper }) - end - # Assert interpolated token position - rubylex_tokens.each do |t| - row, col = t.pos - assert_equal t.tok, code.lines[row - 1].byteslice(col, t.tok.bytesize) - end - end - - def test_local_variables_dependent_code - lines = ["a /1#/ do", "2"] - assert_indent_level(lines, 1) - assert_code_block_open(lines, true) - assert_indent_level(lines, 0, local_variables: ['a']) - assert_code_block_open(lines, false, local_variables: ['a']) - end - - def test_literal_ends_with_space - assert_code_block_open(['% a'], true) - assert_code_block_open(['% a '], false) - end - - def test_literal_ends_with_newline - assert_code_block_open(['%'], true) - assert_code_block_open(['%', ''], false) - end - - def test_should_continue - assert_should_continue(['a'], false) - assert_should_continue(['/a/'], false) - assert_should_continue(['a;'], false) - assert_should_continue(['<<A', 'A'], false) - assert_should_continue(['a...'], false) - assert_should_continue(['a\\'], true) - assert_should_continue(['a.'], true) - assert_should_continue(['a+'], true) - assert_should_continue(['a; #comment', '', '=begin', 'embdoc', '=end', ''], false) - assert_should_continue(['a+ #comment', '', '=begin', 'embdoc', '=end', ''], true) - end - - def test_code_block_open_with_should_continue - # syntax ok - assert_code_block_open(['a'], false) # continue: false - assert_code_block_open(['a\\'], true) # continue: true - - # recoverable syntax error code is not terminated - assert_code_block_open(['a+'], true) - - # unrecoverable syntax error code is terminated - assert_code_block_open(['.; a+'], false) - - # other syntax error that failed to determine if it is recoverable or not - assert_code_block_open(['@; a'], false) - assert_code_block_open(['@; a+'], true) - assert_code_block_open(['@; (a'], true) - end - - def test_broken_percent_literal - tokens = IRB::RubyLex.ripper_lex_without_warning('%wwww') - pos_to_index = {} - tokens.each_with_index { |t, i| - assert_nil(pos_to_index[t.pos], "There is already another token in the position of #{t.inspect}.") - pos_to_index[t.pos] = i - } - end - - def test_broken_percent_literal_in_method - tokens = IRB::RubyLex.ripper_lex_without_warning(<<~EOC.chomp) - def foo - %wwww - end - EOC - pos_to_index = {} - tokens.each_with_index { |t, i| - assert_nil(pos_to_index[t.pos], "There is already another token in the position of #{t.inspect}.") - pos_to_index[t.pos] = i - } - end - - def test_unterminated_code - ['do', '<<A'].each do |code| - tokens = IRB::RubyLex.ripper_lex_without_warning(code) - assert_equal(code, tokens.map(&:tok).join, "Cannot reconstruct code from tokens") - error_tokens = tokens.map(&:event).grep(/error/) - assert_empty(error_tokens, 'Error tokens must be ignored if there is corresponding non-error token') - end - end - - def test_unterminated_heredoc_string_literal - ['<<A;<<B', "<<A;<<B\n", "%W[\#{<<A;<<B", "%W[\#{<<A;<<B\n"].each do |code| - tokens = IRB::RubyLex.ripper_lex_without_warning(code) - string_literal = IRB::NestingParser.open_tokens(tokens).last - assert_equal('<<A', string_literal&.tok) - end - end - - def test_indent_level_with_heredoc_and_embdoc - reference_code = <<~EOC.chomp - if true - hello - p( - ) - EOC - code_with_heredoc = <<~EOC.chomp - if true - <<~A - A - p( - ) - EOC - code_with_embdoc = <<~EOC.chomp - if true - =begin - =end - p( - ) - EOC - expected = 1 - assert_indent_level(reference_code.lines, expected) - assert_indent_level(code_with_heredoc.lines, expected) - assert_indent_level(code_with_embdoc.lines, expected) - end - - def test_assignment_expression - ruby_lex = IRB::RubyLex.new - - [ - "foo = bar", - "@foo = bar", - "$foo = bar", - "@@foo = bar", - "::Foo = bar", - "a::Foo = bar", - "Foo = bar", - "foo.bar = 1", - "foo[1] = bar", - "foo += bar", - "foo -= bar", - "foo ||= bar", - "foo &&= bar", - "foo, bar = 1, 2", - "foo.bar=(1)", - "foo; foo = bar", - "foo; foo = bar; ;\n ;", - "foo\nfoo = bar", - ].each do |exp| - assert( - ruby_lex.assignment_expression?(exp, local_variables: []), - "#{exp.inspect}: should be an assignment expression" - ) - end - - [ - "foo", - "foo.bar", - "foo[0]", - "foo = bar; foo", - "foo = bar\nfoo", - ].each do |exp| - refute( - ruby_lex.assignment_expression?(exp, local_variables: []), - "#{exp.inspect}: should not be an assignment expression" - ) - end - end - - def test_assignment_expression_with_local_variable - ruby_lex = IRB::RubyLex.new - code = "a /1;x=1#/" - refute(ruby_lex.assignment_expression?(code, local_variables: []), "#{code}: should not be an assignment expression") - assert(ruby_lex.assignment_expression?(code, local_variables: [:a]), "#{code}: should be an assignment expression") - refute(ruby_lex.assignment_expression?("", local_variables: [:a]), "empty code should not be an assignment expression") - end - - def test_initialising_the_old_top_level_ruby_lex - assert_in_out_err(["--disable-gems", "-W:deprecated"], <<~RUBY, [], /warning: constant ::RubyLex is deprecated/) - require "irb" - ::RubyLex.new(nil) - RUBY - end - - private - - def assert_indent_level(lines, expected, local_variables: []) - indent_level, _continue, _code_block_open = check_state(lines, local_variables: local_variables) - error_message = "Calculated the wrong number of indent level for:\n #{lines.join("\n")}" - assert_equal(expected, indent_level, error_message) - end - - def assert_should_continue(lines, expected, local_variables: []) - _indent_level, continue, _code_block_open = check_state(lines, local_variables: local_variables) - error_message = "Wrong result of should_continue for:\n #{lines.join("\n")}" - assert_equal(expected, continue, error_message) - 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) - end - - def check_state(lines, local_variables: []) - code = lines.map { |l| "#{l}\n" }.join # code should end with "\n" - ruby_lex = IRB::RubyLex.new - tokens, opens, terminated = ruby_lex.check_code_state(code, local_variables: local_variables) - indent_level = ruby_lex.calc_indent_level(opens) - continue = ruby_lex.should_continue?(tokens) - [indent_level, continue, !terminated] - end - end -end diff --git a/test/irb/test_tracer.rb b/test/irb/test_tracer.rb deleted file mode 100644 index 540f8be131..0000000000 --- a/test/irb/test_tracer.rb +++ /dev/null @@ -1,90 +0,0 @@ -# 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 deleted file mode 100644 index 3d0e25d19e..0000000000 --- a/test/irb/test_type_completor.rb +++ /dev/null @@ -1,109 +0,0 @@ -# frozen_string_literal: true - -# Run test only when Ruby >= 3.0 and repl_type_completor is available -return unless RUBY_VERSION >= '3.0.0' -return if RUBY_ENGINE == 'truffleruby' # needs endless method definition -begin - require 'repl_type_completor' -rescue LoadError - return -end - -require 'irb' -require 'tempfile' -require_relative './helper' - -module TestIRB - class TypeCompletorTest < TestCase - DummyContext = Struct.new(:irb_path) - - def setup - ReplTypeCompletor.load_rbs unless ReplTypeCompletor.rbs_loaded? - context = DummyContext.new('(irb)') - @completor = IRB::TypeCompletor.new(context) - end - - def empty_binding - binding - end - - def test_build_completor - IRB.init_config(nil) - verbose, $VERBOSE = $VERBOSE, nil - original_completor = IRB.conf[:COMPLETOR] - workspace = IRB::WorkSpace.new(Object.new) - @context = IRB::Context.new(nil, workspace, TestInputMethod.new) - IRB.conf[:COMPLETOR] = nil - expected_default_completor = RUBY_VERSION >= '3.4' ? 'IRB::TypeCompletor' : 'IRB::RegexpCompletor' - assert_equal expected_default_completor, @context.send(:build_completor).class.name - IRB.conf[:COMPLETOR] = :type - assert_equal 'IRB::TypeCompletor', @context.send(:build_completor).class.name - ensure - $VERBOSE = verbose - IRB.conf[:COMPLETOR] = original_completor - end - - def assert_completion(preposing, target, binding: empty_binding, include: nil, exclude: nil) - raise ArgumentError if include.nil? && exclude.nil? - candidates = @completor.completion_candidates(preposing, target, '', bind: binding) - assert ([*include] - candidates).empty?, "Expected #{candidates} to include #{include}" if include - assert (candidates & [*exclude]).empty?, "Expected #{candidates} not to include #{exclude}" if exclude - end - - def assert_doc_namespace(preposing, target, namespace, binding: empty_binding) - @completor.completion_candidates(preposing, target, '', bind: binding) - assert_equal namespace, @completor.doc_namespace(preposing, target, '', bind: binding) - end - - def test_type_completion - bind = eval('num = 1; binding') - assert_completion('num.times.map(&:', 'ab', binding: bind, include: 'abs') - assert_doc_namespace('num.chr.', 'upcase', 'String#upcase', binding: bind) - end - - def test_inspect - assert_match(/\AReplTypeCompletor.*\z/, @completor.inspect) - end - - def test_empty_completion - candidates = @completor.completion_candidates('(', ')', '', bind: binding) - assert_equal [], candidates - assert_doc_namespace('(', ')', nil) - end - - def test_command_completion - binding.eval("some_var = 1") - # completion for help command's argument should only include command names - assert_include(@completor.completion_candidates('help ', 's', '', bind: binding), 'show_source') - assert_not_include(@completor.completion_candidates('help ', 's', '', bind: binding), 'some_var') - - 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 - def test_type_completor - write_rc <<~RUBY - IRB.conf[:COMPLETOR] = :type - RUBY - - write_ruby <<~'RUBY' - binding.irb - RUBY - - output = run_ruby_file do - type "irb_info" - type "sleep 0.01 until ReplTypeCompletor.rbs_loaded?" - type "completor = IRB.CurrentContext.io.instance_variable_get(:@completor);" - type "n = 10" - type "puts completor.completion_candidates 'a = n.abs;', 'a.b', '', bind: binding" - type "puts completor.doc_namespace 'a = n.chr;', 'a.encoding', '', bind: binding" - type "exit!" - end - assert_match(/Completion: Autocomplete, ReplTypeCompletor/, output) - assert_match(/a\.bit_length/, output) - assert_match(/String#encoding/, output) - end - end -end diff --git a/test/irb/test_workspace.rb b/test/irb/test_workspace.rb deleted file mode 100644 index ad515f91df..0000000000 --- a/test/irb/test_workspace.rb +++ /dev/null @@ -1,126 +0,0 @@ -# frozen_string_literal: false -require 'tempfile' -require 'irb' -require 'irb/workspace' -require 'irb/color' - -require_relative "helper" - -module TestIRB - class WorkSpaceTest < TestCase - def test_code_around_binding - IRB.conf[:USE_COLORIZE] = false - Tempfile.create('irb') do |f| - code = <<~RUBY - # 1 - # 2 - IRB::WorkSpace.new(binding) # 3 - # 4 - # 5 - RUBY - f.print(code) - f.close - - workspace = eval(code, binding, f.path) - assert_equal(<<~EOS, without_term { workspace.code_around_binding }) - - From: #{f.path} @ line 3 : - - 1: # 1 - 2: # 2 - => 3: IRB::WorkSpace.new(binding) # 3 - 4: # 4 - 5: # 5 - - EOS - end - ensure - IRB.conf.delete(:USE_COLORIZE) - end - - def test_code_around_binding_with_existing_unreadable_file - pend 'chmod cannot make file unreadable on windows' if windows? - pend 'skipped in root privilege' if Process.uid == 0 - - Tempfile.create('irb') do |f| - code = "IRB::WorkSpace.new(binding)\n" - f.print(code) - f.close - - File.chmod(0, f.path) - - workspace = eval(code, binding, f.path) - assert_equal(nil, workspace.code_around_binding) - end - end - - def test_code_around_binding_with_script_lines__ - IRB.conf[:USE_COLORIZE] = false - with_script_lines do |script_lines| - Tempfile.create('irb') do |f| - code = "IRB::WorkSpace.new(binding)\n" - script_lines[f.path] = code.split(/^/) - - workspace = eval(code, binding, f.path) - assert_equal(<<~EOS, without_term { workspace.code_around_binding }) - - From: #{f.path} @ line 1 : - - => 1: IRB::WorkSpace.new(binding) - - EOS - end - end - ensure - IRB.conf.delete(:USE_COLORIZE) - end - - def test_code_around_binding_on_irb - workspace = eval("IRB::WorkSpace.new(binding)", binding, "(irb)") - assert_equal(nil, workspace.code_around_binding) - end - - def test_toplevel_binding_local_variables - bug17623 = '[ruby-core:102468]' - bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : [] - top_srcdir = "#{__dir__}/../.." - irb_path = nil - %w[exe libexec].find do |dir| - 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) - version = 'xyz' # typical rubygems loading file - load('#{irb_path}') - RUBY - end - - private - - def with_script_lines - script_lines = nil - debug_lines = {} - Object.class_eval do - if defined?(SCRIPT_LINES__) - script_lines = SCRIPT_LINES__ - remove_const :SCRIPT_LINES__ - end - const_set(:SCRIPT_LINES__, debug_lines) - end - yield debug_lines - ensure - Object.class_eval do - remove_const :SCRIPT_LINES__ - const_set(:SCRIPT_LINES__, script_lines) if script_lines - end - end - - def without_term - env = ENV.to_h.dup - ENV.delete('TERM') - yield - ensure - ENV.replace(env) - end - end -end diff --git a/test/irb/yamatanooroti/test_rendering.rb b/test/irb/yamatanooroti/test_rendering.rb deleted file mode 100644 index c1bc81241c..0000000000 --- a/test/irb/yamatanooroti/test_rendering.rb +++ /dev/null @@ -1,494 +0,0 @@ -require 'irb' - -begin - require 'yamatanooroti' -rescue LoadError, NameError - # On Ruby repository, this test suite doesn't run because Ruby repo doesn't - # have the yamatanooroti gem. - return -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) - @tmpdir = File.join(File.expand_path(Dir.tmpdir), "test_irb_#{$$}_#{suffix}") - begin - Dir.mkdir(@tmpdir) - rescue Errno::EEXIST - FileUtils.rm_rf(@tmpdir) - Dir.mkdir(@tmpdir) - end - @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['HOME'] = @home_backup - ENV['XDG_CONFIG_HOME'] = @xdg_config_home_backup - end - - def test_launch - start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) - write(<<~EOC) - 'Hello, World!' - EOC - assert_screen(<<~EOC) - irb(main):001> 'Hello, World!' - => "Hello, World!" - irb(main):002> - EOC - close - 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: /irb\(main\)/) - write(<<~EOC) - 'Hello, World!' - EOC - assert_screen(<<~EOC) - irb(main):001> 'Hello, World!' - => "Hello, World!" - irb(main):002> - EOC - close - 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: /irb\(main\)/) - write(<<~EOC) - 'Hello, World!' - binding.irb - exit! - EOC - assert_screen(<<~EOC) - irb(main):001> 'Hello, World!' - => "Hello, World!" - irb(main):002> binding.irb - irb(main):003> exit! - irb(main):001> - EOC - close - end - - def test_nomultiline - start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb --nomultiline}, startup_message: /irb\(main\)/) - write(<<~EOC) - if true - if false - a = "hello - world" - puts a - end - end - EOC - assert_screen(<<~EOC) - irb(main):001> if true - irb(main):002* if false - irb(main):003* a = "hello - irb(main):004" world" - irb(main):005* puts a - irb(main):006* end - irb(main):007* end - => nil - irb(main):008> - EOC - close - end - - def test_multiline_paste - start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) - write(<<~EOC) - class A - def inspect; '#<A>'; end - def a; self; end - def b; true; end - end - - a = A.new - - a - .a - .b - .itself - EOC - assert_screen(<<~EOC) - irb(main):001* class A - irb(main):002* def inspect; '#<A>'; end - irb(main):003* def a; self; end - irb(main):004* def b; true; end - irb(main):005> end - => :b - irb(main):006> - irb(main):007> a = A.new - => #<A> - irb(main):008> - irb(main):009> a - irb(main):010> .a - irb(main):011> .b - irb(main):012> .itself - => true - irb(main):013> - EOC - close - end - - def test_evaluate_each_toplevel_statement_by_multiline_paste - start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) - write(<<~EOC) - class A - def inspect; '#<A>'; end - def b; self; end - def c; true; end - end - - a = A.new - - a - .b - # aaa - .c - - (a) - &.b() - - class A def b; self; end; def c; true; end; end; - a = A.new - a - .b - # aaa - .c - (a) - &.b() - .itself - EOC - assert_screen(<<~EOC) - irb(main):001* class A - irb(main):002* def inspect; '#<A>'; end - irb(main):003* def b; self; end - irb(main):004* def c; true; end - irb(main):005> end - => :c - irb(main):006> - irb(main):007> a = A.new - => #<A> - irb(main):008> - irb(main):009> a - irb(main):010> .b - irb(main):011> # aaa - irb(main):012> .c - => true - irb(main):013> - irb(main):014> (a) - irb(main):015> &.b() - => #<A> - irb(main):016> - irb(main):017> class A def b; self; end; def c; true; end; end; - irb(main):018> a = A.new - => #<A> - irb(main):019> a - irb(main):020> .b - irb(main):021> # aaa - irb(main):022> .c - => true - irb(main):023> (a) - irb(main):024> &.b() - irb(main):025> .itself - => #<A> - irb(main):026> - EOC - close - end - - def test_symbol_with_backtick - start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) - write(<<~EOC) - :` - EOC - assert_screen(<<~EOC) - irb(main):001> :` - => :` - irb(main):002> - EOC - close - end - - def test_autocomplete_with_multiple_doc_namespaces - start_terminal(3, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) - write("{}.__id_") - write("\C-i") - assert_screen(/irb\(main\):001> {}\.__id__\n }\.__id__(?:Press )?/) - close - end - - def test_autocomplete_with_showdoc_in_gaps_on_narrow_screen_right - rdoc_dir = File.join(@tmpdir, 'rdoc') - system("bundle exec rdoc lib -r -o #{rdoc_dir}") - write_irbrc <<~LINES - IRB.conf[:EXTRA_DOC_DIRS] = ['#{rdoc_dir}'] - IRB.conf[:PROMPT][:MY_PROMPT] = { - :PROMPT_I => "%03n> ", - :PROMPT_S => "%03n> ", - :PROMPT_C => "%03n> " - } - IRB.conf[:PROMPT_MODE] = :MY_PROMPT - LINES - start_terminal(4, 19, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /001>/) - write("IR") - write("\C-i") - - # This is because on macOS we display different shortcut for displaying the full doc - # 'O' is for 'Option' and 'A' is for 'Alt' - if RUBY_PLATFORM =~ /darwin/ - assert_screen(<<~EOC) - 001> IRB - IRBPress Opti - IRB - EOC - else - assert_screen(<<~EOC) - 001> IRB - IRBPress Alt+ - IRB - EOC - end - close - end - - def test_autocomplete_with_showdoc_in_gaps_on_narrow_screen_left - rdoc_dir = File.join(@tmpdir, 'rdoc') - system("bundle exec rdoc lib -r -o #{rdoc_dir}") - write_irbrc <<~LINES - IRB.conf[:EXTRA_DOC_DIRS] = ['#{rdoc_dir}'] - IRB.conf[:PROMPT][:MY_PROMPT] = { - :PROMPT_I => "%03n> ", - :PROMPT_S => "%03n> ", - :PROMPT_C => "%03n> " - } - IRB.conf[:PROMPT_MODE] = :MY_PROMPT - LINES - start_terminal(4, 12, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /001>/) - write("IR") - write("\C-i") - assert_screen(<<~EOC) - 001> IRB - PressIRB - IRB - EOC - close - end - - def test_assignment_expression_truncate - start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) - # Assignment expression code that turns into non-assignment expression after evaluation - code = "a /'/i if false; a=1; x=1000.times.to_a#'.size" - write(code + "\n") - assert_screen(<<~EOC) - irb(main):001> #{code} - => - [0, - ... - irb(main):002> - EOC - close - end - - def test_ctrl_c_is_handled - start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) - # Assignment expression code that turns into non-assignment expression after evaluation - write("\C-c") - assert_screen(<<~EOC) - irb(main):001> - ^C - irb(main):001> - EOC - close - end - - def test_show_cmds_with_pager_can_quit_with_ctrl_c - start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) - 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 - - # IRB should resume - assert_screen(/foobar/) - # IRB::Abort should be rescued - assert_screen(/\A(?!IRB::Abort)/) - close - end - - def test_pager_page_content_pages_output_when_it_does_not_fit_in_the_screen_because_of_total_length - write_irbrc <<~'LINES' - require "irb/pager" - LINES - start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) - write("IRB::Pager.page_content('a' * (80 * 8))\n") - write("'foo' + 'bar'\n") # eval something to make sure IRB resumes - - assert_screen(/a{80}/) - # because pager is invoked, foobar will not be evaluated - assert_screen(/\A(?!foobar)/) - close - end - - def test_pager_page_content_pages_output_when_it_does_not_fit_in_the_screen_because_of_screen_height - write_irbrc <<~'LINES' - require "irb/pager" - LINES - start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) - write("IRB::Pager.page_content('a\n' * 8)\n") - write("'foo' + 'bar'\n") # eval something to make sure IRB resumes - - assert_screen(/(a\n){8}/) - # because pager is invoked, foobar will not be evaluated - assert_screen(/\A(?!foobar)/) - close - end - - def test_pager_page_content_doesnt_page_output_when_it_fits_in_the_screen - write_irbrc <<~'LINES' - require "irb/pager" - LINES - start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) - write("IRB::Pager.page_content('a' * (80 * 7))\n") - write("'foo' + 'bar'\n") # eval something to make sure IRB resumes - - assert_screen(/a{80}/) - # because pager is not invoked, foobar will be evaluated - assert_screen(/foobar/) - close - end - - def test_long_evaluation_output_is_paged - write_irbrc <<~'LINES' - require "irb/pager" - LINES - start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) - write("'a' * 80 * 11\n") - write("'foo' + 'bar'\n") # eval something to make sure IRB resumes - - assert_screen(/"a{79}\n(a{80}\n){7}/) - # because pager is invoked, foobar will not be evaluated - assert_screen(/\A(?!foobar)/) - close - end - - def test_pretty_print_preview_with_slow_inspect - write_irbrc <<~'LINES' - require "irb/pager" - LINES - start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) - write("o1 = Object.new; def o1.inspect; 'INSPECT'; end\n") - write("o2 = Object.new; def o2.inspect; sleep 0.1; 'SLOW'; end\n") - # preview should be shown even if pretty_print is not completed. - write("[o1] * 20 + [o2] * 100\n") - assert_screen(/=>\n\[INSPECT,\n( INSPECT,\n){6}Preparing full inspection value\.\.\./) - write("\C-c") # abort pretty_print - write("'foo' + 'bar'\n") # eval something to make sure IRB resumes - assert_screen(/foobar/) - close - end - - def test_long_evaluation_output_is_preserved_after_paging - write_irbrc <<~'LINES' - require "irb/pager" - LINES - start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) - write("'a' * 80 * 11\n") - write("q") # quit pager - write("'foo' + 'bar'\n") # eval something to make sure IRB resumes - - # confirm pager has exited - assert_screen(/foobar/) - # confirm output is preserved - assert_screen(/(a{80}\n){6}/) - close - end - - def test_debug_integration_hints_debugger_commands - write_irbrc <<~'LINES' - IRB.conf[:USE_COLORIZE] = false - LINES - script = Tempfile.create(["debug", ".rb"]) - script.write <<~RUBY - binding.irb - RUBY - script.close - start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{script.to_path}}, startup_message: /irb\(main\)/) - write("debug\n") - write("pp 1\n") - write("pp 1") - - # submitted input shouldn't contain hint - assert_screen(/irb:rdbg\(main\):002> pp 1\n/) - # unsubmitted input should contain hint - assert_screen(/irb:rdbg\(main\):003> pp 1 # debug command\n/) - close - ensure - File.unlink(script) if script - end - - def test_debug_integration_doesnt_hint_non_debugger_commands - write_irbrc <<~'LINES' - IRB.conf[:USE_COLORIZE] = false - LINES - script = Tempfile.create(["debug", ".rb"]) - script.write <<~RUBY - binding.irb - RUBY - script.close - start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{script.to_path}}, startup_message: /irb\(main\)/) - write("debug\n") - write("foo") - assert_screen(/irb:rdbg\(main\):002> foo\n/) - close - ensure - File.unlink(script) if script - end - - def test_debug_integration_doesnt_hint_debugger_commands_in_nomultiline_mode - write_irbrc <<~'LINES' - IRB.conf[:USE_SINGLELINE] = true - LINES - script = Tempfile.create(["debug", ".rb"]) - script.write <<~RUBY - puts 'start IRB' - binding.irb - RUBY - script.close - start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{script.to_path}}, startup_message: 'start IRB') - write("debug\n") - write("pp 1") - # submitted input shouldn't contain hint - assert_screen(/irb:rdbg\(main\):002> pp 1\n/) - close - ensure - File.unlink(script) if script - end - - private - - def write_irbrc(content) - File.open(@irbrc_file, 'w') do |f| - f.write content - end - end -end diff --git a/test/reline/helper.rb b/test/reline/helper.rb deleted file mode 100644 index 6f470a617f..0000000000 --- a/test/reline/helper.rb +++ /dev/null @@ -1,158 +0,0 @@ -$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) - -ENV['TERM'] = 'xterm' # for some CI environments - -require 'reline' -require 'test/unit' - -begin - require 'rbconfig' -rescue LoadError -end - -begin - # This should exist and available in load path when this file is mirrored to ruby/ruby and running at there - if File.exist?(File.expand_path('../../tool/lib/envutil.rb', __dir__)) - require 'envutil' - end -rescue LoadError -end - -module Reline - class << self - def test_mode(ansi: false) - @original_iogate = IOGate - - if defined?(RELINE_TEST_ENCODING) - encoding = RELINE_TEST_ENCODING - else - encoding = Encoding::UTF_8 - end - - if ansi - new_io_gate = ANSI.new - # Setting ANSI gate's screen size through set_screen_size will also change the tester's stdin's screen size - # Let's avoid that side-effect by stubbing the get_screen_size method - new_io_gate.define_singleton_method(:get_screen_size) do - [24, 80] - end - new_io_gate.define_singleton_method(:encoding) do - encoding - end - else - new_io_gate = Dumb.new(encoding: encoding) - end - - remove_const('IOGate') - const_set('IOGate', new_io_gate) - core.config.instance_variable_set(:@test_mode, true) - core.config.reset - end - - def test_reset - remove_const('IOGate') - const_set('IOGate', @original_iogate) - Reline.instance_variable_set(:@core, nil) - end - - # Return a executable name to spawn Ruby process. In certain build configuration, - # "ruby" may not be available. - def test_rubybin - # When this test suite is running in ruby/ruby, prefer EnvUtil result over original implementation - if const_defined?(:EnvUtil) - return EnvUtil.rubybin - end - - # The following is a simplified port of EnvUtil.rubybin in ruby/ruby - if ruby = ENV["RUBY"] - return ruby - end - ruby = "ruby" - exeext = RbConfig::CONFIG["EXEEXT"] - rubyexe = (ruby + exeext if exeext and !exeext.empty?) - if File.exist? ruby and File.executable? ruby and !File.directory? ruby - return File.expand_path(ruby) - end - if rubyexe and File.exist? rubyexe and File.executable? rubyexe - return File.expand_path(rubyexe) - end - if defined?(RbConfig.ruby) - RbConfig.ruby - else - "ruby" - end - end - end -end - -class Reline::TestCase < Test::Unit::TestCase - private def convert_str(input) - input.encode(@line_editor.encoding, Encoding::UTF_8) - end - - def omit_unless_utf8 - omit "This test is for UTF-8 but the locale is #{Reline.core.encoding}" if Reline.core.encoding != Encoding::UTF_8 - end - - def input_key_by_symbol(method_symbol, char: nil, csi: false) - char ||= csi ? "\e[A" : "\C-a" - @line_editor.input_key(Reline::Key.new(char, method_symbol, false)) - end - - def input_keys(input) - input = convert_str(input) - - key_stroke = Reline::KeyStroke.new(@config, @encoding) - input_bytes = input.bytes - until input_bytes.empty? - expanded, input_bytes = key_stroke.expand(input_bytes) - expanded.each do |key| - @line_editor.input_key(key) - end - end - end - - def set_line_around_cursor(before, after) - input_keys("\C-a\C-k") - input_keys(after) - input_keys("\C-a") - input_keys(before) - end - - 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) - expected = convert_str(expected) - byte_pointer = @line_editor.instance_variable_get(:@byte_pointer) - chunk = @line_editor.line.byteslice(0, byte_pointer) - assert_equal( - expected.bytesize, byte_pointer, - <<~EOM) - <#{expected.inspect} (#{expected.encoding.inspect})> expected but was - <#{chunk.inspect} (#{chunk.encoding.inspect})> in <Terminal #{Reline::Dumb.new.encoding.inspect}> - EOM - end - - def assert_line_index(expected) - assert_equal(expected, @line_editor.instance_variable_get(:@line_index)) - end - - def assert_whole_lines(expected) - assert_equal(expected, @line_editor.whole_lines) - end - - def assert_key_binding(input, method_symbol, editing_modes = [:emacs, :vi_insert, :vi_command]) - editing_modes.each do |editing_mode| - @config.editing_mode = editing_mode - assert_equal(method_symbol, @config.editing_mode.get(input.bytes)) - end - end -end diff --git a/test/reline/test_ansi.rb b/test/reline/test_ansi.rb deleted file mode 100644 index 5e28e72b06..0000000000 --- a/test/reline/test_ansi.rb +++ /dev/null @@ -1,72 +0,0 @@ -require_relative 'helper' -require 'reline' - -class Reline::ANSITest < Reline::TestCase - def setup - Reline.send(:test_mode, ansi: true) - @config = Reline::Config.new - Reline.core.io_gate.set_default_key_bindings(@config) - end - - def teardown - Reline.test_reset - end - - def test_home - assert_key_binding("\e[1~", :ed_move_to_beg) # Console (80x25) - assert_key_binding("\e[H", :ed_move_to_beg) # KDE - assert_key_binding("\e[7~", :ed_move_to_beg) # urxvt / exoterm - assert_key_binding("\eOH", :ed_move_to_beg) # GNOME - end - - def test_end - assert_key_binding("\e[4~", :ed_move_to_end) # Console (80x25) - assert_key_binding("\e[F", :ed_move_to_end) # KDE - assert_key_binding("\e[8~", :ed_move_to_end) # urxvt / exoterm - assert_key_binding("\eOF", :ed_move_to_end) # GNOME - end - - def test_delete - assert_key_binding("\e[3~", :key_delete) - end - - def test_up_arrow - assert_key_binding("\e[A", :ed_prev_history) # Console (80x25) - assert_key_binding("\eOA", :ed_prev_history) - end - - def test_down_arrow - assert_key_binding("\e[B", :ed_next_history) # Console (80x25) - assert_key_binding("\eOB", :ed_next_history) - end - - def test_right_arrow - assert_key_binding("\e[C", :ed_next_char) # Console (80x25) - assert_key_binding("\eOC", :ed_next_char) - end - - def test_left_arrow - assert_key_binding("\e[D", :ed_prev_char) # Console (80x25) - assert_key_binding("\eOD", :ed_prev_char) - end - - # Ctrl+arrow and Meta+arrow - def test_extended - assert_key_binding("\e[1;5C", :em_next_word) # Ctrl+→ - assert_key_binding("\e[1;5D", :ed_prev_word) # Ctrl+← - assert_key_binding("\e[1;3C", :em_next_word) # Meta+→ - assert_key_binding("\e[1;3D", :ed_prev_word) # Meta+← - assert_key_binding("\e\e[C", :em_next_word) # Meta+→ - assert_key_binding("\e\e[D", :ed_prev_word) # Meta+← - end - - def test_shift_tab - assert_key_binding("\e[Z", :completion_journey_up, [:emacs, :vi_insert]) - end - - # A few emacs bindings that are always mapped - def test_more_emacs - assert_key_binding("\e ", :em_set_mark, [:emacs]) - assert_key_binding("\C-x\C-x", :em_exchange_mark, [:emacs]) - end -end diff --git a/test/reline/test_config.rb b/test/reline/test_config.rb deleted file mode 100644 index 3c9094eece..0000000000 --- a/test/reline/test_config.rb +++ /dev/null @@ -1,616 +0,0 @@ -require_relative 'helper' - -class Reline::Config::Test < Reline::TestCase - def setup - @pwd = Dir.pwd - @tmpdir = File.join(Dir.tmpdir, "test_reline_config_#{$$}") - begin - Dir.mkdir(@tmpdir) - rescue Errno::EEXIST - FileUtils.rm_rf(@tmpdir) - Dir.mkdir(@tmpdir) - end - Dir.chdir(@tmpdir) - Reline.test_mode - @config = Reline::Config.new - @inputrc_backup = ENV['INPUTRC'] - end - - def teardown - Dir.chdir(@pwd) - FileUtils.rm_rf(@tmpdir) - Reline.test_reset - @config.reset - ENV['INPUTRC'] = @inputrc_backup - end - - def get_config_variable(variable) - @config.instance_variable_get(variable) - end - - def additional_key_bindings(keymap_label) - get_config_variable(:@additional_key_bindings)[keymap_label].instance_variable_get(:@key_bindings) - end - - def registered_key_bindings(keys) - key_bindings = @config.key_bindings - keys.to_h { |key| [key, key_bindings.get(key)] } - end - - def test_read_lines - @config.read_lines(<<~LINES.lines) - set show-mode-in-prompt on - LINES - - assert_equal true, get_config_variable(:@show_mode_in_prompt) - end - - def test_read_lines_with_variable - @config.read_lines(<<~LINES.lines) - set disable-completion on - LINES - - assert_equal true, get_config_variable(:@disable_completion) - end - - def test_string_value - @config.read_lines(<<~LINES.lines) - set show-mode-in-prompt on - set emacs-mode-string Emacs - LINES - - assert_equal 'Emacs', get_config_variable(:@emacs_mode_string) - end - - def test_string_value_with_brackets - @config.read_lines(<<~LINES.lines) - set show-mode-in-prompt on - set emacs-mode-string [Emacs] - LINES - - assert_equal '[Emacs]', get_config_variable(:@emacs_mode_string) - end - - def test_string_value_with_brackets_and_quotes - @config.read_lines(<<~LINES.lines) - set show-mode-in-prompt on - set emacs-mode-string "[Emacs]" - LINES - - assert_equal '[Emacs]', get_config_variable(:@emacs_mode_string) - end - - def test_string_value_with_parens - @config.read_lines(<<~LINES.lines) - set show-mode-in-prompt on - set emacs-mode-string (Emacs) - LINES - - assert_equal '(Emacs)', get_config_variable(:@emacs_mode_string) - end - - def test_string_value_with_parens_and_quotes - @config.read_lines(<<~LINES.lines) - set show-mode-in-prompt on - set emacs-mode-string "(Emacs)" - LINES - - assert_equal '(Emacs)', get_config_variable(:@emacs_mode_string) - end - - def test_encoding_is_ascii - @config.reset - Reline.core.io_gate.instance_variable_set(:@encoding, Encoding::US_ASCII) - @config = Reline::Config.new - - assert_equal true, @config.convert_meta - end - - def test_encoding_is_not_ascii - @config = Reline::Config.new - - assert_equal false, @config.convert_meta - end - - def test_invalid_keystroke - @config.read_lines(<<~LINES.lines) - #"a": comment - a: error - "b": no-error - LINES - key_bindings = additional_key_bindings(:emacs) - assert_not_include key_bindings, 'a'.bytes - assert_not_include key_bindings, nil - assert_not_include key_bindings, [] - assert_include key_bindings, 'b'.bytes - end - - def test_bind_key - assert_equal ['input'.bytes, 'abcde'.bytes], @config.parse_key_binding('"input"', '"abcde"') - end - - def test_bind_key_with_macro - - assert_equal ['input'.bytes, :abcde], @config.parse_key_binding('"input"', 'abcde') - end - - def test_bind_key_with_escaped_chars - assert_equal ['input'.bytes, "\e \\ \" ' \a \b \d \f \n \r \t \v".bytes], @config.parse_key_binding('"input"', '"\\e \\\\ \\" \\\' \\a \\b \\d \\f \\n \\r \\t \\v"') - end - - def test_bind_key_with_ctrl_chars - assert_equal ['input'.bytes, "\C-h\C-h\C-_".bytes], @config.parse_key_binding('"input"', '"\C-h\C-H\C-_"') - assert_equal ['input'.bytes, "\C-h\C-h\C-_".bytes], @config.parse_key_binding('"input"', '"\Control-h\Control-H\Control-_"') - end - - def test_bind_key_with_meta_chars - assert_equal ['input'.bytes, "\eh\eH\e_".bytes], @config.parse_key_binding('"input"', '"\M-h\M-H\M-_"') - assert_equal ['input'.bytes, "\eh\eH\e_".bytes], @config.parse_key_binding('"input"', '"\Meta-h\Meta-H\M-_"') - end - - def test_bind_key_with_ctrl_meta_chars - assert_equal ['input'.bytes, "\e\C-h\e\C-h\e\C-_".bytes], @config.parse_key_binding('"input"', '"\M-\C-h\C-\M-H\M-\C-_"') - assert_equal ['input'.bytes, "\e\C-h\e\C-_".bytes], @config.parse_key_binding('"input"', '"\Meta-\Control-h\Control-\Meta-_"') - end - - def test_bind_key_with_octal_number - input = %w{i n p u t}.map(&:ord) - assert_equal [input, "\1".bytes], @config.parse_key_binding('"input"', '"\1"') - assert_equal [input, "\12".bytes], @config.parse_key_binding('"input"', '"\12"') - assert_equal [input, "\123".bytes], @config.parse_key_binding('"input"', '"\123"') - assert_equal [input, "\123".bytes + '4'.bytes], @config.parse_key_binding('"input"', '"\1234"') - end - - def test_bind_key_with_hexadecimal_number - input = %w{i n p u t}.map(&:ord) - assert_equal [input, "\x4".bytes], @config.parse_key_binding('"input"', '"\x4"') - assert_equal [input, "\x45".bytes], @config.parse_key_binding('"input"', '"\x45"') - assert_equal [input, "\x45".bytes + '6'.bytes], @config.parse_key_binding('"input"', '"\x456"') - end - - def test_include - File.open('included_partial', 'wt') do |f| - f.write(<<~PARTIAL_LINES) - set show-mode-in-prompt on - PARTIAL_LINES - end - @config.read_lines(<<~LINES.lines) - $include included_partial - LINES - - assert_equal true, get_config_variable(:@show_mode_in_prompt) - end - - def test_include_expand_path - home_backup = ENV['HOME'] - File.open('included_partial', 'wt') do |f| - f.write(<<~PARTIAL_LINES) - set show-mode-in-prompt on - PARTIAL_LINES - end - ENV['HOME'] = Dir.pwd - @config.read_lines(<<~LINES.lines) - $include ~/included_partial - LINES - - assert_equal true, get_config_variable(:@show_mode_in_prompt) - ensure - ENV['HOME'] = home_backup - end - - def test_if - @config.read_lines(<<~LINES.lines) - $if Ruby - set vi-cmd-mode-string (cmd) - $else - set vi-cmd-mode-string [cmd] - $endif - LINES - - assert_equal '(cmd)', get_config_variable(:@vi_cmd_mode_string) - end - - def test_if_with_false - @config.read_lines(<<~LINES.lines) - $if Python - set vi-cmd-mode-string (cmd) - $else - set vi-cmd-mode-string [cmd] - $endif - LINES - - assert_equal '[cmd]', get_config_variable(:@vi_cmd_mode_string) - end - - def test_if_with_indent - %w[Ruby Reline].each do |cond| - @config.read_lines(<<~LINES.lines) - set vi-cmd-mode-string {cmd} - $if #{cond} - set vi-cmd-mode-string (cmd) - $else - set vi-cmd-mode-string [cmd] - $endif - LINES - - assert_equal '(cmd)', get_config_variable(:@vi_cmd_mode_string) - 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], ['O'.ord]] } - assert_equal(key_bindings, additional_key_bindings(:emacs)) - end - - def test_unclosed_if - e = assert_raise(Reline::Config::InvalidInputrc) do - @config.read_lines(<<~LINES.lines, "INPUTRC") - $if Ruby - LINES - end - assert_equal "INPUTRC:1: unclosed if", e.message - end - - def test_unmatched_else - e = assert_raise(Reline::Config::InvalidInputrc) do - @config.read_lines(<<~LINES.lines, "INPUTRC") - $else - LINES - end - assert_equal "INPUTRC:1: unmatched else", e.message - end - - def test_unmatched_endif - e = assert_raise(Reline::Config::InvalidInputrc) do - @config.read_lines(<<~LINES.lines, "INPUTRC") - $endif - LINES - end - 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}, additional_key_bindings(:emacs)) - assert_equal({}, additional_key_bindings(:vi_insert)) - assert_equal({}, 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}, additional_key_bindings(:emacs)) - assert_equal({}, additional_key_bindings(:vi_insert)) - assert_equal({}, 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}, additional_key_bindings(:emacs)) - assert_equal({}, additional_key_bindings(:vi_insert)) - assert_equal({}, 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}, additional_key_bindings(:emacs)) - assert_equal({}, additional_key_bindings(:vi_insert)) - assert_equal({}, 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({}, additional_key_bindings(:emacs)) - assert_equal({[5] => :history_search_backward}, additional_key_bindings(:vi_insert)) - assert_equal({}, 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) - "abcd": "ABCD" - "ijkl": "IJKL" - LINES - - expected = { 'abcd'.bytes => 'ABCD'.bytes, 'ijkl'.bytes => 'IJKL'.bytes } - assert_equal expected, registered_key_bindings(expected.keys) - end - - def test_additional_key_bindings - @config.read_lines(<<~'LINES'.lines) - "ef": "EF" - "gh": "GH" - LINES - - expected = { 'ef'.bytes => 'EF'.bytes, 'gh'.bytes => 'GH'.bytes } - assert_equal expected, registered_key_bindings(expected.keys) - end - - def test_unquoted_additional_key_bindings - @config.read_lines(<<~'LINES'.lines) - Meta-a: "Ma" - Control-b: "Cb" - Meta-Control-c: "MCc" - Control-Meta-d: "CMd" - M-C-e: "MCe" - C-M-f: "CMf" - LINES - - expected = { "\ea".bytes => 'Ma'.bytes, "\C-b".bytes => 'Cb'.bytes, "\e\C-c".bytes => 'MCc'.bytes, "\e\C-d".bytes => 'CMd'.bytes, "\e\C-e".bytes => 'MCe'.bytes, "\e\C-f".bytes => 'CMf'.bytes } - assert_equal expected, registered_key_bindings(expected.keys) - end - - def test_additional_key_bindings_with_nesting_and_comment_out - @config.read_lines(<<~'LINES'.lines) - #"ab": "AB" - #"cd": "cd" - "ef": "EF" - "gh": "GH" - LINES - - expected = { 'ef'.bytes => 'EF'.bytes, 'gh'.bytes => 'GH'.bytes } - assert_equal expected, registered_key_bindings(expected.keys) - end - - def test_additional_key_bindings_for_other_keymap - @config.read_lines(<<~'LINES'.lines) - set keymap vi-command - "ab": "AB" - set keymap vi-insert - "cd": "CD" - set keymap emacs - "ef": "EF" - set editing-mode vi # keymap changes to be vi-insert - LINES - - expected = { 'cd'.bytes => 'CD'.bytes } - assert_equal expected, registered_key_bindings(expected.keys) - end - - def test_additional_key_bindings_for_auxiliary_emacs_keymaps - @config.read_lines(<<~'LINES'.lines) - set keymap emacs - "ab": "AB" - set keymap emacs-standard - "cd": "CD" - set keymap emacs-ctlx - "ef": "EF" - set keymap emacs-meta - "gh": "GH" - set editing-mode emacs # keymap changes to be emacs - LINES - - expected = { - 'ab'.bytes => 'AB'.bytes, - 'cd'.bytes => 'CD'.bytes, - "\C-xef".bytes => 'EF'.bytes, - "\egh".bytes => 'GH'.bytes, - } - assert_equal expected, registered_key_bindings(expected.keys) - 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, registered_key_bindings(expected.keys) - end - - def test_history_size - @config.read_lines(<<~LINES.lines) - set history-size 5000 - LINES - - assert_equal 5000, get_config_variable(:@history_size) - history = Reline::History.new(@config) - history << "a\n" - assert_equal 1, history.size - end - - def test_empty_inputrc_env - inputrc_backup = ENV['INPUTRC'] - ENV['INPUTRC'] = '' - assert_nothing_raised do - @config.read - end - ensure - ENV['INPUTRC'] = inputrc_backup - end - - def test_inputrc - inputrc_backup = ENV['INPUTRC'] - expected = "#{@tmpdir}/abcde" - ENV['INPUTRC'] = expected - assert_equal expected, @config.inputrc_path - ensure - ENV['INPUTRC'] = inputrc_backup - end - - def test_inputrc_raw_value - @config.read_lines(<<~'LINES'.lines) - set editing-mode vi ignored-string - set vi-ins-mode-string aaa aaa - set vi-cmd-mode-string bbb ccc # comment - LINES - assert_equal :vi_insert, get_config_variable(:@editing_mode_label) - assert_equal 'aaa aaa', @config.vi_ins_mode_string - assert_equal 'bbb ccc # comment', @config.vi_cmd_mode_string - end - - def test_inputrc_with_utf8 - # This file is encoded by UTF-8 so this heredoc string is also UTF-8. - @config.read_lines(<<~'LINES'.lines) - set editing-mode vi - set vi-cmd-mode-string 🍸 - set vi-ins-mode-string 🍶 - LINES - assert_equal '🍸', @config.vi_cmd_mode_string - assert_equal '🍶', @config.vi_ins_mode_string - rescue Reline::ConfigEncodingConversionError - # do nothing - end - - def test_inputrc_with_eucjp - @config.read_lines(<<~"LINES".encode(Encoding::EUC_JP).lines) - set editing-mode vi - set vi-cmd-mode-string ォャッ - set vi-ins-mode-string 能 - LINES - assert_equal 'ォャッ'.encode(Reline.encoding_system_needs), @config.vi_cmd_mode_string - assert_equal '能'.encode(Reline.encoding_system_needs), @config.vi_ins_mode_string - rescue Reline::ConfigEncodingConversionError - # do nothing - end - - def test_empty_inputrc - assert_nothing_raised do - @config.read_lines([]) - end - end - - def test_xdg_config_home - home_backup = ENV['HOME'] - xdg_config_home_backup = ENV['XDG_CONFIG_HOME'] - inputrc_backup = ENV['INPUTRC'] - xdg_config_home = File.expand_path("#{@tmpdir}/.config/example_dir") - expected = File.expand_path("#{xdg_config_home}/readline/inputrc") - FileUtils.mkdir_p(File.dirname(expected)) - FileUtils.touch(expected) - ENV['HOME'] = @tmpdir - ENV['XDG_CONFIG_HOME'] = xdg_config_home - ENV['INPUTRC'] = '' - assert_equal expected, @config.inputrc_path - ensure - FileUtils.rm(expected) - ENV['XDG_CONFIG_HOME'] = xdg_config_home_backup - ENV['HOME'] = home_backup - ENV['INPUTRC'] = inputrc_backup - end - - def test_empty_xdg_config_home - home_backup = ENV['HOME'] - xdg_config_home_backup = ENV['XDG_CONFIG_HOME'] - inputrc_backup = ENV['INPUTRC'] - ENV['HOME'] = @tmpdir - ENV['XDG_CONFIG_HOME'] = '' - ENV['INPUTRC'] = '' - expected = File.expand_path('~/.config/readline/inputrc') - FileUtils.mkdir_p(File.dirname(expected)) - FileUtils.touch(expected) - assert_equal expected, @config.inputrc_path - ensure - FileUtils.rm(expected) - ENV['XDG_CONFIG_HOME'] = xdg_config_home_backup - ENV['HOME'] = home_backup - ENV['INPUTRC'] = inputrc_backup - end - - def test_relative_xdg_config_home - home_backup = ENV['HOME'] - xdg_config_home_backup = ENV['XDG_CONFIG_HOME'] - inputrc_backup = ENV['INPUTRC'] - ENV['HOME'] = @tmpdir - ENV['INPUTRC'] = '' - expected = File.expand_path('~/.config/readline/inputrc') - FileUtils.mkdir_p(File.dirname(expected)) - FileUtils.touch(expected) - result = Dir.chdir(@tmpdir) do - xdg_config_home = ".config/example_dir" - ENV['XDG_CONFIG_HOME'] = xdg_config_home - inputrc = "#{xdg_config_home}/readline/inputrc" - FileUtils.mkdir_p(File.dirname(inputrc)) - FileUtils.touch(inputrc) - @config.inputrc_path - end - assert_equal expected, result - FileUtils.rm(expected) - ENV['XDG_CONFIG_HOME'] = xdg_config_home_backup - ENV['HOME'] = home_backup - ENV['INPUTRC'] = inputrc_backup - end - - def test_reload - inputrc = "#{@tmpdir}/inputrc" - ENV['INPUTRC'] = inputrc - - File.write(inputrc, "set emacs-mode-string !") - @config.read - assert_equal '!', @config.emacs_mode_string - - File.write(inputrc, "set emacs-mode-string ?") - @config.reload - assert_equal '?', @config.emacs_mode_string - - File.write(inputrc, "") - @config.reload - assert_equal '@', @config.emacs_mode_string - end -end diff --git a/test/reline/test_face.rb b/test/reline/test_face.rb deleted file mode 100644 index 8fa2be8fa4..0000000000 --- a/test/reline/test_face.rb +++ /dev/null @@ -1,257 +0,0 @@ -# frozen_string_literal: true - -require_relative 'helper' - -class Reline::Face::Test < Reline::TestCase - RESET_SGR = "\e[0m" - - def setup - @colorterm_backup = ENV['COLORTERM'] - ENV['COLORTERM'] = 'truecolor' - end - - def teardown - Reline::Face.reset_to_initial_configs - ENV['COLORTERM'] = @colorterm_backup - end - - class WithInsufficientSetupTest < self - def setup - super - Reline::Face.config(:my_insufficient_config) do |face| - end - @face = Reline::Face[:my_insufficient_config] - end - - def test_my_insufficient_config_line - assert_equal RESET_SGR, @face[:default] - assert_equal RESET_SGR, @face[:enhanced] - assert_equal RESET_SGR, @face[:scrollbar] - end - - def test_my_insufficient_configs - my_configs = Reline::Face.configs[:my_insufficient_config] - assert_equal( - { - default: { style: :reset, escape_sequence: RESET_SGR }, - enhanced: { style: :reset, escape_sequence: RESET_SGR }, - scrollbar: { style: :reset, escape_sequence: RESET_SGR } - }, - my_configs - ) - end - end - - class WithSetupTest < self - def setup - super - Reline::Face.config(:my_config) do |face| - face.define :default, foreground: :blue - face.define :enhanced, foreground: "#FF1020", background: :black, style: [:bold, :underlined] - end - Reline::Face.config(:another_config) do |face| - face.define :another_label, foreground: :red - end - @face = Reline::Face[:my_config] - end - - def test_now_there_are_four_configs - assert_equal %i(default completion_dialog my_config another_config), Reline::Face.configs.keys - end - - def test_resetting_config_discards_user_defined_configs - Reline::Face.reset_to_initial_configs - assert_equal %i(default completion_dialog), Reline::Face.configs.keys - end - - def test_my_configs - my_configs = Reline::Face.configs[:my_config] - assert_equal( - { - default: { - escape_sequence: "#{RESET_SGR}\e[34m", foreground: :blue - }, - enhanced: { - background: :black, - foreground: "#FF1020", - style: [:bold, :underlined], - escape_sequence: "\e[0m\e[38;2;255;16;32;40;1;4m" - }, - scrollbar: { - style: :reset, - escape_sequence: "\e[0m" - } - }, - my_configs - ) - end - - def test_my_config_line - assert_equal "#{RESET_SGR}\e[34m", @face[:default] - end - - def test_my_config_enhanced - assert_equal "#{RESET_SGR}\e[38;2;255;16;32;40;1;4m", @face[:enhanced] - end - - def test_not_respond_to_another_label - assert_equal false, @face.respond_to?(:another_label) - end - end - - class WithoutSetupTest < self - def test_my_config_default - Reline::Face.config(:my_config) do |face| - # do nothing - end - face = Reline::Face[:my_config] - assert_equal RESET_SGR, face[:default] - end - - def test_style_does_not_exist - face = Reline::Face[:default] - assert_raise ArgumentError do - face[:style_does_not_exist] - end - end - - def test_invalid_keyword - assert_raise ArgumentError do - Reline::Face.config(:invalid_config) do |face| - face.define :default, invalid_keyword: :red - end - end - end - - def test_invalid_foreground_name - assert_raise ArgumentError do - Reline::Face.config(:invalid_config) do |face| - face.define :default, foreground: :invalid_name - end - end - end - - def test_invalid_background_name - assert_raise ArgumentError do - Reline::Face.config(:invalid_config) do |face| - face.define :default, background: :invalid_name - end - end - end - - def test_invalid_style_name - assert_raise ArgumentError do - Reline::Face.config(:invalid_config) do |face| - face.define :default, style: :invalid_name - end - end - end - - def test_private_constants - [:SGR_PARAMETER, :Config, :CONFIGS].each do |name| - assert_equal false, Reline::Face.constants.include?(name) - end - end - end - - class ConfigTest < self - def setup - super - @config = Reline::Face.const_get(:Config).new(:my_config) { } - end - - def teardown - super - Reline::Face.instance_variable_set(:@force_truecolor, nil) - end - - def test_rgb? - assert_equal true, @config.send(:rgb_expression?, "#FFFFFF") - end - - def test_invalid_rgb? - assert_equal false, @config.send(:rgb_expression?, "FFFFFF") - assert_equal false, @config.send(:rgb_expression?, "#FFFFF") - end - - def test_format_to_sgr_preserves_order - assert_equal( - "#{RESET_SGR}\e[37;41;1;3m", - @config.send(:format_to_sgr, foreground: :white, background: :red, style: [:bold, :italicized]) - ) - - assert_equal( - "#{RESET_SGR}\e[37;1;3;41m", - @config.send(:format_to_sgr, foreground: :white, style: [:bold, :italicized], background: :red) - ) - end - - def test_format_to_sgr_with_reset - assert_equal( - RESET_SGR, - @config.send(:format_to_sgr, style: :reset) - ) - assert_equal( - "#{RESET_SGR}\e[37;0;41m", - @config.send(:format_to_sgr, foreground: :white, style: :reset, background: :red) - ) - end - - def test_format_to_sgr_with_single_style - assert_equal( - "#{RESET_SGR}\e[37;41;1m", - @config.send(:format_to_sgr, foreground: :white, background: :red, style: :bold) - ) - end - - def test_truecolor - ENV['COLORTERM'] = 'truecolor' - assert_equal true, Reline::Face.truecolor? - ENV['COLORTERM'] = '24bit' - assert_equal true, Reline::Face.truecolor? - ENV['COLORTERM'] = nil - assert_equal false, Reline::Face.truecolor? - Reline::Face.force_truecolor - assert_equal true, Reline::Face.truecolor? - end - - def test_sgr_rgb_truecolor - ENV['COLORTERM'] = 'truecolor' - assert_equal "38;2;255;255;255", @config.send(:sgr_rgb, :foreground, "#ffffff") - assert_equal "48;2;18;52;86", @config.send(:sgr_rgb, :background, "#123456") - end - - def test_sgr_rgb_256color - ENV['COLORTERM'] = nil - assert_equal '38;5;231', @config.send(:sgr_rgb, :foreground, '#ffffff') - assert_equal '48;5;16', @config.send(:sgr_rgb, :background, '#000000') - # Color steps are [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff] - assert_equal '38;5;24', @config.send(:sgr_rgb, :foreground, '#005f87') - assert_equal '38;5;67', @config.send(:sgr_rgb, :foreground, '#5f87af') - assert_equal '48;5;110', @config.send(:sgr_rgb, :background, '#87afd7') - assert_equal '48;5;153', @config.send(:sgr_rgb, :background, '#afd7ff') - # Boundary values are [0x30, 0x73, 0x9b, 0xc3, 0xeb] - assert_equal '38;5;24', @config.send(:sgr_rgb, :foreground, '#2f729a') - assert_equal '38;5;67', @config.send(:sgr_rgb, :foreground, '#30739b') - assert_equal '48;5;110', @config.send(:sgr_rgb, :background, '#9ac2ea') - assert_equal '48;5;153', @config.send(:sgr_rgb, :background, '#9bc3eb') - end - - def test_force_truecolor_reconfigure - ENV['COLORTERM'] = nil - - Reline::Face.config(:my_config) do |face| - face.define :default, foreground: '#005f87' - face.define :enhanced, background: '#afd7ff' - end - - assert_equal "\e[0m\e[38;5;24m", Reline::Face[:my_config][:default] - assert_equal "\e[0m\e[48;5;153m", Reline::Face[:my_config][:enhanced] - - Reline::Face.force_truecolor - - assert_equal "\e[0m\e[38;2;0;95;135m", Reline::Face[:my_config][:default] - assert_equal "\e[0m\e[48;2;175;215;255m", Reline::Face[:my_config][:enhanced] - end - end -end diff --git a/test/reline/test_history.rb b/test/reline/test_history.rb deleted file mode 100644 index ea902b0653..0000000000 --- a/test/reline/test_history.rb +++ /dev/null @@ -1,317 +0,0 @@ -require_relative 'helper' -require "reline/history" - -class Reline::History::Test < Reline::TestCase - def setup - Reline.send(:test_mode) - end - - def teardown - Reline.test_reset - end - - def test_ancestors - assert_equal(Reline::History.ancestors.include?(Array), true) - end - - def test_to_s - history = history_new - expected = "HISTORY" - assert_equal(expected, history.to_s) - end - - def test_get - history, lines = lines = history_new_and_push_history(5) - lines.each_with_index do |s, i| - assert_external_string_equal(s, history[i]) - end - end - - def test_get__negative - history, lines = lines = history_new_and_push_history(5) - (1..5).each do |i| - assert_equal(lines[-i], history[-i]) - end - end - - def test_get__out_of_range - history, _ = history_new_and_push_history(5) - invalid_indexes = [5, 6, 100, -6, -7, -100] - invalid_indexes.each do |i| - assert_raise(IndexError, "i=<#{i}>") do - history[i] - end - end - - invalid_indexes = [100_000_000_000_000_000_000, - -100_000_000_000_000_000_000] - invalid_indexes.each do |i| - assert_raise(RangeError, "i=<#{i}>") do - history[i] - end - end - end - - def test_set - begin - history, _ = history_new_and_push_history(5) - 5.times do |i| - expected = "set: #{i}" - history[i] = expected - assert_external_string_equal(expected, history[i]) - end - rescue NotImplementedError - end - end - - def test_set__out_of_range - history = history_new - assert_raise(IndexError, NotImplementedError, "index=<0>") do - history[0] = "set: 0" - end - - history, _ = history_new_and_push_history(5) - invalid_indexes = [5, 6, 100, -6, -7, -100] - invalid_indexes.each do |i| - assert_raise(IndexError, NotImplementedError, "index=<#{i}>") do - history[i] = "set: #{i}" - end - end - - invalid_indexes = [100_000_000_000_000_000_000, - -100_000_000_000_000_000_000] - invalid_indexes.each do |i| - assert_raise(RangeError, NotImplementedError, "index=<#{i}>") do - history[i] = "set: #{i}" - end - end - end - - def test_push - history = history_new - 5.times do |i| - s = i.to_s - assert_equal(history, history.push(s)) - assert_external_string_equal(s, history[i]) - end - assert_equal(5, history.length) - end - - def test_push__operator - history = history_new - 5.times do |i| - s = i.to_s - assert_equal(history, history << s) - assert_external_string_equal(s, history[i]) - end - assert_equal(5, history.length) - end - - def test_push__plural - history = history_new - assert_equal(history, history.push("0", "1", "2", "3", "4")) - (0..4).each do |i| - assert_external_string_equal(i.to_s, history[i]) - end - assert_equal(5, history.length) - - assert_equal(history, history.push("5", "6", "7", "8", "9")) - (5..9).each do |i| - assert_external_string_equal(i.to_s, history[i]) - end - assert_equal(10, history.length) - end - - def test_pop - history = history_new - begin - assert_equal(nil, history.pop) - - history, lines = lines = history_new_and_push_history(5) - (1..5).each do |i| - assert_external_string_equal(lines[-i], history.pop) - assert_equal(lines.length - i, history.length) - end - - assert_equal(nil, history.pop) - rescue NotImplementedError - end - end - - def test_shift - history = history_new - begin - assert_equal(nil, history.shift) - - history, lines = lines = history_new_and_push_history(5) - (0..4).each do |i| - assert_external_string_equal(lines[i], history.shift) - assert_equal(lines.length - (i + 1), history.length) - end - - assert_equal(nil, history.shift) - rescue NotImplementedError - end - end - - def test_each - history = history_new - e = history.each do |s| - assert(false) # not reachable - end - assert_equal(history, e) - history, lines = lines = history_new_and_push_history(5) - i = 0 - e = history.each do |s| - assert_external_string_equal(history[i], s) - assert_external_string_equal(lines[i], s) - i += 1 - end - assert_equal(history, e) - end - - def test_each__enumerator - history = history_new - e = history.each - assert_instance_of(Enumerator, e) - end - - def test_length - history = history_new - assert_equal(0, history.length) - push_history(history, 1) - assert_equal(1, history.length) - push_history(history, 4) - assert_equal(5, history.length) - history.clear - assert_equal(0, history.length) - end - - def test_empty_p - history = history_new - 2.times do - assert(history.empty?) - history.push("s") - assert_equal(false, history.empty?) - history.clear - assert(history.empty?) - end - end - - def test_delete_at - begin - history, lines = lines = history_new_and_push_history(5) - (0..4).each do |i| - assert_external_string_equal(lines[i], history.delete_at(0)) - end - assert(history.empty?) - - history, lines = lines = history_new_and_push_history(5) - (1..5).each do |i| - assert_external_string_equal(lines[lines.length - i], history.delete_at(-1)) - end - assert(history.empty?) - - history, lines = lines = history_new_and_push_history(5) - assert_external_string_equal(lines[0], history.delete_at(0)) - assert_external_string_equal(lines[4], history.delete_at(3)) - assert_external_string_equal(lines[1], history.delete_at(0)) - assert_external_string_equal(lines[3], history.delete_at(1)) - assert_external_string_equal(lines[2], history.delete_at(0)) - assert(history.empty?) - rescue NotImplementedError - end - end - - def test_delete_at__out_of_range - history = history_new - assert_raise(IndexError, NotImplementedError, "index=<0>") do - history.delete_at(0) - end - - history, _ = history_new_and_push_history(5) - invalid_indexes = [5, 6, 100, -6, -7, -100] - invalid_indexes.each do |i| - assert_raise(IndexError, NotImplementedError, "index=<#{i}>") do - history.delete_at(i) - end - end - - invalid_indexes = [100_000_000_000_000_000_000, - -100_000_000_000_000_000_000] - invalid_indexes.each do |i| - assert_raise(RangeError, NotImplementedError, "index=<#{i}>") do - history.delete_at(i) - end - end - end - - def test_history_size_zero - history = history_new(history_size: 0) - assert_equal 0, history.size - history << 'aa' - history << 'bb' - assert_equal 0, history.size - history.push(*%w{aa bb cc}) - assert_equal 0, history.size - end - - def test_history_size_negative_unlimited - history = history_new(history_size: -1) - assert_equal 0, history.size - history << 'aa' - history << 'bb' - assert_equal 2, history.size - history.push(*%w{aa bb cc}) - assert_equal 5, history.size - end - - def test_history_encoding_conversion - history = history_new - text1 = String.new("a\u{65535}b\xFFc", encoding: Encoding::UTF_8) - text2 = String.new("d\xFFe", encoding: Encoding::Shift_JIS) - history.push(text1.dup, text2.dup) - expected = [text1, text2].map { |s| s.encode(Reline.encoding_system_needs, invalid: :replace, undef: :replace) } - assert_equal(expected, history.to_a) - end - - private - - def history_new(history_size: 10) - Reline::History.new(Struct.new(:history_size).new(history_size)) - end - - def push_history(history, num) - lines = [] - num.times do |i| - s = "a" - i.times do - s = s.succ - end - lines.push("#{i + 1}:#{s}") - end - history.push(*lines) - return history, lines - end - - def history_new_and_push_history(num) - history = history_new(history_size: 100) - return push_history(history, num) - end - - def assert_external_string_equal(expected, actual) - assert_equal(expected, actual) - mes = "Encoding of #{actual.inspect} is expected #{get_default_internal_encoding.inspect} but #{actual.encoding}" - assert_equal(get_default_internal_encoding, actual.encoding, mes) - end - - def get_default_internal_encoding - if encoding = Reline.core.encoding - encoding - elsif RUBY_PLATFORM =~ /mswin|mingw/ - Encoding.default_internal || Encoding::UTF_8 - else - Encoding.default_internal || Encoding.find("locale") - end - end -end diff --git a/test/reline/test_key_actor_emacs.rb b/test/reline/test_key_actor_emacs.rb deleted file mode 100644 index 78b4c936b9..0000000000 --- a/test/reline/test_key_actor_emacs.rb +++ /dev/null @@ -1,1743 +0,0 @@ -require_relative 'helper' - -class Reline::KeyActor::EmacsTest < Reline::TestCase - def setup - Reline.send(:test_mode) - @prompt = '> ' - @config = Reline::Config.new # Emacs mode is default - @config.autocompletion = false - Reline::HISTORY.instance_variable_set(:@config, @config) - Reline::HISTORY.clear - @encoding = Reline.core.encoding - @line_editor = Reline::LineEditor.new(@config) - @line_editor.reset(@prompt) - end - - def teardown - Reline.test_reset - end - - def test_ed_insert_one - input_keys('a') - assert_line_around_cursor('a', '') - end - - def test_ed_insert_two - input_keys('ab') - assert_line_around_cursor('ab', '') - end - - def test_ed_insert_mbchar_one - input_keys('か') - assert_line_around_cursor('か', '') - end - - def test_ed_insert_mbchar_two - input_keys('かき') - assert_line_around_cursor('かき', '') - end - - def test_ed_insert_for_mbchar_by_plural_code_points - omit_unless_utf8 - input_keys("か\u3099") - assert_line_around_cursor("か\u3099", '') - end - - def test_ed_insert_for_plural_mbchar_by_plural_code_points - omit_unless_utf8 - input_keys("か\u3099き\u3099") - assert_line_around_cursor("か\u3099き\u3099", '') - end - - def test_move_next_and_prev - input_keys('abd') - assert_line_around_cursor('abd', '') - input_keys("\C-b") - assert_line_around_cursor('ab', 'd') - input_keys("\C-b") - assert_line_around_cursor('a', 'bd') - input_keys("\C-f") - assert_line_around_cursor('ab', 'd') - input_keys('c') - assert_line_around_cursor('abc', 'd') - end - - def test_move_next_and_prev_for_mbchar - input_keys('かきけ') - assert_line_around_cursor('かきけ', '') - input_keys("\C-b") - assert_line_around_cursor('かき', 'け') - input_keys("\C-b") - assert_line_around_cursor('か', 'きけ') - input_keys("\C-f") - assert_line_around_cursor('かき', 'け') - input_keys('く') - assert_line_around_cursor('かきく', 'け') - end - - def test_move_next_and_prev_for_mbchar_by_plural_code_points - omit_unless_utf8 - input_keys("か\u3099き\u3099け\u3099") - assert_line_around_cursor("か\u3099き\u3099け\u3099", '') - input_keys("\C-b") - assert_line_around_cursor("か\u3099き\u3099", "け\u3099") - input_keys("\C-b") - assert_line_around_cursor("か\u3099", "き\u3099け\u3099") - input_keys("\C-f") - assert_line_around_cursor("か\u3099き\u3099", "け\u3099") - input_keys("く\u3099") - assert_line_around_cursor("か\u3099き\u3099く\u3099", "け\u3099") - end - - def test_move_to_beg_end - input_keys('bcd') - assert_line_around_cursor('bcd', '') - input_keys("\C-a") - assert_line_around_cursor('', 'bcd') - input_keys('a') - assert_line_around_cursor('a', 'bcd') - input_keys("\C-e") - assert_line_around_cursor('abcd', '') - input_keys('e') - assert_line_around_cursor('abcde', '') - end - - def test_ed_newline_with_cr - input_keys('ab') - assert_line_around_cursor('ab', '') - refute(@line_editor.finished?) - input_keys("\C-m") - assert_line_around_cursor('ab', '') - assert(@line_editor.finished?) - end - - def test_ed_newline_with_lf - input_keys('ab') - assert_line_around_cursor('ab', '') - refute(@line_editor.finished?) - input_keys("\C-j") - assert_line_around_cursor('ab', '') - assert(@line_editor.finished?) - end - - def test_em_delete_prev_char - input_keys('ab') - assert_line_around_cursor('ab', '') - input_keys("\C-h") - assert_line_around_cursor('a', '') - end - - def test_em_delete_prev_char_for_mbchar - input_keys('かき') - assert_line_around_cursor('かき', '') - input_keys("\C-h") - assert_line_around_cursor('か', '') - end - - def test_em_delete_prev_char_for_mbchar_by_plural_code_points - omit_unless_utf8 - input_keys("か\u3099き\u3099") - assert_line_around_cursor("か\u3099き\u3099", '') - input_keys("\C-h") - assert_line_around_cursor("か\u3099", '') - end - - def test_bracketed_paste_insert - set_line_around_cursor('A', 'Z') - input_key_by_symbol(:insert_multiline_text, char: "abc\n\C-abc") - assert_whole_lines(['Aabc', "\C-abcZ"]) - assert_line_around_cursor("\C-abc", 'Z') - end - - def test_ed_quoted_insert - set_line_around_cursor('A', 'Z') - input_key_by_symbol(:insert_raw_char, char: "\C-a") - assert_line_around_cursor("A\C-a", 'Z') - end - - def test_ed_quoted_insert_with_vi_arg - input_keys("a\C-[3") - input_key_by_symbol(:insert_raw_char, char: "\C-a") - input_keys("b\C-[4") - input_key_by_symbol(:insert_raw_char, char: '1') - assert_line_around_cursor("a\C-a\C-a\C-ab1111", '') - end - - def test_ed_kill_line - input_keys("\C-k") - assert_line_around_cursor('', '') - input_keys('abc') - assert_line_around_cursor('abc', '') - input_keys("\C-k") - assert_line_around_cursor('abc', '') - input_keys("\C-b\C-k") - assert_line_around_cursor('ab', '') - end - - def test_em_kill_line - input_key_by_symbol(:em_kill_line) - assert_line_around_cursor('', '') - input_keys('abc') - input_key_by_symbol(:em_kill_line) - assert_line_around_cursor('', '') - input_keys('abc') - input_keys("\C-b") - input_key_by_symbol(:em_kill_line) - assert_line_around_cursor('', '') - input_keys('abc') - input_keys("\C-a") - input_key_by_symbol(:em_kill_line) - assert_line_around_cursor('', '') - end - - def test_ed_move_to_beg - input_keys('abd') - assert_line_around_cursor('abd', '') - input_keys("\C-b") - assert_line_around_cursor('ab', 'd') - input_keys('c') - assert_line_around_cursor('abc', 'd') - input_keys("\C-a") - assert_line_around_cursor('', 'abcd') - input_keys('012') - assert_line_around_cursor('012', 'abcd') - input_keys("\C-a") - assert_line_around_cursor('', '012abcd') - input_keys('ABC') - assert_line_around_cursor('ABC', '012abcd') - input_keys("\C-f" * 10 + "\C-a") - assert_line_around_cursor('', 'ABC012abcd') - input_keys('a') - assert_line_around_cursor('a', 'ABC012abcd') - end - - def test_ed_move_to_beg_with_blank - input_keys(' abc') - assert_line_around_cursor(' abc', '') - input_keys("\C-a") - assert_line_around_cursor('', ' abc') - end - - def test_ed_move_to_end - input_keys('abd') - assert_line_around_cursor('abd', '') - input_keys("\C-b") - assert_line_around_cursor('ab', 'd') - input_keys('c') - assert_line_around_cursor('abc', 'd') - input_keys("\C-e") - assert_line_around_cursor('abcd', '') - input_keys('012') - assert_line_around_cursor('abcd012', '') - input_keys("\C-e") - assert_line_around_cursor('abcd012', '') - input_keys('ABC') - assert_line_around_cursor('abcd012ABC', '') - input_keys("\C-b" * 10 + "\C-e") - assert_line_around_cursor('abcd012ABC', '') - input_keys('a') - assert_line_around_cursor('abcd012ABCa', '') - end - - def test_em_delete - input_keys('ab') - assert_line_around_cursor('ab', '') - input_keys("\C-a") - assert_line_around_cursor('', 'ab') - input_keys("\C-d") - assert_line_around_cursor('', 'b') - end - - def test_em_delete_for_mbchar - input_keys('かき') - assert_line_around_cursor('かき', '') - input_keys("\C-a") - assert_line_around_cursor('', 'かき') - input_keys("\C-d") - assert_line_around_cursor('', 'き') - end - - def test_em_delete_for_mbchar_by_plural_code_points - omit_unless_utf8 - input_keys("か\u3099き\u3099") - assert_line_around_cursor("か\u3099き\u3099", '') - input_keys("\C-a") - assert_line_around_cursor('', "か\u3099き\u3099") - input_keys("\C-d") - assert_line_around_cursor('', "き\u3099") - end - - def test_em_delete_ends_editing - input_keys("\C-d") # quit from inputing - assert_nil(@line_editor.line) - assert(@line_editor.finished?) - end - - def test_ed_clear_screen - @line_editor.instance_variable_get(:@rendered_screen).lines = [[]] - input_keys("\C-l") - assert_empty(@line_editor.instance_variable_get(:@rendered_screen).lines) - end - - def test_ed_clear_screen_with_inputted - input_keys('abc') - input_keys("\C-b") - @line_editor.instance_variable_get(:@rendered_screen).lines = [[]] - assert_line_around_cursor('ab', 'c') - input_keys("\C-l") - 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_line_around_cursor('abc', '') - input_key_by_symbol(:key_delete) - assert_line_around_cursor('abc', '') - end - - def test_key_delete_does_not_end_editing - input_key_by_symbol(:key_delete) - assert_line_around_cursor('', '') - refute(@line_editor.finished?) - end - - def test_key_delete_preserves_cursor - input_keys('abc') - input_keys("\C-b") - assert_line_around_cursor('ab', 'c') - input_key_by_symbol(:key_delete) - assert_line_around_cursor('ab', '') - end - - def test_em_next_word - assert_line_around_cursor('', '') - input_keys('abc def{bbb}ccc') - input_keys("\C-a\eF") - assert_line_around_cursor('abc', ' def{bbb}ccc') - input_keys("\eF") - assert_line_around_cursor('abc def', '{bbb}ccc') - input_keys("\eF") - assert_line_around_cursor('abc def{bbb', '}ccc') - input_keys("\eF") - assert_line_around_cursor('abc def{bbb}ccc', '') - input_keys("\eF") - assert_line_around_cursor('abc def{bbb}ccc', '') - end - - def test_em_next_word_for_mbchar - assert_line_around_cursor('', '') - input_keys('あいう かきく{さしす}たちつ') - input_keys("\C-a\eF") - assert_line_around_cursor('あいう', ' かきく{さしす}たちつ') - input_keys("\eF") - assert_line_around_cursor('あいう かきく', '{さしす}たちつ') - input_keys("\eF") - assert_line_around_cursor('あいう かきく{さしす', '}たちつ') - input_keys("\eF") - assert_line_around_cursor('あいう かきく{さしす}たちつ', '') - input_keys("\eF") - assert_line_around_cursor('あいう かきく{さしす}たちつ', '') - end - - def test_em_next_word_for_mbchar_by_plural_code_points - omit_unless_utf8 - assert_line_around_cursor("", "") - input_keys("あいう か\u3099き\u3099く\u3099{さしす}たちつ") - input_keys("\C-a\eF") - assert_line_around_cursor("あいう", " か\u3099き\u3099く\u3099{さしす}たちつ") - input_keys("\eF") - assert_line_around_cursor("あいう か\u3099き\u3099く\u3099", "{さしす}たちつ") - input_keys("\eF") - assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす", "}たちつ") - input_keys("\eF") - assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}たちつ", "") - input_keys("\eF") - assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}たちつ", "") - end - - def test_em_prev_word - input_keys('abc def{bbb}ccc') - assert_line_around_cursor('abc def{bbb}ccc', '') - input_keys("\eB") - assert_line_around_cursor('abc def{bbb}', 'ccc') - input_keys("\eB") - assert_line_around_cursor('abc def{', 'bbb}ccc') - input_keys("\eB") - assert_line_around_cursor('abc ', 'def{bbb}ccc') - input_keys("\eB") - assert_line_around_cursor('', 'abc def{bbb}ccc') - input_keys("\eB") - assert_line_around_cursor('', 'abc def{bbb}ccc') - end - - def test_em_prev_word_for_mbchar - input_keys('あいう かきく{さしす}たちつ') - assert_line_around_cursor('あいう かきく{さしす}たちつ', '') - input_keys("\eB") - assert_line_around_cursor('あいう かきく{さしす}', 'たちつ') - input_keys("\eB") - assert_line_around_cursor('あいう かきく{', 'さしす}たちつ') - input_keys("\eB") - assert_line_around_cursor('あいう ', 'かきく{さしす}たちつ') - input_keys("\eB") - assert_line_around_cursor('', 'あいう かきく{さしす}たちつ') - input_keys("\eB") - assert_line_around_cursor('', 'あいう かきく{さしす}たちつ') - end - - def test_em_prev_word_for_mbchar_by_plural_code_points - omit_unless_utf8 - input_keys("あいう か\u3099き\u3099く\u3099{さしす}たちつ") - assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}たちつ", "") - input_keys("\eB") - assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}", "たちつ") - input_keys("\eB") - assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{", "さしす}たちつ") - input_keys("\eB") - assert_line_around_cursor("あいう ", "か\u3099き\u3099く\u3099{さしす}たちつ") - input_keys("\eB") - assert_line_around_cursor("", "あいう か\u3099き\u3099く\u3099{さしす}たちつ") - input_keys("\eB") - assert_line_around_cursor("", "あいう か\u3099き\u3099く\u3099{さしす}たちつ") - end - - def test_em_delete_next_word - input_keys('abc def{bbb}ccc') - input_keys("\C-a") - assert_line_around_cursor('', 'abc def{bbb}ccc') - input_keys("\ed") - assert_line_around_cursor('', ' def{bbb}ccc') - input_keys("\ed") - assert_line_around_cursor('', '{bbb}ccc') - input_keys("\ed") - assert_line_around_cursor('', '}ccc') - input_keys("\ed") - assert_line_around_cursor('', '') - end - - def test_em_delete_next_word_for_mbchar - input_keys('あいう かきく{さしす}たちつ') - input_keys("\C-a") - assert_line_around_cursor('', 'あいう かきく{さしす}たちつ') - input_keys("\ed") - assert_line_around_cursor('', ' かきく{さしす}たちつ') - input_keys("\ed") - assert_line_around_cursor('', '{さしす}たちつ') - input_keys("\ed") - assert_line_around_cursor('', '}たちつ') - input_keys("\ed") - assert_line_around_cursor('', '') - end - - def test_em_delete_next_word_for_mbchar_by_plural_code_points - omit_unless_utf8 - input_keys("あいう か\u3099き\u3099く\u3099{さしす}たちつ") - input_keys("\C-a") - assert_line_around_cursor('', "あいう か\u3099き\u3099く\u3099{さしす}たちつ") - input_keys("\ed") - assert_line_around_cursor('', " か\u3099き\u3099く\u3099{さしす}たちつ") - input_keys("\ed") - assert_line_around_cursor('', '{さしす}たちつ') - input_keys("\ed") - assert_line_around_cursor('', '}たちつ') - input_keys("\ed") - assert_line_around_cursor('', '') - end - - def test_ed_delete_prev_word - input_keys('abc def{bbb}ccc') - assert_line_around_cursor('abc def{bbb}ccc', '') - input_keys("\e\C-H") - assert_line_around_cursor('abc def{bbb}', '') - input_keys("\e\C-H") - assert_line_around_cursor('abc def{', '') - input_keys("\e\C-H") - assert_line_around_cursor('abc ', '') - input_keys("\e\C-H") - assert_line_around_cursor('', '') - end - - def test_ed_delete_prev_word_for_mbchar - input_keys('あいう かきく{さしす}たちつ') - assert_line_around_cursor('あいう かきく{さしす}たちつ', '') - input_keys("\e\C-H") - assert_line_around_cursor('あいう かきく{さしす}', '') - input_keys("\e\C-H") - assert_line_around_cursor('あいう かきく{', '') - input_keys("\e\C-H") - assert_line_around_cursor('あいう ', '') - input_keys("\e\C-H") - assert_line_around_cursor('', '') - end - - def test_ed_delete_prev_word_for_mbchar_by_plural_code_points - omit_unless_utf8 - input_keys("あいう か\u3099き\u3099く\u3099{さしす}たちつ") - assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}たちつ", '') - input_keys("\e\C-H") - assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}", '') - input_keys("\e\C-H") - assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{", '') - input_keys("\e\C-H") - assert_line_around_cursor('あいう ', '') - input_keys("\e\C-H") - assert_line_around_cursor('', '') - end - - def test_ed_transpose_chars - input_keys('abc') - input_keys("\C-a") - assert_line_around_cursor('', 'abc') - input_keys("\C-t") - assert_line_around_cursor('', 'abc') - input_keys("\C-f\C-t") - assert_line_around_cursor('ba', 'c') - input_keys("\C-t") - assert_line_around_cursor('bca', '') - input_keys("\C-t") - assert_line_around_cursor('bac', '') - end - - def test_ed_transpose_chars_for_mbchar - input_keys('あかさ') - input_keys("\C-a") - assert_line_around_cursor('', 'あかさ') - input_keys("\C-t") - assert_line_around_cursor('', 'あかさ') - input_keys("\C-f\C-t") - assert_line_around_cursor('かあ', 'さ') - input_keys("\C-t") - assert_line_around_cursor('かさあ', '') - input_keys("\C-t") - assert_line_around_cursor('かあさ', '') - end - - def test_ed_transpose_chars_for_mbchar_by_plural_code_points - omit_unless_utf8 - input_keys("あか\u3099さ") - input_keys("\C-a") - assert_line_around_cursor('', "あか\u3099さ") - input_keys("\C-t") - assert_line_around_cursor('', "あか\u3099さ") - input_keys("\C-f\C-t") - assert_line_around_cursor("か\u3099あ", 'さ') - input_keys("\C-t") - assert_line_around_cursor("か\u3099さあ", '') - input_keys("\C-t") - assert_line_around_cursor("か\u3099あさ", '') - end - - def test_ed_transpose_words - input_keys('abc def') - assert_line_around_cursor('abc def', '') - input_keys("\et") - assert_line_around_cursor('def abc', '') - input_keys("\C-a\C-k") - input_keys(' abc def ') - input_keys("\C-b" * 4) - assert_line_around_cursor(' abc de', 'f ') - input_keys("\et") - assert_line_around_cursor(' def abc', ' ') - input_keys("\C-a\C-k") - input_keys(' abc def ') - input_keys("\C-b" * 6) - assert_line_around_cursor(' abc ', 'def ') - input_keys("\et") - assert_line_around_cursor(' def abc', ' ') - input_keys("\et") - assert_line_around_cursor(' abc def', '') - end - - def test_ed_transpose_words_for_mbchar - input_keys('あいう かきく') - assert_line_around_cursor('あいう かきく', '') - input_keys("\et") - assert_line_around_cursor('かきく あいう', '') - input_keys("\C-a\C-k") - input_keys(' あいう かきく ') - input_keys("\C-b" * 4) - assert_line_around_cursor(' あいう かき', 'く ') - input_keys("\et") - assert_line_around_cursor(' かきく あいう', ' ') - input_keys("\C-a\C-k") - input_keys(' あいう かきく ') - input_keys("\C-b" * 6) - assert_line_around_cursor(' あいう ', 'かきく ') - input_keys("\et") - assert_line_around_cursor(' かきく あいう', ' ') - input_keys("\et") - assert_line_around_cursor(' あいう かきく', '') - end - - def test_ed_transpose_words_with_one_word - input_keys('abc ') - assert_line_around_cursor('abc ', '') - input_keys("\et") - assert_line_around_cursor('abc ', '') - input_keys("\C-b") - assert_line_around_cursor('abc ', ' ') - input_keys("\et") - assert_line_around_cursor('abc ', ' ') - input_keys("\C-b" * 2) - assert_line_around_cursor('ab', 'c ') - input_keys("\et") - assert_line_around_cursor('ab', 'c ') - input_keys("\et") - assert_line_around_cursor('ab', 'c ') - end - - def test_ed_transpose_words_with_one_word_for_mbchar - input_keys('あいう ') - assert_line_around_cursor('あいう ', '') - input_keys("\et") - assert_line_around_cursor('あいう ', '') - input_keys("\C-b") - assert_line_around_cursor('あいう ', ' ') - input_keys("\et") - assert_line_around_cursor('あいう ', ' ') - input_keys("\C-b" * 2) - assert_line_around_cursor('あい', 'う ') - input_keys("\et") - assert_line_around_cursor('あい', 'う ') - input_keys("\et") - assert_line_around_cursor('あい', 'う ') - end - - def test_ed_digit - input_keys('0123') - assert_line_around_cursor('0123', '') - end - - def test_ed_next_and_prev_char - input_keys('abc') - assert_line_around_cursor('abc', '') - input_keys("\C-b") - assert_line_around_cursor('ab', 'c') - input_keys("\C-b") - assert_line_around_cursor('a', 'bc') - input_keys("\C-b") - assert_line_around_cursor('', 'abc') - input_keys("\C-b") - assert_line_around_cursor('', 'abc') - input_keys("\C-f") - assert_line_around_cursor('a', 'bc') - input_keys("\C-f") - assert_line_around_cursor('ab', 'c') - input_keys("\C-f") - assert_line_around_cursor('abc', '') - input_keys("\C-f") - assert_line_around_cursor('abc', '') - end - - def test_ed_next_and_prev_char_for_mbchar - input_keys('あいう') - assert_line_around_cursor('あいう', '') - input_keys("\C-b") - assert_line_around_cursor('あい', 'う') - input_keys("\C-b") - assert_line_around_cursor('あ', 'いう') - input_keys("\C-b") - assert_line_around_cursor('', 'あいう') - input_keys("\C-b") - assert_line_around_cursor('', 'あいう') - input_keys("\C-f") - assert_line_around_cursor('あ', 'いう') - input_keys("\C-f") - assert_line_around_cursor('あい', 'う') - input_keys("\C-f") - assert_line_around_cursor('あいう', '') - input_keys("\C-f") - assert_line_around_cursor('あいう', '') - end - - def test_ed_next_and_prev_char_for_mbchar_by_plural_code_points - omit_unless_utf8 - input_keys("か\u3099き\u3099く\u3099") - assert_line_around_cursor("か\u3099き\u3099く\u3099", '') - input_keys("\C-b") - assert_line_around_cursor("か\u3099き\u3099", "く\u3099") - input_keys("\C-b") - assert_line_around_cursor("か\u3099", "き\u3099く\u3099") - input_keys("\C-b") - assert_line_around_cursor('', "か\u3099き\u3099く\u3099") - input_keys("\C-b") - assert_line_around_cursor('', "か\u3099き\u3099く\u3099") - input_keys("\C-f") - assert_line_around_cursor("か\u3099", "き\u3099く\u3099") - input_keys("\C-f") - assert_line_around_cursor("か\u3099き\u3099", "く\u3099") - input_keys("\C-f") - assert_line_around_cursor("か\u3099き\u3099く\u3099", '') - input_keys("\C-f") - assert_line_around_cursor("か\u3099き\u3099く\u3099", '') - end - - def test_em_capitol_case - input_keys('abc def{bbb}ccc') - input_keys("\C-a\ec") - assert_line_around_cursor('Abc', ' def{bbb}ccc') - input_keys("\ec") - assert_line_around_cursor('Abc Def', '{bbb}ccc') - input_keys("\ec") - assert_line_around_cursor('Abc Def{Bbb', '}ccc') - input_keys("\ec") - 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\ec") - assert_line_around_cursor('{}#* Aaa', '!!!cCc ') - input_keys("\ec") - assert_line_around_cursor('{}#* Aaa!!!Ccc', ' ') - input_keys("\ec") - assert_line_around_cursor('{}#* Aaa!!!Ccc ', '') - end - - def test_em_lower_case - input_keys('AbC def{bBb}CCC') - input_keys("\C-a\el") - assert_line_around_cursor('abc', ' def{bBb}CCC') - input_keys("\el") - assert_line_around_cursor('abc def', '{bBb}CCC') - input_keys("\el") - assert_line_around_cursor('abc def{bbb', '}CCC') - input_keys("\el") - 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\el") - assert_line_around_cursor('{}#* aaa', '!!!cCc ') - input_keys("\el") - assert_line_around_cursor('{}#* aaa!!!ccc', ' ') - input_keys("\el") - assert_line_around_cursor('{}#* aaa!!!ccc ', '') - end - - def test_em_upper_case - input_keys('AbC def{bBb}CCC') - input_keys("\C-a\eu") - assert_line_around_cursor('ABC', ' def{bBb}CCC') - input_keys("\eu") - assert_line_around_cursor('ABC DEF', '{bBb}CCC') - input_keys("\eu") - assert_line_around_cursor('ABC DEF{BBB', '}CCC') - input_keys("\eu") - 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\eu") - assert_line_around_cursor('{}#* AAA', '!!!cCc ') - input_keys("\eu") - assert_line_around_cursor('{}#* AAA!!!CCC', ' ') - input_keys("\eu") - assert_line_around_cursor('{}#* AAA!!!CCC ', '') - end - - def test_em_delete_or_list - @line_editor.completion_proc = proc { |word| - %w{ - foo_foo - foo_bar - foo_baz - qux - }.map { |i| - i.encode(@encoding) - } - } - input_keys('fooo') - assert_line_around_cursor('fooo', '') - assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) - input_keys("\C-b") - assert_line_around_cursor('foo', 'o') - assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) - input_key_by_symbol(:em_delete_or_list) - assert_line_around_cursor('foo', '') - assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) - input_key_by_symbol(:em_delete_or_list) - assert_line_around_cursor('foo', '') - assert_equal(%w{foo_foo foo_bar foo_baz}, @line_editor.instance_variable_get(:@menu_info).list) - end - - def test_completion_duplicated_list - @line_editor.completion_proc = proc { |word| - %w{ - foo_foo - foo_foo - foo_bar - }.map { |i| - i.encode(@encoding) - } - } - input_keys('foo_') - assert_line_around_cursor('foo_', '') - assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) - input_keys("\C-i") - assert_line_around_cursor('foo_', '') - assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) - input_keys("\C-i") - assert_line_around_cursor('foo_', '') - assert_equal(%w{foo_foo foo_bar}, @line_editor.instance_variable_get(:@menu_info).list) - end - - def test_completion - @line_editor.completion_proc = proc { |word| - %w{ - foo_foo - foo_bar - foo_baz - qux - }.map { |i| - i.encode(@encoding) - } - } - input_keys('fo') - assert_line_around_cursor('fo', '') - assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) - input_keys("\C-i") - assert_line_around_cursor('foo_', '') - assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) - input_keys("\C-i") - 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") - assert_line_around_cursor('foo_a', '') - input_keys("\C-h") - input_keys('b') - input_keys("\C-i") - 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") - 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") - 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") - assert_line_around_cursor('Readline', '') - input_keys("\C-i") - 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 - @line_editor.completion_proc = proc { |word| - %w{ - foo_foo - foo_bar - foo_baz - qux - }.map { |i| - i.encode(@encoding) - } - } - input_keys(' fo') - assert_line_around_cursor(' fo', '') - assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) - input_keys("\C-i") - assert_line_around_cursor(' foo_', '') - assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) - input_keys("\C-i") - assert_line_around_cursor(' foo_', '') - assert_equal(%w{foo_foo foo_bar foo_baz}, @line_editor.instance_variable_get(:@menu_info).list) - end - - def test_completion_with_perfect_match - @line_editor.completion_proc = proc { |word| - %w{ - foo - foo_bar - }.map { |i| - i.encode(@encoding) - } - } - matched = nil - @line_editor.dig_perfect_match_proc = proc { |m| - matched = m - } - input_keys('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") - 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") - 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") - 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") - 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") - 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 - - def test_continuous_completion_with_perfect_match - @line_editor.completion_proc = proc { |word| - word == 'f' ? ['foo'] : %w[foobar foobaz] - } - input_keys('f') - input_keys("\C-i") - assert_line_around_cursor('foo', '') - input_keys("\C-i") - assert_line_around_cursor('fooba', '') - end - - def test_continuous_completion_disabled_with_perfect_match - @line_editor.completion_proc = proc { |word| - word == 'f' ? ['foo'] : %w[foobar foobaz] - } - @line_editor.dig_perfect_match_proc = proc {} - input_keys('f') - input_keys("\C-i") - assert_line_around_cursor('foo', '') - input_keys("\C-i") - assert_line_around_cursor('foo', '') - end - - def test_completion_append_character - @line_editor.completion_proc = proc { |word| - %w[foo_ foo_foo foo_bar].select { |s| s.start_with? word } - } - @line_editor.completion_append_character = 'X' - input_keys('f') - input_keys("\C-i") - assert_line_around_cursor('foo_', '') - input_keys('f') - input_keys("\C-i") - assert_line_around_cursor('foo_fooX', '') - input_keys(' foo_bar') - input_keys("\C-i") - assert_line_around_cursor('foo_fooX foo_barX', '') - end - - def test_completion_with_quote_append - @line_editor.completion_proc = proc { |word| - %w[foo bar baz].select { |s| s.start_with? word } - } - set_line_around_cursor('x = "b', '') - input_keys("\C-i") - assert_line_around_cursor('x = "ba', '') - set_line_around_cursor('x = "f', ' ') - input_keys("\C-i") - assert_line_around_cursor('x = "foo', ' ') - set_line_around_cursor("x = 'f", '') - input_keys("\C-i") - assert_line_around_cursor("x = 'foo'", '') - set_line_around_cursor('"a "f', '') - input_keys("\C-i") - assert_line_around_cursor('"a "foo', '') - set_line_around_cursor('"a\\" "f', '') - input_keys("\C-i") - assert_line_around_cursor('"a\\" "foo', '') - set_line_around_cursor('"a" "f', '') - input_keys("\C-i") - assert_line_around_cursor('"a" "foo"', '') - end - - def test_completion_with_completion_ignore_case - @line_editor.completion_proc = proc { |word| - %w{ - foo_foo - foo_bar - Foo_baz - qux - }.map { |i| - i.encode(@encoding) - } - } - input_keys('fo') - assert_line_around_cursor('fo', '') - assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) - input_keys("\C-i") - assert_line_around_cursor('foo_', '') - assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) - input_keys("\C-i") - 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") - 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") - assert_line_around_cursor('foo_a', '') - input_keys("\C-h") - input_keys('b') - input_keys("\C-i") - assert_line_around_cursor('foo_ba', '') - input_keys('Z') - input_keys("\C-i") - assert_line_around_cursor('Foo_baz', '') - end - - def test_completion_in_middle_of_line - @line_editor.completion_proc = proc { |word| - %w{ - foo_foo - foo_bar - foo_baz - qux - }.map { |i| - i.encode(@encoding) - } - } - input_keys('abcde fo ABCDE') - assert_line_around_cursor('abcde fo ABCDE', '') - input_keys("\C-b" * 6 + "\C-i") - assert_line_around_cursor('abcde foo_', ' ABCDE') - input_keys("\C-b" * 2 + "\C-i") - assert_line_around_cursor('abcde foo_', 'o_ ABCDE') - end - - def test_completion_with_nil_value - @line_editor.completion_proc = proc { |word| - %w{ - foo_foo - foo_bar - Foo_baz - qux - }.map { |i| - i.encode(@encoding) - }.prepend(nil) - } - @config.completion_ignore_case = true - input_keys('fo') - assert_line_around_cursor('fo', '') - assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) - input_keys("\C-i") - assert_line_around_cursor('foo_', '') - assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) - input_keys("\C-i") - 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") - assert_line_around_cursor('foo_a', '') - input_keys("\C-h") - input_keys('b') - input_keys("\C-i") - assert_line_around_cursor('foo_ba', '') - end - - def test_em_kill_region - input_keys('abc def{bbb}ccc ddd ') - assert_line_around_cursor('abc def{bbb}ccc ddd ', '') - input_keys("\C-w") - assert_line_around_cursor('abc def{bbb}ccc ', '') - input_keys("\C-w") - assert_line_around_cursor('abc ', '') - input_keys("\C-w") - assert_line_around_cursor('', '') - input_keys("\C-w") - assert_line_around_cursor('', '') - end - - def test_em_kill_region_mbchar - input_keys('あ い う{う}う ') - assert_line_around_cursor('あ い う{う}う ', '') - input_keys("\C-w") - assert_line_around_cursor('あ い ', '') - input_keys("\C-w") - assert_line_around_cursor('あ ', '') - input_keys("\C-w") - assert_line_around_cursor('', '') - end - - def test_vi_search_prev - Reline::HISTORY.concat(%w{abc 123 AAA}) - assert_line_around_cursor('', '') - input_keys("\C-ra\C-j") - 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_around_cursor('', '') - input_keys("\C-p") - assert_line_around_cursor('AAA', '') - input_keys("\C-p") - assert_line_around_cursor('123', '') - input_keys("\C-p") - assert_line_around_cursor('123', '') - ensure - @config.history_size = history_size - end - - def test_search_history_to_back - Reline::HISTORY.concat([ - '1235', # old - '12aa', - '1234' # new - ]) - assert_line_around_cursor('', '') - input_keys("\C-r123") - assert_line_around_cursor('1234', '') - input_keys("\C-ha") - assert_line_around_cursor('12aa', '') - input_keys("\C-h3") - assert_line_around_cursor('1235', '') - end - - def test_search_history_to_front - Reline::HISTORY.concat([ - '1235', # old - '12aa', - '1234' # new - ]) - assert_line_around_cursor('', '') - input_keys("\C-s123") - assert_line_around_cursor('1235', '') - input_keys("\C-ha") - assert_line_around_cursor('12aa', '') - input_keys("\C-h3") - assert_line_around_cursor('1234', '') - end - - def test_search_history_front_and_back - Reline::HISTORY.concat([ - '1235', # old - '12aa', - '1234' # new - ]) - assert_line_around_cursor('', '') - input_keys("\C-s12") - assert_line_around_cursor('1235', '') - input_keys("\C-s") - assert_line_around_cursor('12aa', '') - input_keys("\C-r") - assert_line_around_cursor('12aa', '') - input_keys("\C-r") - assert_line_around_cursor('1235', '') - end - - def test_search_history_back_and_front - Reline::HISTORY.concat([ - '1235', # old - '12aa', - '1234' # new - ]) - assert_line_around_cursor('', '') - input_keys("\C-r12") - assert_line_around_cursor('1234', '') - input_keys("\C-r") - assert_line_around_cursor('12aa', '') - input_keys("\C-s") - assert_line_around_cursor('12aa', '') - input_keys("\C-s") - assert_line_around_cursor('1234', '') - end - - def test_search_history_to_back_in_the_middle_of_histories - Reline::HISTORY.concat([ - '1235', # old - '12aa', - '1234' # new - ]) - assert_line_around_cursor('', '') - input_keys("\C-p\C-p") - assert_line_around_cursor('12aa', '') - input_keys("\C-r123") - assert_line_around_cursor('1235', '') - end - - def test_search_history_twice - Reline::HISTORY.concat([ - '1235', # old - '12aa', - '1234' # new - ]) - assert_line_around_cursor('', '') - input_keys("\C-r123") - assert_line_around_cursor('1234', '') - input_keys("\C-r") - assert_line_around_cursor('1235', '') - end - - def test_search_history_by_last_determined - Reline::HISTORY.concat([ - '1235', # old - '12aa', - '1234' # new - ]) - assert_line_around_cursor('', '') - input_keys("\C-r123") - assert_line_around_cursor('1234', '') - input_keys("\C-j") - assert_line_around_cursor('', '1234') - input_keys("\C-k") # delete - assert_line_around_cursor('', '') - input_keys("\C-r") - assert_line_around_cursor('', '') - input_keys("\C-r") - assert_line_around_cursor('1235', '') - end - - def test_search_history_with_isearch_terminator - @config.read_lines(<<~LINES.split(/(?<=\n)/)) - set isearch-terminators "XYZ" - LINES - Reline::HISTORY.concat([ - '1235', # old - '12aa', - '1234' # new - ]) - assert_line_around_cursor('', '') - input_keys("\C-r12a") - assert_line_around_cursor('12aa', '') - input_keys('Y') - assert_line_around_cursor('', '12aa') - input_keys('x') - assert_line_around_cursor('x', '12aa') - end - - def test_em_set_mark_and_em_exchange_mark - input_keys('aaa bbb ccc ddd') - assert_line_around_cursor('aaa bbb ccc ddd', '') - input_keys("\C-a\eF\eF") - assert_line_around_cursor('aaa bbb', ' ccc ddd') - assert_equal(nil, @line_editor.instance_variable_get(:@mark_pointer)) - input_keys("\x00") # C-Space - assert_line_around_cursor('aaa bbb', ' ccc ddd') - assert_equal([7, 0], @line_editor.instance_variable_get(:@mark_pointer)) - input_keys("\C-a") - 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_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_line_around_cursor('aaa bbb ccc ddd', '') - input_keys("\C-a\ef") - 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_line_around_cursor('aaa', ' bbb ccc ddd') - assert_equal(nil, @line_editor.instance_variable_get(:@mark_pointer)) - end - - def test_modify_lines_with_wrong_rs - verbose, $VERBOSE = $VERBOSE, nil - original_global_slash = $/ - $/ = 'b' - $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, @line_editor.finished?) - $/ = nil - assert_equal(['abcdef'], result) - ensure - $VERBOSE = nil - $/ = original_global_slash - $VERBOSE = verbose - end - - def test_ed_search_prev_history - Reline::HISTORY.concat([ - '12356', # old - '12aaa', - '12345' # new - ]) - input_keys('123') - # The ed_search_prev_history doesn't have default binding - @line_editor.__send__(:ed_search_prev_history, "\C-p".ord) - assert_line_around_cursor('123', '45') - @line_editor.__send__(:ed_search_prev_history, "\C-p".ord) - assert_line_around_cursor('123', '56') - @line_editor.__send__(:ed_search_prev_history, "\C-p".ord) - assert_line_around_cursor('123', '56') - end - - def test_ed_search_prev_history_with_empty - Reline::HISTORY.concat([ - '12356', # old - '12aaa', - '12345' # new - ]) - # The ed_search_prev_history doesn't have default binding - input_key_by_symbol(:ed_search_prev_history) - assert_line_around_cursor('12345', '') - input_key_by_symbol(:ed_search_prev_history) - assert_line_around_cursor('12aaa', '') - input_key_by_symbol(:ed_search_prev_history) - assert_line_around_cursor('12356', '') - input_key_by_symbol(:ed_search_next_history) - assert_line_around_cursor('12aaa', '') - input_key_by_symbol(:ed_prev_char) - input_key_by_symbol(:ed_next_char) - assert_line_around_cursor('12aaa', '') - input_key_by_symbol(:ed_search_prev_history) - assert_line_around_cursor('12aaa', '') - 3.times { input_key_by_symbol(:ed_prev_char) } - input_key_by_symbol(:ed_search_prev_history) - assert_line_around_cursor('12', '356') - end - - def test_ed_search_prev_history_without_match - Reline::HISTORY.concat([ - '12356', # old - '12aaa', - '12345' # new - ]) - input_keys('ABC') - # The ed_search_prev_history doesn't have default binding - @line_editor.__send__(:ed_search_prev_history, "\C-p".ord) - assert_line_around_cursor('ABC', '') - end - - def test_ed_search_next_history - Reline::HISTORY.concat([ - '12356', # old - '12aaa', - '12345' # new - ]) - 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_line_around_cursor('123', '45') - @line_editor.__send__(:ed_search_prev_history, "\C-p".ord) - assert_line_around_cursor('123', '56') - @line_editor.__send__(:ed_search_prev_history, "\C-p".ord) - assert_line_around_cursor('123', '56') - @line_editor.__send__(:ed_search_next_history, "\C-n".ord) - assert_line_around_cursor('123', '45') - @line_editor.__send__(:ed_search_next_history, "\C-n".ord) - assert_line_around_cursor('123', '45') - end - - def test_ed_search_next_history_with_empty - Reline::HISTORY.concat([ - '12356', # old - '12aaa', - '12345' # new - ]) - # The ed_search_prev_history and ed_search_next_history doesn't have default binding - input_key_by_symbol(:ed_search_prev_history) - assert_line_around_cursor('12345', '') - input_key_by_symbol(:ed_search_prev_history) - assert_line_around_cursor('12aaa', '') - input_key_by_symbol(:ed_search_prev_history) - assert_line_around_cursor('12356', '') - input_key_by_symbol(:ed_search_next_history) - assert_line_around_cursor('12aaa', '') - input_key_by_symbol(:ed_search_next_history) - assert_line_around_cursor('12345', '') - input_key_by_symbol(:ed_search_prev_history) - assert_line_around_cursor('12aaa', '') - input_key_by_symbol(:ed_prev_char) - input_key_by_symbol(:ed_next_char) - input_key_by_symbol(:ed_search_next_history) - assert_line_around_cursor('12aaa', '') - 3.times { input_key_by_symbol(:ed_prev_char) } - input_key_by_symbol(:ed_search_next_history) - assert_line_around_cursor('12', '345') - 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, csi: true) - input_keys('d') - assert_line_around_cursor('abd', 'c') - end - - def test_incremental_search_history_saves_and_restores_last_input - Reline::HISTORY.concat(['abc', '123']) - input_keys("abcd") - # \C-j: terminate incremental search - input_keys("\C-r12\C-j") - assert_line_around_cursor('', '123') - input_key_by_symbol(:ed_next_history) - assert_line_around_cursor('abcd', '') - # Most non-printable keys also terminates incremental search - input_keys("\C-r12\C-i") - assert_line_around_cursor('', '123') - input_key_by_symbol(:ed_next_history) - assert_line_around_cursor('abcd', '') - # \C-g: cancel incremental search and restore input, cursor position and history index - input_key_by_symbol(:ed_prev_history) - input_keys("\C-b\C-b") - assert_line_around_cursor('1', '23') - input_keys("\C-rab\C-g") - assert_line_around_cursor('1', '23') - input_key_by_symbol(:ed_next_history) - assert_line_around_cursor('abcd', '') - end - - # Unicode emoji test - def test_ed_insert_for_include_zwj_emoji - omit_unless_utf8 - # 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_around_cursor('👨', '') - input_keys("\u200D") # U+200D is ZERO WIDTH JOINER - assert_line_around_cursor('👨', '') - input_keys("\u{1F469}") # U+1F469 is woman "👩" - assert_line_around_cursor('👨👩', '') - input_keys("\u200D") # U+200D is ZERO WIDTH JOINER - assert_line_around_cursor('👨👩', '') - input_keys("\u{1F467}") # U+1F467 is girl "👧" - assert_line_around_cursor('👨👩👧', '') - input_keys("\u200D") # U+200D is ZERO WIDTH JOINER - assert_line_around_cursor('👨👩👧', '') - input_keys("\u{1F466}") # U+1F466 is boy "👦" - 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_around_cursor('👨👩👧👦👨👩👧👦', '') - end - - def test_ed_insert_for_include_valiation_selector - omit_unless_utf8 - # U+0030 U+FE00 is DIGIT ZERO + VARIATION SELECTOR-1 "0︀" - input_keys("\u0030") # U+0030 is DIGIT ZERO - assert_line_around_cursor('0', '') - input_keys("\uFE00") # U+FE00 is VARIATION SELECTOR-1 - assert_line_around_cursor('0︀', '') - end - - def test_em_yank_pop - input_keys("def hoge\C-w\C-b\C-f\C-w") - assert_line_around_cursor('', '') - input_keys("\C-y") - assert_line_around_cursor('def ', '') - input_keys("\e\C-y") - 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") - assert_line_around_cursor('def ', 'hoge') - input_keys("\C-k\C-w") - assert_line_around_cursor('', '') - input_keys("\C-y") - assert_line_around_cursor('def hoge', '') - end - - def test_ed_search_prev_next_history_in_multibyte - Reline::HISTORY.concat([ - "def hoge\n 67890\n 12345\nend", # old - "def aiu\n 0xDEADBEEF\nend", - "def foo\n 12345\nend" # new - ]) - @line_editor.multiline_on - input_keys(' 123') - # The ed_search_prev_history doesn't have default binding - @line_editor.__send__(:ed_search_prev_history, "\C-p".ord) - assert_whole_lines(['def foo', ' 12345', 'end']) - assert_line_index(1) - assert_whole_lines(['def foo', ' 12345', 'end']) - 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_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_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_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_line_around_cursor(' 123', '45') - end - - def test_ignore_NUL_by_ed_quoted_insert - input_keys('"') - input_key_by_symbol(:insert_raw_char, char: 0.chr) - input_keys('"') - assert_line_around_cursor('""', '') - end - - def test_ed_argument_digit_by_meta_num - input_keys('abcdef') - assert_line_around_cursor('abcdef', '') - input_keys("\e2") - input_keys("\C-h") - assert_line_around_cursor('abcd', '') - end - - def test_ed_digit_with_ed_argument_digit - input_keys('1' * 30) - assert_line_around_cursor('1' * 30, '') - input_keys("\e2") - input_keys('3') - input_keys("\C-h") - input_keys('4') - assert_line_around_cursor('1' * 7 + '4', '') - end - - def test_halfwidth_kana_width_dakuten - omit_unless_utf8 - input_keys('ガギゲゴ') - assert_line_around_cursor('ガギゲゴ', '') - input_keys("\C-b\C-b") - assert_line_around_cursor('ガギ', 'ゲゴ') - input_keys('グ') - assert_line_around_cursor('ガギグ', 'ゲゴ') - end - - def test_input_unknown_char - omit_unless_utf8 - input_keys('') # U+0378 (unassigned) - assert_line_around_cursor('', '') - end - - def test_unix_line_discard - input_keys("\C-u") - assert_line_around_cursor('', '') - input_keys('abc') - assert_line_around_cursor('abc', '') - input_keys("\C-b\C-u") - assert_line_around_cursor('', 'c') - input_keys("\C-f\C-u") - 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-_") - assert_line_around_cursor('', '') - input_keys("aあb\C-h\C-h\C-h") - assert_line_around_cursor('', '') - input_keys("\C-_") - assert_line_around_cursor('a', '') - input_keys("\C-_") - assert_line_around_cursor('aあ', '') - input_keys("\C-_") - assert_line_around_cursor('aあb', '') - input_keys("\C-_") - assert_line_around_cursor('aあ', '') - input_keys("\C-_") - assert_line_around_cursor('a', '') - input_keys("\C-_") - assert_line_around_cursor('', '') - end - - def test_undo_with_cursor_position - input_keys("abc\C-b\C-h") - assert_line_around_cursor('a', 'c') - input_keys("\C-_") - assert_line_around_cursor('ab', 'c') - input_keys("あいう\C-b\C-h") - assert_line_around_cursor('abあ', 'うc') - input_keys("\C-_") - 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") - assert_whole_lines(["1", "2", "3"]) - assert_line_index(2) - assert_line_around_cursor('3', '') - input_keys("\C-p\C-h\C-h") - assert_whole_lines(["1", "3"]) - assert_line_index(0) - assert_line_around_cursor('1', '') - input_keys("\C-_") - assert_whole_lines(["1", "", "3"]) - assert_line_index(1) - assert_line_around_cursor('', '') - input_keys("\C-_") - assert_whole_lines(["1", "2", "3"]) - assert_line_index(1) - assert_line_around_cursor('2', '') - input_keys("\C-_") - assert_whole_lines(["1", "2", ""]) - assert_line_index(2) - assert_line_around_cursor('', '') - input_keys("\C-_") - assert_whole_lines(["1", "2"]) - assert_line_index(1) - assert_line_around_cursor('2', '') - end - - def test_undo_with_many_times - str = "a" + "b" * 99 - input_keys(str) - 100.times { input_keys("\C-_") } - assert_line_around_cursor('a', '') - input_keys("\C-_") - assert_line_around_cursor('a', '') - end - - def test_redo - input_keys("aあb") - assert_line_around_cursor('aあb', '') - input_keys("\e\C-_") - assert_line_around_cursor('aあb', '') - input_keys("\C-_") - assert_line_around_cursor('aあ', '') - input_keys("\C-_") - assert_line_around_cursor('a', '') - input_keys("\e\C-_") - assert_line_around_cursor('aあ', '') - input_keys("\e\C-_") - assert_line_around_cursor('aあb', '') - input_keys("\C-_") - assert_line_around_cursor('aあ', '') - input_keys("c") - assert_line_around_cursor('aあc', '') - input_keys("\e\C-_") - assert_line_around_cursor('aあc', '') - end - - def test_redo_with_cursor_position - input_keys("abc\C-b\C-h") - assert_line_around_cursor('a', 'c') - input_keys("\e\C-_") - assert_line_around_cursor('a', 'c') - input_keys("\C-_") - assert_line_around_cursor('ab', 'c') - input_keys("\e\C-_") - assert_line_around_cursor('a', 'c') - end - - def test_redo_with_multiline - @line_editor.multiline_on - @line_editor.confirm_multiline_termination_proc = proc {} - input_keys("1\n2\n3") - assert_whole_lines(["1", "2", "3"]) - assert_line_index(2) - assert_line_around_cursor('3', '') - - input_keys("\C-_") - assert_whole_lines(["1", "2", ""]) - assert_line_index(2) - assert_line_around_cursor('', '') - - input_keys("\C-_") - assert_whole_lines(["1", "2"]) - assert_line_index(1) - assert_line_around_cursor('2', '') - - input_keys("\e\C-_") - assert_whole_lines(["1", "2", ""]) - assert_line_index(2) - assert_line_around_cursor('', '') - - input_keys("\e\C-_") - assert_whole_lines(["1", "2", "3"]) - assert_line_index(2) - assert_line_around_cursor('3', '') - - input_keys("\C-p\C-h\C-h") - assert_whole_lines(["1", "3"]) - assert_line_index(0) - assert_line_around_cursor('1', '') - - input_keys("\C-n") - assert_whole_lines(["1", "3"]) - assert_line_index(1) - assert_line_around_cursor('3', '') - - input_keys("\C-_") - assert_whole_lines(["1", "", "3"]) - assert_line_index(1) - assert_line_around_cursor('', '') - - input_keys("\C-_") - assert_whole_lines(["1", "2", "3"]) - assert_line_index(1) - assert_line_around_cursor('2', '') - - input_keys("\e\C-_") - assert_whole_lines(["1", "", "3"]) - assert_line_index(1) - assert_line_around_cursor('', '') - - input_keys("\e\C-_") - assert_whole_lines(["1", "3"]) - assert_line_index(1) - assert_line_around_cursor('3', '') - end - - def test_undo_redo_restores_indentation - @line_editor.multiline_on - @line_editor.confirm_multiline_termination_proc = proc {} - input_keys(" 1") - assert_whole_lines([' 1']) - input_keys("2") - assert_whole_lines([' 12']) - @line_editor.auto_indent_proc = proc { 2 } - input_keys("\C-_") - assert_whole_lines([' 1']) - input_keys("\e\C-_") - assert_whole_lines([' 12']) - end - - def test_redo_with_many_times - str = "a" + "b" * 98 + "c" - input_keys(str) - 100.times { input_keys("\C-_") } - assert_line_around_cursor('a', '') - input_keys("\C-_") - assert_line_around_cursor('a', '') - 100.times { input_keys("\e\C-_") } - assert_line_around_cursor(str, '') - input_keys("\e\C-_") - assert_line_around_cursor(str, '') - end -end diff --git a/test/reline/test_key_actor_vi.rb b/test/reline/test_key_actor_vi.rb deleted file mode 100644 index 083433f9a8..0000000000 --- a/test/reline/test_key_actor_vi.rb +++ /dev/null @@ -1,967 +0,0 @@ -require_relative 'helper' - -class Reline::ViInsertTest < Reline::TestCase - def setup - Reline.send(:test_mode) - @prompt = '> ' - @config = Reline::Config.new - @config.read_lines(<<~LINES.split(/(?<=\n)/)) - set editing-mode vi - LINES - @encoding = Reline.core.encoding - @line_editor = Reline::LineEditor.new(@config) - @line_editor.reset(@prompt) - end - - def editing_mode_label - @config.instance_variable_get(:@editing_mode_label) - end - - def teardown - Reline.test_reset - end - - def test_vi_command_mode - input_keys("\C-[") - assert_equal(:vi_command, editing_mode_label) - end - - def test_vi_command_mode_with_input - input_keys("abc\C-[") - assert_equal(:vi_command, editing_mode_label) - assert_line_around_cursor('ab', 'c') - end - - def test_vi_insert - assert_equal(:vi_insert, editing_mode_label) - input_keys('i') - assert_line_around_cursor('i', '') - assert_equal(:vi_insert, editing_mode_label) - input_keys("\C-[") - assert_line_around_cursor('', 'i') - assert_equal(:vi_command, editing_mode_label) - input_keys('i') - assert_line_around_cursor('', 'i') - assert_equal(:vi_insert, editing_mode_label) - end - - def test_vi_add - assert_equal(:vi_insert, editing_mode_label) - input_keys('a') - assert_line_around_cursor('a', '') - assert_equal(:vi_insert, editing_mode_label) - input_keys("\C-[") - assert_line_around_cursor('', 'a') - assert_equal(:vi_command, editing_mode_label) - input_keys('a') - assert_line_around_cursor('a', '') - assert_equal(:vi_insert, editing_mode_label) - end - - def test_vi_insert_at_bol - input_keys('I') - assert_line_around_cursor('I', '') - assert_equal(:vi_insert, editing_mode_label) - input_keys("12345\C-[hh") - assert_line_around_cursor('I12', '345') - assert_equal(:vi_command, editing_mode_label) - input_keys('I') - assert_line_around_cursor('', 'I12345') - assert_equal(:vi_insert, editing_mode_label) - end - - def test_vi_add_at_eol - input_keys('A') - assert_line_around_cursor('A', '') - assert_equal(:vi_insert, editing_mode_label) - input_keys("12345\C-[hh") - assert_line_around_cursor('A12', '345') - assert_equal(:vi_command, editing_mode_label) - input_keys('A') - assert_line_around_cursor('A12345', '') - assert_equal(:vi_insert, editing_mode_label) - end - - def test_ed_insert_one - input_keys('a') - assert_line_around_cursor('a', '') - end - - def test_ed_insert_two - input_keys('ab') - assert_line_around_cursor('ab', '') - end - - def test_ed_insert_mbchar_one - input_keys('か') - assert_line_around_cursor('か', '') - end - - def test_ed_insert_mbchar_two - input_keys('かき') - assert_line_around_cursor('かき', '') - end - - def test_ed_insert_for_mbchar_by_plural_code_points - omit_unless_utf8 - input_keys("か\u3099") - assert_line_around_cursor("か\u3099", '') - end - - def test_ed_insert_for_plural_mbchar_by_plural_code_points - omit_unless_utf8 - input_keys("か\u3099き\u3099") - assert_line_around_cursor("か\u3099き\u3099", '') - end - - def test_ed_insert_ignore_in_vi_command - input_keys("\C-[") - chars_to_be_ignored = "\C-Oあ=".chars - input_keys(chars_to_be_ignored.join) - assert_line_around_cursor('', '') - input_keys(chars_to_be_ignored.map {|c| "5#{c}" }.join) - assert_line_around_cursor('', '') - input_keys('iい') - assert_line_around_cursor("い", '') - end - - def test_ed_next_char - input_keys("abcdef\C-[0") - assert_line_around_cursor('', 'abcdef') - input_keys('l') - assert_line_around_cursor('a', 'bcdef') - input_keys('2l') - assert_line_around_cursor('abc', 'def') - end - - def test_ed_prev_char - input_keys("abcdef\C-[") - assert_line_around_cursor('abcde', 'f') - input_keys('h') - assert_line_around_cursor('abcd', 'ef') - input_keys('2h') - assert_line_around_cursor('ab', 'cdef') - end - - def test_history - Reline::HISTORY.concat(%w{abc 123 AAA}) - input_keys("\C-[") - assert_line_around_cursor('', '') - input_keys('k') - assert_line_around_cursor('', 'AAA') - input_keys('2k') - assert_line_around_cursor('', 'abc') - input_keys('j') - assert_line_around_cursor('', '123') - input_keys('2j') - assert_line_around_cursor('', '') - end - - def test_vi_paste_prev - input_keys("abcde\C-[3h") - assert_line_around_cursor('a', 'bcde') - input_keys('P') - assert_line_around_cursor('a', 'bcde') - input_keys('d$') - assert_line_around_cursor('', 'a') - input_keys('P') - assert_line_around_cursor('bcd', 'ea') - input_keys('2P') - assert_line_around_cursor('bcdbcdbcd', 'eeea') - end - - def test_vi_paste_next - input_keys("abcde\C-[3h") - assert_line_around_cursor('a', 'bcde') - input_keys('p') - assert_line_around_cursor('a', 'bcde') - input_keys('d$') - assert_line_around_cursor('', 'a') - input_keys('p') - assert_line_around_cursor('abcd', 'e') - input_keys('2p') - assert_line_around_cursor('abcdebcdebcd', 'e') - end - - def test_vi_paste_prev_for_mbchar - input_keys("あいうえお\C-[3h") - assert_line_around_cursor('あ', 'いうえお') - input_keys('P') - assert_line_around_cursor('あ', 'いうえお') - input_keys('d$') - assert_line_around_cursor('', 'あ') - input_keys('P') - assert_line_around_cursor('いうえ', 'おあ') - input_keys('2P') - assert_line_around_cursor('いうえいうえいうえ', 'おおおあ') - end - - def test_vi_paste_next_for_mbchar - input_keys("あいうえお\C-[3h") - assert_line_around_cursor('あ', 'いうえお') - input_keys('p') - assert_line_around_cursor('あ', 'いうえお') - input_keys('d$') - assert_line_around_cursor('', 'あ') - input_keys('p') - assert_line_around_cursor('あいうえ', 'お') - input_keys('2p') - assert_line_around_cursor('あいうえおいうえおいうえ', 'お') - end - - def test_vi_paste_prev_for_mbchar_by_plural_code_points - omit_unless_utf8 - input_keys("か\u3099き\u3099く\u3099け\u3099こ\u3099\C-[3h") - assert_line_around_cursor("か\u3099", "き\u3099く\u3099け\u3099こ\u3099") - input_keys('P') - assert_line_around_cursor("か\u3099", "き\u3099く\u3099け\u3099こ\u3099") - input_keys('d$') - assert_line_around_cursor('', "か\u3099") - input_keys('P') - assert_line_around_cursor("き\u3099く\u3099け\u3099", "こ\u3099か\u3099") - input_keys('2P') - 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 - omit_unless_utf8 - input_keys("か\u3099き\u3099く\u3099け\u3099こ\u3099\C-[3h") - assert_line_around_cursor("か\u3099", "き\u3099く\u3099け\u3099こ\u3099") - input_keys('p') - assert_line_around_cursor("か\u3099", "き\u3099く\u3099け\u3099こ\u3099") - input_keys('d$') - assert_line_around_cursor('', "か\u3099") - input_keys('p') - assert_line_around_cursor("か\u3099き\u3099く\u3099け\u3099", "こ\u3099") - input_keys('2p') - 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_line_around_cursor('', 'aaa b{b}b ccc') - input_keys('w') - assert_line_around_cursor('aaa ', 'b{b}b ccc') - input_keys('w') - assert_line_around_cursor('aaa b', '{b}b ccc') - input_keys('w') - assert_line_around_cursor('aaa b{', 'b}b ccc') - input_keys('w') - assert_line_around_cursor('aaa b{b', '}b ccc') - input_keys('w') - assert_line_around_cursor('aaa b{b}', 'b ccc') - input_keys('w') - assert_line_around_cursor('aaa b{b}b ', 'ccc') - input_keys('w') - assert_line_around_cursor('aaa b{b}b cc', 'c') - input_keys('b') - assert_line_around_cursor('aaa b{b}b ', 'ccc') - input_keys('b') - assert_line_around_cursor('aaa b{b}', 'b ccc') - input_keys('b') - assert_line_around_cursor('aaa b{b', '}b ccc') - input_keys('b') - assert_line_around_cursor('aaa b{', 'b}b ccc') - input_keys('b') - assert_line_around_cursor('aaa b', '{b}b ccc') - input_keys('b') - assert_line_around_cursor('aaa ', 'b{b}b ccc') - input_keys('b') - assert_line_around_cursor('', 'aaa b{b}b ccc') - input_keys('3w') - assert_line_around_cursor('aaa b{', 'b}b ccc') - input_keys('3w') - assert_line_around_cursor('aaa b{b}b ', 'ccc') - input_keys('3w') - assert_line_around_cursor('aaa b{b}b cc', 'c') - input_keys('3b') - assert_line_around_cursor('aaa b{b', '}b ccc') - input_keys('3b') - assert_line_around_cursor('aaa ', 'b{b}b ccc') - input_keys('3b') - 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_line_around_cursor('', 'aaa b{b}}}b ccc') - input_keys('e') - assert_line_around_cursor('aa', 'a b{b}}}b ccc') - input_keys('e') - assert_line_around_cursor('aaa ', 'b{b}}}b ccc') - input_keys('e') - assert_line_around_cursor('aaa b', '{b}}}b ccc') - input_keys('e') - assert_line_around_cursor('aaa b{', 'b}}}b ccc') - input_keys('e') - assert_line_around_cursor('aaa b{b}}', '}b ccc') - input_keys('e') - assert_line_around_cursor('aaa b{b}}}', 'b ccc') - input_keys('e') - assert_line_around_cursor('aaa b{b}}}b cc', 'c') - input_keys('e') - assert_line_around_cursor('aaa b{b}}}b cc', 'c') - input_keys('03e') - assert_line_around_cursor('aaa b', '{b}}}b ccc') - input_keys('3e') - assert_line_around_cursor('aaa b{b}}}', 'b ccc') - input_keys('3e') - 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_line_around_cursor('', 'aaa b{b}b ccc') - input_keys('W') - assert_line_around_cursor('aaa ', 'b{b}b ccc') - input_keys('W') - assert_line_around_cursor('aaa b{b}b ', 'ccc') - input_keys('W') - assert_line_around_cursor('aaa b{b}b cc', 'c') - input_keys('B') - assert_line_around_cursor('aaa b{b}b ', 'ccc') - input_keys('B') - assert_line_around_cursor('aaa ', 'b{b}b ccc') - input_keys('B') - assert_line_around_cursor('', 'aaa b{b}b ccc') - input_keys('2W') - assert_line_around_cursor('aaa b{b}b ', 'ccc') - input_keys('2W') - assert_line_around_cursor('aaa b{b}b cc', 'c') - input_keys('2B') - assert_line_around_cursor('aaa ', 'b{b}b ccc') - input_keys('2B') - 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_line_around_cursor('', 'aaa b{b}}}b ccc') - input_keys('E') - assert_line_around_cursor('aa', 'a b{b}}}b ccc') - input_keys('E') - assert_line_around_cursor('aaa b{b}}}', 'b ccc') - input_keys('E') - assert_line_around_cursor('aaa b{b}}}b cc', 'c') - input_keys('E') - assert_line_around_cursor('aaa b{b}}}b cc', 'c') - end - - def test_ed_quoted_insert - input_keys('ab') - input_key_by_symbol(:insert_raw_char, char: "\C-a") - assert_line_around_cursor("ab\C-a", '') - end - - def test_ed_quoted_insert_with_vi_arg - input_keys("ab\C-[3") - input_key_by_symbol(:insert_raw_char, char: "\C-a") - input_keys('4') - input_key_by_symbol(:insert_raw_char, char: '1') - assert_line_around_cursor("a\C-a\C-a\C-a1111", 'b') - end - - def test_vi_replace_char - input_keys("abcdef\C-[03l") - assert_line_around_cursor('abc', 'def') - input_keys('rz') - assert_line_around_cursor('abc', 'zef') - input_keys('2rx') - assert_line_around_cursor('abcxx', 'f') - end - - def test_vi_replace_char_with_mbchar - input_keys("あいうえお\C-[0l") - assert_line_around_cursor('あ', 'いうえお') - input_keys('rx') - assert_line_around_cursor('あ', 'xうえお') - input_keys('l2ry') - assert_line_around_cursor('あxyy', 'お') - end - - def test_vi_next_char - input_keys("abcdef\C-[0") - assert_line_around_cursor('', 'abcdef') - input_keys('fz') - assert_line_around_cursor('', 'abcdef') - input_keys('fe') - assert_line_around_cursor('abcd', 'ef') - end - - def test_vi_to_next_char - input_keys("abcdef\C-[0") - assert_line_around_cursor('', 'abcdef') - input_keys('tz') - assert_line_around_cursor('', 'abcdef') - input_keys('te') - assert_line_around_cursor('abc', 'def') - end - - def test_vi_prev_char - input_keys("abcdef\C-[") - assert_line_around_cursor('abcde', 'f') - input_keys('Fz') - assert_line_around_cursor('abcde', 'f') - input_keys('Fa') - assert_line_around_cursor('', 'abcdef') - end - - def test_vi_to_prev_char - input_keys("abcdef\C-[") - assert_line_around_cursor('abcde', 'f') - input_keys('Tz') - assert_line_around_cursor('abcde', 'f') - input_keys('Ta') - assert_line_around_cursor('a', 'bcdef') - end - - def test_vi_delete_next_char - input_keys("abc\C-[h") - assert_line_around_cursor('a', 'bc') - input_keys('x') - assert_line_around_cursor('a', 'c') - input_keys('x') - assert_line_around_cursor('', 'a') - input_keys('x') - assert_line_around_cursor('', '') - input_keys('x') - assert_line_around_cursor('', '') - end - - def test_vi_delete_next_char_for_mbchar - input_keys("あいう\C-[h") - assert_line_around_cursor('あ', 'いう') - input_keys('x') - assert_line_around_cursor('あ', 'う') - input_keys('x') - assert_line_around_cursor('', 'あ') - input_keys('x') - assert_line_around_cursor('', '') - input_keys('x') - assert_line_around_cursor('', '') - end - - def test_vi_delete_next_char_for_mbchar_by_plural_code_points - omit_unless_utf8 - input_keys("か\u3099き\u3099く\u3099\C-[h") - assert_line_around_cursor("か\u3099", "き\u3099く\u3099") - input_keys('x') - assert_line_around_cursor("か\u3099", "く\u3099") - input_keys('x') - assert_line_around_cursor('', "か\u3099") - input_keys('x') - assert_line_around_cursor('', '') - input_keys('x') - assert_line_around_cursor('', '') - end - - def test_vi_delete_prev_char - input_keys('ab') - assert_line_around_cursor('ab', '') - input_keys("\C-h") - assert_line_around_cursor('a', '') - end - - def test_vi_delete_prev_char_for_mbchar - input_keys('かき') - assert_line_around_cursor('かき', '') - input_keys("\C-h") - assert_line_around_cursor('か', '') - end - - def test_vi_delete_prev_char_for_mbchar_by_plural_code_points - omit_unless_utf8 - input_keys("か\u3099き\u3099") - assert_line_around_cursor("か\u3099き\u3099", '') - input_keys("\C-h") - assert_line_around_cursor("か\u3099", '') - end - - def test_ed_delete_prev_char - input_keys("abcdefg\C-[h") - assert_line_around_cursor('abcde', 'fg') - input_keys('X') - assert_line_around_cursor('abcd', 'fg') - input_keys('3X') - assert_line_around_cursor('a', 'fg') - input_keys('p') - assert_line_around_cursor('afbc', 'dg') - end - - def test_ed_delete_prev_word - input_keys('abc def{bbb}ccc') - assert_line_around_cursor('abc def{bbb}ccc', '') - input_keys("\C-w") - assert_line_around_cursor('abc def{bbb}', '') - input_keys("\C-w") - assert_line_around_cursor('abc def{', '') - input_keys("\C-w") - assert_line_around_cursor('abc ', '') - input_keys("\C-w") - assert_line_around_cursor('', '') - end - - def test_ed_delete_prev_word_for_mbchar - input_keys('あいう かきく{さしす}たちつ') - assert_line_around_cursor('あいう かきく{さしす}たちつ', '') - input_keys("\C-w") - assert_line_around_cursor('あいう かきく{さしす}', '') - input_keys("\C-w") - assert_line_around_cursor('あいう かきく{', '') - input_keys("\C-w") - assert_line_around_cursor('あいう ', '') - input_keys("\C-w") - assert_line_around_cursor('', '') - end - - def test_ed_delete_prev_word_for_mbchar_by_plural_code_points - omit_unless_utf8 - input_keys("あいう か\u3099き\u3099く\u3099{さしす}たちつ") - assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}たちつ", '') - input_keys("\C-w") - assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}", '') - input_keys("\C-w") - assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{", '') - input_keys("\C-w") - assert_line_around_cursor('あいう ', '') - input_keys("\C-w") - assert_line_around_cursor('', '') - end - - def test_ed_newline_with_cr - input_keys('ab') - assert_line_around_cursor('ab', '') - refute(@line_editor.finished?) - input_keys("\C-m") - assert_line_around_cursor('ab', '') - assert(@line_editor.finished?) - end - - def test_ed_newline_with_lf - input_keys('ab') - assert_line_around_cursor('ab', '') - refute(@line_editor.finished?) - input_keys("\C-j") - assert_line_around_cursor('ab', '') - assert(@line_editor.finished?) - end - - def test_vi_list_or_eof - input_keys("\C-d") # quit from inputing - assert_nil(@line_editor.line) - assert(@line_editor.finished?) - end - - def test_vi_list_or_eof_with_non_empty_line - input_keys('ab') - assert_line_around_cursor('ab', '') - refute(@line_editor.finished?) - input_keys("\C-d") - assert_line_around_cursor('ab', '') - assert(@line_editor.finished?) - end - - def test_completion_journey - @line_editor.completion_proc = proc { |word| - %w{ - foo_bar - foo_bar_baz - }.map { |i| - i.encode(@encoding) - } - } - input_keys('foo') - assert_line_around_cursor('foo', '') - input_keys("\C-n") - assert_line_around_cursor('foo_bar', '') - input_keys("\C-n") - assert_line_around_cursor('foo_bar_baz', '') - input_keys("\C-n") - assert_line_around_cursor('foo', '') - input_keys("\C-n") - assert_line_around_cursor('foo_bar', '') - input_keys("_\C-n") - assert_line_around_cursor('foo_bar_baz', '') - input_keys("\C-n") - assert_line_around_cursor('foo_bar_', '') - end - - def test_completion_journey_reverse - @line_editor.completion_proc = proc { |word| - %w{ - foo_bar - foo_bar_baz - }.map { |i| - i.encode(@encoding) - } - } - input_keys('foo') - assert_line_around_cursor('foo', '') - input_keys("\C-p") - assert_line_around_cursor('foo_bar_baz', '') - input_keys("\C-p") - assert_line_around_cursor('foo_bar', '') - input_keys("\C-p") - assert_line_around_cursor('foo', '') - input_keys("\C-p") - assert_line_around_cursor('foo_bar_baz', '') - input_keys("\C-h\C-p") - assert_line_around_cursor('foo_bar_baz', '') - input_keys("\C-p") - assert_line_around_cursor('foo_bar_ba', '') - end - - def test_completion_journey_in_middle_of_line - @line_editor.completion_proc = proc { |word| - %w{ - foo_bar - foo_bar_baz - }.map { |i| - i.encode(@encoding) - } - } - input_keys('abcde fo ABCDE') - assert_line_around_cursor('abcde fo ABCDE', '') - input_keys("\C-[" + 'h' * 5 + "i\C-n") - assert_line_around_cursor('abcde foo_bar', ' ABCDE') - input_keys("\C-n") - assert_line_around_cursor('abcde foo_bar_baz', ' ABCDE') - input_keys("\C-n") - assert_line_around_cursor('abcde fo', ' ABCDE') - input_keys("\C-n") - assert_line_around_cursor('abcde foo_bar', ' ABCDE') - input_keys("_\C-n") - assert_line_around_cursor('abcde foo_bar_baz', ' ABCDE') - input_keys("\C-n") - assert_line_around_cursor('abcde foo_bar_', ' ABCDE') - input_keys("\C-n") - assert_line_around_cursor('abcde foo_bar_baz', ' ABCDE') - end - - def test_completion - @line_editor.completion_proc = proc { |word| - %w{ - foo_bar - foo_bar_baz - }.map { |i| - i.encode(@encoding) - } - } - input_keys('foo') - assert_line_around_cursor('foo', '') - input_keys("\C-i") - 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") - assert_line_around_cursor('Readline', '') - input_keys("\C-i") - assert_line_around_cursor('Regexp', '') - input_key_by_symbol(:completion_journey_up) - 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") - assert_line_around_cursor('Readline', '') - input_keys("\C-i") - assert_line_around_cursor('Regexp', '') - input_key_by_symbol(:menu_complete_backward) - assert_line_around_cursor('Readline', '') - ensure - @config.autocompletion = false - end - - def test_completion_with_disable_completion - @config.disable_completion = true - @line_editor.completion_proc = proc { |word| - %w{ - foo_bar - foo_bar_baz - }.map { |i| - i.encode(@encoding) - } - } - input_keys('foo') - assert_line_around_cursor('foo', '') - input_keys("\C-i") - assert_line_around_cursor('foo', '') - end - - def test_vi_first_print - input_keys("abcde\C-[^") - assert_line_around_cursor('', 'abcde') - input_keys("0\C-ki") - input_keys(" abcde\C-[^") - assert_line_around_cursor(' ', 'abcde') - input_keys("0\C-ki") - input_keys(" abcde ABCDE \C-[^") - assert_line_around_cursor(' ', 'abcde ABCDE ') - end - - def test_ed_move_to_beg - input_keys("abcde\C-[0") - assert_line_around_cursor('', 'abcde') - input_keys("0\C-ki") - input_keys(" abcde\C-[0") - assert_line_around_cursor('', ' abcde') - input_keys("0\C-ki") - input_keys(" abcde ABCDE \C-[0") - 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_line_around_cursor('aaa bbb ', 'ccc ddd eee') - input_keys('dw') - assert_line_around_cursor('aaa bbb ', 'ddd eee') - input_keys('db') - assert_line_around_cursor('aaa ', 'ddd eee') - end - - def test_vi_delete_meta_nothing - input_keys("foo\C-[0") - assert_line_around_cursor('', 'foo') - input_keys('dhp') - assert_line_around_cursor('', 'foo') - end - - def test_vi_delete_meta_with_vi_next_word_at_eol - input_keys("foo bar\C-[0w") - assert_line_around_cursor('foo ', 'bar') - input_keys('w') - assert_line_around_cursor('foo ba', 'r') - input_keys('0dw') - assert_line_around_cursor('', 'bar') - input_keys('dw') - assert_line_around_cursor('', '') - end - - def test_vi_delete_meta_with_vi_next_char - input_keys("aaa bbb ccc ___ ddd\C-[02w") - assert_line_around_cursor('aaa bbb ', 'ccc ___ ddd') - input_keys('df_') - assert_line_around_cursor('aaa bbb ', '__ ddd') - end - - def test_vi_delete_meta_with_arg - input_keys("aaa bbb ccc ddd\C-[03w") - assert_line_around_cursor('aaa bbb ccc ', 'ddd') - input_keys('2dl') - 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_line_around_cursor('aaa bbb ', 'ccc ddd eee') - input_keys('cwaiueo') - assert_line_around_cursor('aaa bbb aiueo', ' ddd eee') - input_keys("\C-[") - assert_line_around_cursor('aaa bbb aiue', 'o ddd eee') - input_keys('cb') - 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_line_around_cursor('foo ', 'bar baz') - input_keys('cwhoge') - assert_line_around_cursor('foo hoge', ' baz') - input_keys("\C-[") - 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_waiting_operator_arg_including_zero - input_keys("a111111111111222222222222\C-[0") - input_keys('10df1') - assert_line_around_cursor('', '11222222222222') - input_keys('d10f2') - assert_line_around_cursor('', '22') - 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, csi: true) - input_keys('l') - assert_line_around_cursor('aa', 'a bbb lll') - # vi_delete_meta + ed_next_char should delete character - input_keys('d') - input_key_by_symbol(:ed_next_char, csi: true) - input_keys('l') - assert_line_around_cursor('aa ', 'bbb lll') - end - - def test_unimplemented_vi_command_should_be_no_op - input_keys("abc\C-[h") - assert_line_around_cursor('a', 'bc') - input_keys('@') - assert_line_around_cursor('a', 'bc') - end - - def test_vi_yank - input_keys("foo bar\C-[2h") - assert_line_around_cursor('foo ', 'bar') - input_keys('y3l') - assert_line_around_cursor('foo ', 'bar') - input_keys('P') - 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_yank_nothing - input_keys("foo\C-[0") - assert_line_around_cursor('', 'foo') - input_keys('yhp') - assert_line_around_cursor('', 'foo') - end - - def test_vi_end_word_with_operator - input_keys("foo bar\C-[0") - assert_line_around_cursor('', 'foo bar') - input_keys('de') - assert_line_around_cursor('', ' bar') - input_keys('de') - assert_line_around_cursor('', '') - input_keys('de') - assert_line_around_cursor('', '') - end - - def test_vi_end_big_word_with_operator - input_keys("aaa b{b}}}b\C-[0") - assert_line_around_cursor('', 'aaa b{b}}}b') - input_keys('dE') - assert_line_around_cursor('', ' b{b}}}b') - input_keys('dE') - assert_line_around_cursor('', '') - input_keys('dE') - assert_line_around_cursor('', '') - end - - def test_vi_next_char_with_operator - input_keys("foo bar\C-[0") - assert_line_around_cursor('', 'foo bar') - input_keys('df ') - assert_line_around_cursor('', 'bar') - end - - def test_ed_delete_next_char_at_eol - input_keys('"あ"') - assert_line_around_cursor('"あ"', '') - input_keys("\C-[") - assert_line_around_cursor('"あ', '"') - input_keys('xa"') - assert_line_around_cursor('"あ"', '') - end - - def test_vi_kill_line_prev - input_keys("\C-u") - assert_line_around_cursor('', '') - input_keys('abc') - assert_line_around_cursor('abc', '') - input_keys("\C-u") - assert_line_around_cursor('', '') - input_keys('abc') - input_keys("\C-[\C-u") - assert_line_around_cursor('', 'c') - input_keys("\C-u") - 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_equal(:vi_insert, editing_mode_label) - end - - def test_vi_motion_operators - assert_equal(:vi_insert, editing_mode_label) - - assert_nothing_raised do - 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_key_stroke.rb b/test/reline/test_key_stroke.rb deleted file mode 100644 index fb2cb1c8b8..0000000000 --- a/test/reline/test_key_stroke.rb +++ /dev/null @@ -1,111 +0,0 @@ -require_relative 'helper' - -class Reline::KeyStroke::Test < Reline::TestCase - def encoding - Reline.core.encoding - end - - def test_match_status - config = Reline::Config.new - { - 'a' => 'xx', - 'ab' => 'y', - 'abc' => 'z', - 'x' => 'rr' - }.each_pair do |key, func| - config.add_default_key_binding(key.bytes, func.bytes) - end - stroke = Reline::KeyStroke.new(config, encoding) - assert_equal(Reline::KeyStroke::MATCHING_MATCHED, stroke.match_status("a".bytes)) - assert_equal(Reline::KeyStroke::MATCHING_MATCHED, stroke.match_status("ab".bytes)) - assert_equal(Reline::KeyStroke::MATCHED, stroke.match_status("abc".bytes)) - assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status("abz".bytes)) - assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status("abcx".bytes)) - assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status("aa".bytes)) - assert_equal(Reline::KeyStroke::MATCHED, stroke.match_status("x".bytes)) - assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status("xa".bytes)) - end - - def test_match_unknown - config = Reline::Config.new - config.add_default_key_binding("\e[9abc".bytes, 'x') - stroke = Reline::KeyStroke.new(config, encoding) - sequences = [ - "\e[9abc", - "\e[9d", - "\e[A", # Up - "\e[1;1R", # Cursor position report - "\e[15~", # F5 - "\eOP", # F1 - "\e\e[A", # Option+Up - "\eX", - "\e\eX" - ] - sequences.each do |seq| - assert_equal(Reline::KeyStroke::MATCHED, stroke.match_status(seq.bytes)) - assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status(seq.bytes + [32])) - (2...seq.size).each do |i| - assert_equal(Reline::KeyStroke::MATCHING, stroke.match_status(seq.bytes.take(i))) - end - end - end - - def test_expand - config = Reline::Config.new - { - 'abc' => 'AB', - 'ab' => "1\C-a" - }.each_pair do |key, func| - config.add_default_key_binding(key.bytes, func.bytes) - end - stroke = Reline::KeyStroke.new(config, encoding) - assert_equal([[Reline::Key.new('A', :ed_insert, false), Reline::Key.new('B', :ed_insert, false)], 'de'.bytes], stroke.expand('abcde'.bytes)) - assert_equal([[Reline::Key.new('1', :ed_digit, false), Reline::Key.new("\C-a", :ed_move_to_beg, false)], 'de'.bytes], stroke.expand('abde'.bytes)) - # CSI sequence - assert_equal([[], 'bc'.bytes], stroke.expand("\e[1;2;3;4;5abc".bytes)) - assert_equal([[], 'BC'.bytes], stroke.expand("\e\e[ABC".bytes)) - # SS3 sequence - assert_equal([[], 'QR'.bytes], stroke.expand("\eOPQR".bytes)) - end - - def test_oneshot_key_bindings - config = Reline::Config.new - { - 'abc'.bytes => '123', - # IRB version <= 1.13.1 wrongly uses Reline::Key with wrong argument. It should be ignored without error. - [Reline::Key.new(nil, 0xE4, true)] => '012', - "\eda".bytes => 'abc', # Alt+d a - [195, 164] => 'def' - }.each_pair do |key, func| - config.add_oneshot_key_binding(key, func.bytes) - end - stroke = Reline::KeyStroke.new(config, encoding) - assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status('zzz'.bytes)) - assert_equal(Reline::KeyStroke::MATCHED, stroke.match_status('abc'.bytes)) - assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status('da'.bytes)) - assert_equal(Reline::KeyStroke::MATCHED, stroke.match_status("\eda".bytes)) - assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status(" \eda".bytes)) - assert_equal(Reline::KeyStroke::MATCHED, stroke.match_status([195, 164])) - end - - def test_multibyte_matching - begin - char = 'あ'.encode(encoding) - rescue Encoding::UndefinedConversionError - omit - end - config = Reline::Config.new - stroke = Reline::KeyStroke.new(config, encoding) - key = Reline::Key.new(char, :ed_insert, false) - bytes = char.bytes - assert_equal(Reline::KeyStroke::MATCHED, stroke.match_status(bytes)) - assert_equal([[key], []], stroke.expand(bytes)) - assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status(bytes * 2)) - assert_equal([[key], bytes], stroke.expand(bytes * 2)) - (1...bytes.size).each do |i| - partial_bytes = bytes.take(i) - assert_equal(Reline::KeyStroke::MATCHING_MATCHED, stroke.match_status(partial_bytes)) - assert_equal([[], []], stroke.expand(partial_bytes)) - end - end -end diff --git a/test/reline/test_kill_ring.rb b/test/reline/test_kill_ring.rb deleted file mode 100644 index 9f6e0c3e74..0000000000 --- a/test/reline/test_kill_ring.rb +++ /dev/null @@ -1,268 +0,0 @@ -require_relative 'helper' - -class Reline::KillRing::Test < Reline::TestCase - def setup - @prompt = '> ' - @kill_ring = Reline::KillRing.new - end - - def test_append_one - assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state)) - @kill_ring.append('a') - assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.process - assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state)) - assert_equal('a', @kill_ring.yank) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - assert_equal('a', @kill_ring.yank) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - assert_equal(['a', 'a'], @kill_ring.yank_pop) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - assert_equal(['a', 'a'], @kill_ring.yank_pop) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - end - - def test_append_two - assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state)) - @kill_ring.append('a') - assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.process - assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.process - assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state)) - @kill_ring.append('b') - assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.process - assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state)) - assert_equal('b', @kill_ring.yank) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - assert_equal('b', @kill_ring.yank) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - assert_equal(['a', 'b'], @kill_ring.yank_pop) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - assert_equal(['b', 'a'], @kill_ring.yank_pop) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - end - - def test_append_three - assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state)) - @kill_ring.append('a') - assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.process - assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.process - assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state)) - @kill_ring.append('b') - assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.process - assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.process - assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state)) - @kill_ring.append('c') - assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.process - assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state)) - assert_equal('c', @kill_ring.yank) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - assert_equal('c', @kill_ring.yank) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - assert_equal(['b', 'c'], @kill_ring.yank_pop) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - assert_equal(['a', 'b'], @kill_ring.yank_pop) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - assert_equal(['c', 'a'], @kill_ring.yank_pop) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - end - - def test_append_three_with_max_two - @kill_ring = Reline::KillRing.new(2) - assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state)) - @kill_ring.append('a') - assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.process - assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.process - assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state)) - @kill_ring.append('b') - assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.process - assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.process - assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state)) - @kill_ring.append('c') - assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.process - assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state)) - assert_equal('c', @kill_ring.yank) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - assert_equal('c', @kill_ring.yank) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - assert_equal(['b', 'c'], @kill_ring.yank_pop) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - assert_equal(['c', 'b'], @kill_ring.yank_pop) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - assert_equal(['b', 'c'], @kill_ring.yank_pop) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - end - - def test_append_four_with_max_two - @kill_ring = Reline::KillRing.new(2) - assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state)) - @kill_ring.append('a') - assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.process - assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.process - assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state)) - @kill_ring.append('b') - assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.process - assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.process - assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state)) - @kill_ring.append('c') - assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.process - assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.process - assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state)) - @kill_ring.append('d') - assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.process - assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state)) - assert_equal('d', @kill_ring.yank) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - assert_equal('d', @kill_ring.yank) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - assert_equal(['c', 'd'], @kill_ring.yank_pop) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - assert_equal(['d', 'c'], @kill_ring.yank_pop) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - assert_equal(['c', 'd'], @kill_ring.yank_pop) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - end - - def test_append_after - assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state)) - @kill_ring.append('a') - assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.process - assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.append('b') - assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.process - assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state)) - assert_equal('ab', @kill_ring.yank) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - assert_equal('ab', @kill_ring.yank) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - assert_equal(['ab', 'ab'], @kill_ring.yank_pop) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - assert_equal(['ab', 'ab'], @kill_ring.yank_pop) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - end - - def test_append_before - assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state)) - @kill_ring.append('a') - assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.process - assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.append('b', true) - assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.process - assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state)) - assert_equal('ba', @kill_ring.yank) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - assert_equal('ba', @kill_ring.yank) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - assert_equal(['ba', 'ba'], @kill_ring.yank_pop) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - assert_equal(['ba', 'ba'], @kill_ring.yank_pop) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - end - - def test_append_chain_two - assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state)) - @kill_ring.append('a') - assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.process - assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.append('b') - assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.process - assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.process - assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state)) - @kill_ring.append('c') - assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.process - assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.append('d') - assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.process - assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state)) - assert_equal('cd', @kill_ring.yank) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - assert_equal('cd', @kill_ring.yank) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - assert_equal(['ab', 'cd'], @kill_ring.yank_pop) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - assert_equal(['cd', 'ab'], @kill_ring.yank_pop) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - end - - def test_append_complex_chain - assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state)) - @kill_ring.append('c') - assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.process - assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.append('d') - assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.process - assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.append('b', true) - assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.process - assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.append('e') - assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.process - assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.append('a', true) - assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.process - assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.process - assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state)) - @kill_ring.append('A') - assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.process - assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.append('B') - assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state)) - @kill_ring.process - assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state)) - assert_equal('AB', @kill_ring.yank) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - assert_equal('AB', @kill_ring.yank) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - assert_equal(['abcde', 'AB'], @kill_ring.yank_pop) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - assert_equal(['AB', 'abcde'], @kill_ring.yank_pop) - assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state)) - end - - def test_enumerable - @kill_ring.append('a') - @kill_ring.process - @kill_ring.process - @kill_ring.append('b') - @kill_ring.process - @kill_ring.process - @kill_ring.append('c') - @kill_ring.process - assert_equal(%w{c b a}, @kill_ring.to_a) - end -end diff --git a/test/reline/test_line_editor.rb b/test/reline/test_line_editor.rb deleted file mode 100644 index 28fcbfa6df..0000000000 --- a/test/reline/test_line_editor.rb +++ /dev/null @@ -1,271 +0,0 @@ -require_relative 'helper' -require 'reline/line_editor' -require 'stringio' - -class Reline::LineEditor - - class CompletionBlockTest < Reline::TestCase - def setup - @original_quote_characters = Reline.completer_quote_characters - @original_word_break_characters = Reline.completer_word_break_characters - @line_editor = Reline::LineEditor.new(nil) - end - - def retrieve_completion_block(lines, line_index, byte_pointer) - @line_editor.instance_variable_set(:@buffer_of_lines, lines) - @line_editor.instance_variable_set(:@line_index, line_index) - @line_editor.instance_variable_set(:@byte_pointer, byte_pointer) - @line_editor.retrieve_completion_block - end - - def retrieve_completion_quote(line) - _, _, _, quote = retrieve_completion_block([line], 0, line.bytesize) - quote - end - - def teardown - Reline.completer_quote_characters = @original_quote_characters - Reline.completer_word_break_characters = @original_word_break_characters - end - - def test_retrieve_completion_block - Reline.completer_word_break_characters = ' ([{' - Reline.completer_quote_characters = '' - assert_equal(['', '', 'foo', nil], retrieve_completion_block(['foo'], 0, 0)) - assert_equal(['', 'f', 'oo', nil], retrieve_completion_block(['foo'], 0, 1)) - assert_equal(['foo ', 'ba', 'r baz', nil], retrieve_completion_block(['foo bar baz'], 0, 6)) - assert_equal(['foo([', 'b', 'ar])baz', nil], retrieve_completion_block(['foo([bar])baz'], 0, 6)) - assert_equal(['foo([{', '', '}])baz', nil], retrieve_completion_block(['foo([{}])baz'], 0, 6)) - assert_equal(["abc\nfoo ", 'ba', "r baz\ndef", nil], retrieve_completion_block(['abc', 'foo bar baz', 'def'], 1, 6)) - end - - def test_retrieve_completion_block_with_quote_characters - Reline.completer_word_break_characters = ' ([{' - Reline.completer_quote_characters = '' - assert_equal(['"" ', '"wo', 'rd', nil], retrieve_completion_block(['"" "word'], 0, 6)) - Reline.completer_quote_characters = '"' - assert_equal(['"" "', 'wo', 'rd', nil], retrieve_completion_block(['"" "word'], 0, 6)) - end - - def test_retrieve_completion_quote - Reline.completer_quote_characters = '"\'' - assert_equal('"', retrieve_completion_quote('"\'')) - assert_equal(nil, retrieve_completion_quote('""')) - assert_equal("'", retrieve_completion_quote('""\'"')) - assert_equal(nil, retrieve_completion_quote('""\'\'')) - assert_equal('"', retrieve_completion_quote('"\\"')) - assert_equal(nil, retrieve_completion_quote('"\\""')) - assert_equal(nil, retrieve_completion_quote('"\\\\"')) - end - end - - class CursorPositionTest < Reline::TestCase - def setup - @line_editor = Reline::LineEditor.new(nil) - @line_editor.instance_variable_set(:@config, Reline::Config.new) - end - - def test_cursor_position_with_escaped_input - @line_editor.instance_variable_set(:@screen_size, [4, 16]) - @line_editor.instance_variable_set(:@prompt, "\e[1mprompt\e[0m> ") - @line_editor.instance_variable_set(:@buffer_of_lines, ["\e[1m\0\1\2\3\4\5\6\7abcd"]) - @line_editor.instance_variable_set(:@line_index, 0) - # prompt> ^[[1m^@^ - # A^B^C^D^E^F^Gabc - # d - @line_editor.instance_variable_set(:@byte_pointer, 0) - assert_equal [8, 0], @line_editor.wrapped_cursor_position - @line_editor.instance_variable_set(:@byte_pointer, 5) - assert_equal [15, 0], @line_editor.wrapped_cursor_position - @line_editor.instance_variable_set(:@byte_pointer, 6) - assert_equal [1, 1], @line_editor.wrapped_cursor_position - @line_editor.instance_variable_set(:@byte_pointer, 14) - assert_equal [15, 1], @line_editor.wrapped_cursor_position - @line_editor.instance_variable_set(:@byte_pointer, 15) - assert_equal [0, 2], @line_editor.wrapped_cursor_position - @line_editor.instance_variable_set(:@byte_pointer, 16) - assert_equal [1, 2], @line_editor.wrapped_cursor_position - end - end - - class RenderLineDifferentialTest < Reline::TestCase - class TestIO < Reline::IO - def write(string) - @output << string - end - - def move_cursor_column(col) - @output << "[COL_#{col}]" - end - - def erase_after_cursor - @output << '[ERASE]' - end - end - - def setup - verbose, $VERBOSE = $VERBOSE, nil - @line_editor = Reline::LineEditor.new(nil) - @original_iogate = Reline::IOGate - @output = StringIO.new - @line_editor.instance_variable_set(:@screen_size, [24, 80]) - Reline.send(:remove_const, :IOGate) - Reline.const_set(:IOGate, TestIO.new) - 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 deleted file mode 100644 index cacdb76c60..0000000000 --- a/test/reline/test_macro.rb +++ /dev/null @@ -1,40 +0,0 @@ -require_relative 'helper' - -class Reline::MacroTest < Reline::TestCase - def setup - Reline.send(:test_mode) - @config = Reline::Config.new - @encoding = Reline.core.encoding - @line_editor = Reline::LineEditor.new(@config) - @output = Reline::IOGate.output = File.open(IO::NULL, "w") - end - - def teardown - @output.close - Reline.test_reset - end - - def input_key(char, method_symbol = :ed_insert) - @line_editor.input_key(Reline::Key.new(char, method_symbol, false)) - end - - def input(str) - str.each_char {|c| input_key(c)} - end - - def test_simple_input - input('abc') - assert_equal 'abc', @line_editor.line - end - - def test_alias - class << @line_editor - alias delete_char ed_delete_prev_char - end - input('abc') - assert_nothing_raised(ArgumentError) { - input_key('x', :delete_char) - } - assert_equal 'ab', @line_editor.line - end -end diff --git a/test/reline/test_reline.rb b/test/reline/test_reline.rb deleted file mode 100644 index 691ed9ffda..0000000000 --- a/test/reline/test_reline.rb +++ /dev/null @@ -1,487 +0,0 @@ -require_relative 'helper' -require 'reline' -require 'stringio' -begin - require "pty" -rescue LoadError # some platforms don't support PTY -end - -class Reline::Test < Reline::TestCase - class DummyCallbackObject - def call; end - end - - def setup - Reline.send(:test_mode) - Reline.output_modifier_proc = nil - Reline.completion_proc = nil - Reline.prompt_proc = nil - Reline.auto_indent_proc = nil - Reline.pre_input_hook = nil - Reline.dig_perfect_match_proc = nil - end - - def teardown - Reline.test_reset - end - - def test_completion_append_character - completion_append_character = Reline.completion_append_character - - assert_equal(nil, Reline.completion_append_character) - - Reline.completion_append_character = "" - assert_equal(nil, Reline.completion_append_character) - - Reline.completion_append_character = "a".encode(Encoding::ASCII) - assert_equal("a", Reline.completion_append_character) - assert_equal(get_reline_encoding, Reline.completion_append_character.encoding) - - Reline.completion_append_character = "ba".encode(Encoding::ASCII) - assert_equal("b", Reline.completion_append_character) - assert_equal(get_reline_encoding, Reline.completion_append_character.encoding) - - Reline.completion_append_character = "cba".encode(Encoding::ASCII) - assert_equal("c", Reline.completion_append_character) - assert_equal(get_reline_encoding, Reline.completion_append_character.encoding) - - Reline.completion_append_character = nil - assert_equal(nil, Reline.completion_append_character) - ensure - Reline.completion_append_character = completion_append_character - end - - def test_basic_word_break_characters - basic_word_break_characters = Reline.basic_word_break_characters - - assert_equal(" \t\n`><=;|&{(", Reline.basic_word_break_characters) - - Reline.basic_word_break_characters = "[".encode(Encoding::ASCII) - assert_equal("[", Reline.basic_word_break_characters) - assert_equal(get_reline_encoding, Reline.basic_word_break_characters.encoding) - ensure - Reline.basic_word_break_characters = basic_word_break_characters - end - - def test_completer_word_break_characters - completer_word_break_characters = Reline.completer_word_break_characters - - assert_equal(" \t\n`><=;|&{(", Reline.completer_word_break_characters) - - Reline.completer_word_break_characters = "[".encode(Encoding::ASCII) - assert_equal("[", Reline.completer_word_break_characters) - assert_equal(get_reline_encoding, Reline.completer_word_break_characters.encoding) - - assert_nothing_raised { Reline.completer_word_break_characters = '' } - ensure - Reline.completer_word_break_characters = completer_word_break_characters - end - - def test_basic_quote_characters - basic_quote_characters = Reline.basic_quote_characters - - assert_equal('"\'', Reline.basic_quote_characters) - - Reline.basic_quote_characters = "`".encode(Encoding::ASCII) - assert_equal("`", Reline.basic_quote_characters) - assert_equal(get_reline_encoding, Reline.basic_quote_characters.encoding) - ensure - Reline.basic_quote_characters = basic_quote_characters - end - - def test_completer_quote_characters - completer_quote_characters = Reline.completer_quote_characters - - assert_equal('"\'', Reline.completer_quote_characters) - - Reline.completer_quote_characters = "`".encode(Encoding::ASCII) - assert_equal("`", Reline.completer_quote_characters) - assert_equal(get_reline_encoding, Reline.completer_quote_characters.encoding) - - assert_nothing_raised { Reline.completer_quote_characters = '' } - ensure - Reline.completer_quote_characters = completer_quote_characters - end - - def test_filename_quote_characters - filename_quote_characters = Reline.filename_quote_characters - - assert_equal('', Reline.filename_quote_characters) - - Reline.filename_quote_characters = "\'".encode(Encoding::ASCII) - assert_equal("\'", Reline.filename_quote_characters) - assert_equal(get_reline_encoding, Reline.filename_quote_characters.encoding) - ensure - Reline.filename_quote_characters = filename_quote_characters - end - - def test_special_prefixes - special_prefixes = Reline.special_prefixes - - assert_equal('', Reline.special_prefixes) - - Reline.special_prefixes = "\'".encode(Encoding::ASCII) - assert_equal("\'", Reline.special_prefixes) - assert_equal(get_reline_encoding, Reline.special_prefixes.encoding) - ensure - Reline.special_prefixes = special_prefixes - end - - def test_completion_case_fold - completion_case_fold = Reline.completion_case_fold - - assert_equal(nil, Reline.completion_case_fold) - - Reline.completion_case_fold = true - assert_equal(true, Reline.completion_case_fold) - - Reline.completion_case_fold = "hoge".encode(Encoding::ASCII) - assert_equal("hoge", Reline.completion_case_fold) - ensure - Reline.completion_case_fold = completion_case_fold - end - - def test_completion_proc - omit unless Reline.completion_proc == nil - # Another test can set Reline.completion_proc - - # assert_equal(nil, Reline.completion_proc) - - dummy_proc = proc {} - Reline.completion_proc = dummy_proc - assert_equal(dummy_proc, Reline.completion_proc) - - l = lambda {} - Reline.completion_proc = l - assert_equal(l, Reline.completion_proc) - - assert_raise(ArgumentError) { Reline.completion_proc = 42 } - assert_raise(ArgumentError) { Reline.completion_proc = "hoge" } - - dummy = DummyCallbackObject.new - Reline.completion_proc = dummy - assert_equal(dummy, Reline.completion_proc) - end - - def test_output_modifier_proc - assert_equal(nil, Reline.output_modifier_proc) - - dummy_proc = proc {} - Reline.output_modifier_proc = dummy_proc - assert_equal(dummy_proc, Reline.output_modifier_proc) - - l = lambda {} - Reline.output_modifier_proc = l - assert_equal(l, Reline.output_modifier_proc) - - assert_raise(ArgumentError) { Reline.output_modifier_proc = 42 } - assert_raise(ArgumentError) { Reline.output_modifier_proc = "hoge" } - - dummy = DummyCallbackObject.new - Reline.output_modifier_proc = dummy - assert_equal(dummy, Reline.output_modifier_proc) - end - - def test_prompt_proc - assert_equal(nil, Reline.prompt_proc) - - dummy_proc = proc {} - Reline.prompt_proc = dummy_proc - assert_equal(dummy_proc, Reline.prompt_proc) - - l = lambda {} - Reline.prompt_proc = l - assert_equal(l, Reline.prompt_proc) - - assert_raise(ArgumentError) { Reline.prompt_proc = 42 } - assert_raise(ArgumentError) { Reline.prompt_proc = "hoge" } - - dummy = DummyCallbackObject.new - Reline.prompt_proc = dummy - assert_equal(dummy, Reline.prompt_proc) - end - - def test_auto_indent_proc - assert_equal(nil, Reline.auto_indent_proc) - - dummy_proc = proc {} - Reline.auto_indent_proc = dummy_proc - assert_equal(dummy_proc, Reline.auto_indent_proc) - - l = lambda {} - Reline.auto_indent_proc = l - assert_equal(l, Reline.auto_indent_proc) - - assert_raise(ArgumentError) { Reline.auto_indent_proc = 42 } - assert_raise(ArgumentError) { Reline.auto_indent_proc = "hoge" } - - dummy = DummyCallbackObject.new - Reline.auto_indent_proc = dummy - assert_equal(dummy, Reline.auto_indent_proc) - end - - def test_pre_input_hook - assert_equal(nil, Reline.pre_input_hook) - - dummy_proc = proc {} - Reline.pre_input_hook = dummy_proc - assert_equal(dummy_proc, Reline.pre_input_hook) - - l = lambda {} - Reline.pre_input_hook = l - assert_equal(l, Reline.pre_input_hook) - end - - def test_dig_perfect_match_proc - assert_equal(nil, Reline.dig_perfect_match_proc) - - dummy_proc = proc {} - Reline.dig_perfect_match_proc = dummy_proc - assert_equal(dummy_proc, Reline.dig_perfect_match_proc) - - l = lambda {} - Reline.dig_perfect_match_proc = l - assert_equal(l, Reline.dig_perfect_match_proc) - - assert_raise(ArgumentError) { Reline.dig_perfect_match_proc = 42 } - assert_raise(ArgumentError) { Reline.dig_perfect_match_proc = "hoge" } - - dummy = DummyCallbackObject.new - Reline.dig_perfect_match_proc = dummy - assert_equal(dummy, Reline.dig_perfect_match_proc) - end - - def test_insert_text - assert_equal('', Reline.line_buffer) - assert_equal(0, Reline.point) - Reline.insert_text('abc') - assert_equal('abc', Reline.line_buffer) - assert_equal(3, Reline.point) - end - - def test_delete_text - assert_equal('', Reline.line_buffer) - assert_equal(0, Reline.point) - Reline.insert_text('abc') - assert_equal('abc', Reline.line_buffer) - assert_equal(3, Reline.point) - Reline.delete_text() - assert_equal('', Reline.line_buffer) - assert_equal(0, Reline.point) - Reline.insert_text('abc') - Reline.delete_text(1) - assert_equal('a', Reline.line_buffer) - assert_equal(1, Reline.point) - Reline.insert_text('defghi') - Reline.delete_text(2, 2) - assert_equal('adghi', Reline.line_buffer) - assert_equal(5, Reline.point) - end - - def test_set_input_and_output - assert_raise(TypeError) do - Reline.input = "This is not a file." - end - assert_raise(TypeError) do - Reline.output = "This is not a file." - end - - input, to_write = IO.pipe - to_read, output = IO.pipe - unless Reline.__send__(:input=, input) - omit "Setting to input is not effective on #{Reline.core.io_gate}" - end - Reline.output = output - - to_write.write "a\n" - result = Reline.readline - to_write.close - read_text = to_read.read_nonblock(100) - assert_equal('a', result) - refute(read_text.empty?) - ensure - input&.close - output&.close - to_read&.close - end - - def test_vi_editing_mode - Reline.vi_editing_mode - assert_equal(:vi_insert, Reline.core.config.instance_variable_get(:@editing_mode_label)) - end - - def test_emacs_editing_mode - Reline.emacs_editing_mode - assert_equal(:emacs, Reline.core.config.instance_variable_get(:@editing_mode_label)) - end - - def test_add_dialog_proc - dummy_proc = proc {} - Reline.add_dialog_proc(:test_proc, dummy_proc) - d = Reline.dialog_proc(:test_proc) - assert_equal(dummy_proc, d.dialog_proc) - - dummy_proc_2 = proc {} - Reline.add_dialog_proc(:test_proc, dummy_proc_2) - d = Reline.dialog_proc(:test_proc) - assert_equal(dummy_proc_2, d.dialog_proc) - - Reline.add_dialog_proc(:test_proc, nil) - assert_nil(Reline.dialog_proc(:test_proc)) - - l = lambda {} - Reline.add_dialog_proc(:test_lambda, l) - d = Reline.dialog_proc(:test_lambda) - assert_equal(l, d.dialog_proc) - - assert_equal(nil, Reline.dialog_proc(:test_nothing)) - - assert_raise(ArgumentError) { Reline.add_dialog_proc(:error, 42) } - assert_raise(ArgumentError) { Reline.add_dialog_proc(:error, 'hoge') } - assert_raise(ArgumentError) { Reline.add_dialog_proc('error', proc {} ) } - - dummy = DummyCallbackObject.new - Reline.add_dialog_proc(:dummy, dummy) - d = Reline.dialog_proc(:dummy) - assert_equal(dummy, d.dialog_proc) - end - - def test_add_dialog_proc_with_context - dummy_proc = proc {} - array = Array.new - Reline.add_dialog_proc(:test_proc, dummy_proc, array) - d = Reline.dialog_proc(:test_proc) - assert_equal(dummy_proc, d.dialog_proc) - assert_equal(array, d.context) - - Reline.add_dialog_proc(:test_proc, dummy_proc, nil) - d = Reline.dialog_proc(:test_proc) - assert_equal(dummy_proc, d.dialog_proc) - assert_equal(nil, d.context) - end - - def test_readmultiline - # readmultiline is module function - assert_include(Reline.methods, :readmultiline) - assert_include(Reline.private_instance_methods, :readmultiline) - end - - def test_readline - # readline is module function - assert_include(Reline.methods, :readline) - assert_include(Reline.private_instance_methods, :readline) - end - - def test_read_io - # TODO in Reline::Core - end - - def test_dumb_terminal - lib = File.expand_path("../../lib", __dir__) - out = IO.popen([{"TERM"=>"dumb"}, Reline.test_rubybin, "-I#{lib}", "-rreline", "-e", "p Reline.core.io_gate"], &:read) - assert_match(/#<Reline::Dumb/, out.chomp) - end - - def test_print_prompt_before_everything_else - pend if win? - lib = File.expand_path("../../lib", __dir__) - code = "p Reline::IOGate.class; p Reline.readline 'prompt> '" - out = IO.popen([Reline.test_rubybin, "-I#{lib}", "-rreline", "-e", code], "r+") do |io| - io.write "abc\n" - io.close_write - io.read - end - assert_match(/\AReline::ANSI\nprompt> /, out) - end - - def test_read_eof_returns_input - pend if win? - lib = File.expand_path("../../lib", __dir__) - code = "p result: Reline.readline" - out = IO.popen([Reline.test_rubybin, "-I#{lib}", "-rreline", "-e", code], "r+") do |io| - io.write "a\C-a" - io.close_write - io.read - end - assert_include(out, { result: 'a' }.inspect) - end - - def test_read_eof_returns_nil_if_empty - pend if win? - lib = File.expand_path("../../lib", __dir__) - code = "p result: Reline.readline" - out = IO.popen([Reline.test_rubybin, "-I#{lib}", "-rreline", "-e", code], "r+") do |io| - io.write "a\C-h" - io.close_write - io.read - end - assert_include(out, { result: nil }.inspect) - 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_include(out.chomp, "Reline::ANSI") - end - - def win? - /mswin|mingw/.match?(RUBY_PLATFORM) - end - - def test_tty_amibuous_width - omit unless defined?(PTY) - ruby_file = Tempfile.create('rubyfile') - ruby_file.write(<<~RUBY) - require 'reline' - Thread.new { sleep 2; puts 'timeout'; exit } - p [Reline.ambiguous_width, gets.chomp] - RUBY - ruby_file.close - lib = File.expand_path('../../lib', __dir__) - cmd = [{ 'TERM' => 'xterm' }, Reline.test_rubybin, '-I', lib, ruby_file.to_path] - - # Calculate ambiguous width from cursor position - [1, 2].each do |ambiguous_width| - PTY.spawn(*cmd) do |r, w, pid| - loop { break if r.readpartial(1024).include?("\e[6n") } - w.puts "hello\e[10;#{ambiguous_width + 1}Rworld" - assert_include(r.gets, [ambiguous_width, 'helloworld'].inspect) - ensure - r.close - w.close - Process.waitpid pid - end - end - - # Ambiguous width = 1 when cursor pos timed out - PTY.spawn(*cmd) do |r, w, pid| - loop { break if r.readpartial(1024).include?("\e[6n") } - w.puts "hello\e[10;2Sworld" - assert_include(r.gets, [1, "hello\e[10;2Sworld"].inspect) - ensure - r.close - w.close - Process.waitpid pid - end - ensure - File.delete(ruby_file.path) if ruby_file - end - - def get_reline_encoding - if encoding = Reline.core.encoding - encoding - elsif win? - Encoding::UTF_8 - else - Encoding::default_external - end - end -end diff --git a/test/reline/test_reline_key.rb b/test/reline/test_reline_key.rb deleted file mode 100644 index b6260d57d6..0000000000 --- a/test/reline/test_reline_key.rb +++ /dev/null @@ -1,10 +0,0 @@ -require_relative 'helper' -require "reline" - -class Reline::TestKey < Reline::TestCase - def test_match_symbol - assert(Reline::Key.new('a', :key1, false).match?(:key1)) - refute(Reline::Key.new('a', :key1, false).match?(:key2)) - refute(Reline::Key.new('a', :key1, false).match?(nil)) - end -end diff --git a/test/reline/test_string_processing.rb b/test/reline/test_string_processing.rb deleted file mode 100644 index a105be9aba..0000000000 --- a/test/reline/test_string_processing.rb +++ /dev/null @@ -1,46 +0,0 @@ -require_relative 'helper' - -class Reline::LineEditor::StringProcessingTest < Reline::TestCase - def setup - Reline.send(:test_mode) - @prompt = '> ' - @config = Reline::Config.new - Reline::HISTORY.instance_variable_set(:@config, @config) - @line_editor = Reline::LineEditor.new(@config) - @line_editor.reset(@prompt) - end - - def teardown - Reline.test_reset - end - - def test_calculate_width - width = @line_editor.send(:calculate_width, 'Ruby string') - assert_equal('Ruby string'.size, width) - end - - def test_calculate_width_with_escape_sequence - width = @line_editor.send(:calculate_width, "\1\e[31m\2RubyColor\1\e[34m\2 default string \1\e[m\2>", true) - assert_equal('RubyColor default string >'.size, width) - end - - def test_completion_proc_with_preposing_and_postposing - buf = ['def hoge', ' puts :aaa', 'end'] - - @line_editor.instance_variable_set(:@is_multiline, true) - @line_editor.instance_variable_set(:@buffer_of_lines, buf) - @line_editor.instance_variable_set(:@byte_pointer, 6) - @line_editor.instance_variable_set(:@line_index, 1) - completion_proc_called = false - @line_editor.instance_variable_set(:@completion_proc, proc { |target, pre, post| - assert_equal('puts', target) - assert_equal("def hoge\n ", pre) - assert_equal(" :aaa\nend", post) - completion_proc_called = true - }) - - assert_equal(["def hoge\n ", 'puts', " :aaa\nend", nil], @line_editor.retrieve_completion_block) - @line_editor.__send__(:call_completion_proc, "def hoge\n ", 'puts', " :aaa\nend", nil) - assert(completion_proc_called) - end -end diff --git a/test/reline/test_unicode.rb b/test/reline/test_unicode.rb deleted file mode 100644 index 0778306c32..0000000000 --- a/test/reline/test_unicode.rb +++ /dev/null @@ -1,286 +0,0 @@ -require_relative 'helper' -require "reline/unicode" - -class Reline::Unicode::Test < Reline::TestCase - def setup - Reline.send(:test_mode) - end - - def teardown - Reline.test_reset - end - - def test_get_mbchar_width - assert_equal Reline.ambiguous_width, Reline::Unicode.get_mbchar_width('é') - end - - def test_ambiguous_width - assert_equal 1, Reline::Unicode.calculate_width('√', true) - end - - def test_csi_regexp - csi_sequences = ["\e[m", "\e[1m", "\e[12;34m", "\e[12;34H"] - assert_equal(csi_sequences, "text#{csi_sequences.join('text')}text".scan(Reline::Unicode::CSI_REGEXP)) - end - - def test_osc_regexp - osc_sequences = ["\e]1\a", "\e]0;OSC\a", "\e]1\e\\", "\e]0;OSC\e\\"] - separator = "text\atext" - assert_equal(osc_sequences, "#{separator}#{osc_sequences.join(separator)}#{separator}".scan(Reline::Unicode::OSC_REGEXP)) - end - - def test_split_by_width - # IRB uses this method. - assert_equal [['abc', 'de'], 2], Reline::Unicode.split_by_width('abcde', 3) - end - - def test_split_line_by_width - assert_equal ['abc', 'de'], Reline::Unicode.split_line_by_width('abcde', 3) - assert_equal ['abc', 'def', ''], Reline::Unicode.split_line_by_width('abcdef', 3) - assert_equal ['ab', 'あd', 'ef'], Reline::Unicode.split_line_by_width('abあdef', 3) - assert_equal ['ab[zero]c', 'def', ''], Reline::Unicode.split_line_by_width("ab\1[zero]\2cdef", 3) - assert_equal ["\e[31mabc", "\e[31md\e[42mef", "\e[31m\e[42mg"], Reline::Unicode.split_line_by_width("\e[31mabcd\e[42mefg", 3) - assert_equal ["ab\e]0;1\ac", "\e]0;1\ad"], Reline::Unicode.split_line_by_width("ab\e]0;1\acd", 3) - end - - def test_split_line_by_width_csi_reset_sgr_optimization - assert_equal ["\e[1ma\e[mb\e[2mc", "\e[2md\e[0me\e[3mf", "\e[3mg"], Reline::Unicode.split_line_by_width("\e[1ma\e[mb\e[2mcd\e[0me\e[3mfg", 3) - assert_equal ["\e[1ma\e[mzero\e[0m\e[2mb", "\e[1m\e[2mc"], Reline::Unicode.split_line_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 '[zero]cdef', Reline::Unicode.take_range("ab\1[zero]\2cdef", 2, 4) - assert_equal 'b[zero]cde', Reline::Unicode.take_range("ab\1[zero]\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) - end - - def test_nonprinting_start_end - # \1 and \2 should be removed - assert_equal 'ab[zero]cd', Reline::Unicode.take_range("ab\1[zero]\2cdef", 0, 4) - assert_equal ['ab[zero]cd', 'ef'], Reline::Unicode.split_line_by_width("ab\1[zero]\2cdef", 4) - # CSI between \1 and \2 does not need to be applied to the sebsequent line - assert_equal ["\e[31mab\e[32mcd", "\e[31mef"], Reline::Unicode.split_line_by_width("\e[31mab\1\e[32m\2cdef", 4) - end - - def test_strip_non_printing_start_end - assert_equal "ab[zero]cd[ze\1ro]ef[zero]", Reline::Unicode.strip_non_printing_start_end("ab\1[zero]\2cd\1[ze\1ro]\2ef\1[zero]") - end - - def test_calculate_width - assert_equal 9, Reline::Unicode.calculate_width('abcdefghi') - assert_equal 9, Reline::Unicode.calculate_width('abcdefghi', true) - assert_equal 7, Reline::Unicode.calculate_width('abあdef') - assert_equal 7, Reline::Unicode.calculate_width('abあdef', true) - assert_equal 16, Reline::Unicode.calculate_width("ab\1[zero]\2cdef") - assert_equal 6, Reline::Unicode.calculate_width("ab\1[zero]\2cdef", true) - assert_equal 19, Reline::Unicode.calculate_width("\e[31mabcd\e[42mefg") - assert_equal 7, Reline::Unicode.calculate_width("\e[31mabcd\e[42mefg", true) - assert_equal 12, Reline::Unicode.calculate_width("ab\e]0;1\acd") - assert_equal 4, Reline::Unicode.calculate_width("ab\e]0;1\acd", true) - 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[ABC]d\e[0mef", 2, 4], Reline::Unicode.take_mbchar_range("\e[31mabc\1[ABC]\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 - - def test_common_prefix - assert_equal('', Reline::Unicode.common_prefix([])) - assert_equal('abc', Reline::Unicode.common_prefix(['abc'])) - assert_equal('12', Reline::Unicode.common_prefix(['123', '123️⃣'])) - assert_equal('', Reline::Unicode.common_prefix(['abc', 'xyz'])) - assert_equal('ab', Reline::Unicode.common_prefix(['abcd', 'abc', 'abx', 'abcd'])) - assert_equal('A', Reline::Unicode.common_prefix(['AbcD', 'ABC', 'AbX', 'AbCD'])) - assert_equal('Ab', Reline::Unicode.common_prefix(['AbcD', 'ABC', 'AbX', 'AbCD'], ignore_case: true)) - end - - def test_encoding_conversion - texts = [ - String.new("invalid\xFFutf8", encoding: 'utf-8'), - String.new("invalid\xFFsjis", encoding: 'sjis'), - "utf8#{33111.chr('sjis')}convertible", - "utf8#{33222.chr('sjis')}inconvertible", - "sjis->utf8->sjis#{60777.chr('sjis')}irreversible" - ] - utf8_texts = [ - 'invalid�utf8', - 'invalid�sjis', - 'utf8仝convertible', - 'utf8�inconvertible', - 'sjis->utf8->sjis劦irreversible' - ] - sjis_texts = [ - 'invalid?utf8', - 'invalid?sjis', - "utf8#{33111.chr('sjis')}convertible", - 'utf8?inconvertible', - "sjis->utf8->sjis#{60777.chr('sjis')}irreversible" - ] - assert_equal(utf8_texts, texts.map { |s| Reline::Unicode.safe_encode(s, 'utf-8') }) - assert_equal(utf8_texts, texts.map { |s| Reline::Unicode.safe_encode(s, Encoding::UTF_8) }) - assert_equal(sjis_texts, texts.map { |s| Reline::Unicode.safe_encode(s, 'sjis') }) - assert_equal(sjis_texts, texts.map { |s| Reline::Unicode.safe_encode(s, Encoding::Windows_31J) }) - end - - def test_em_forward_word - assert_equal(12, Reline::Unicode.em_forward_word('abc---fooあbar-baz', 3)) - assert_equal(11, Reline::Unicode.em_forward_word('abc---fooあbar-baz'.encode('sjis'), 3)) - assert_equal(3, Reline::Unicode.em_forward_word('abcfoo', 3)) - assert_equal(3, Reline::Unicode.em_forward_word('abc---', 3)) - assert_equal(0, Reline::Unicode.em_forward_word('abc', 3)) - end - - def test_em_forward_word_with_capitalization - assert_equal([12, '---Fooあbar'], Reline::Unicode.em_forward_word_with_capitalization('abc---foOあBar-baz', 3)) - assert_equal([11, '---Fooあbar'.encode('sjis')], Reline::Unicode.em_forward_word_with_capitalization('abc---foOあBar-baz'.encode('sjis'), 3)) - assert_equal([3, 'Foo'], Reline::Unicode.em_forward_word_with_capitalization('abcfOo', 3)) - assert_equal([3, '---'], Reline::Unicode.em_forward_word_with_capitalization('abc---', 3)) - assert_equal([0, ''], Reline::Unicode.em_forward_word_with_capitalization('abc', 3)) - assert_equal([6, 'Ii̇i̇'], Reline::Unicode.em_forward_word_with_capitalization('ıİİ', 0)) - end - - def test_em_backward_word - assert_equal(12, Reline::Unicode.em_backward_word('abc foo-barあbaz--- xyz', 20)) - assert_equal(11, Reline::Unicode.em_backward_word('abc foo-barあbaz--- xyz'.encode('sjis'), 19)) - assert_equal(2, Reline::Unicode.em_backward_word(' ', 2)) - assert_equal(2, Reline::Unicode.em_backward_word('ab', 2)) - assert_equal(0, Reline::Unicode.em_backward_word('ab', 0)) - end - - def test_em_big_backward_word - assert_equal(16, Reline::Unicode.em_big_backward_word('abc foo-barあbaz--- xyz', 20)) - assert_equal(15, Reline::Unicode.em_big_backward_word('abc foo-barあbaz--- xyz'.encode('sjis'), 19)) - assert_equal(2, Reline::Unicode.em_big_backward_word(' ', 2)) - assert_equal(2, Reline::Unicode.em_big_backward_word('ab', 2)) - assert_equal(0, Reline::Unicode.em_big_backward_word('ab', 0)) - end - - def test_ed_transpose_words - # any value that does not trigger transpose - assert_equal([0, 0, 0, 2], Reline::Unicode.ed_transpose_words('aa bb cc ', 1)) - - assert_equal([0, 2, 3, 5], Reline::Unicode.ed_transpose_words('aa bb cc ', 2)) - assert_equal([0, 2, 3, 5], Reline::Unicode.ed_transpose_words('aa bb cc ', 4)) - assert_equal([3, 5, 6, 8], Reline::Unicode.ed_transpose_words('aa bb cc ', 5)) - assert_equal([3, 5, 6, 8], Reline::Unicode.ed_transpose_words('aa bb cc ', 7)) - assert_equal([3, 5, 6, 10], Reline::Unicode.ed_transpose_words('aa bb cc ', 8)) - assert_equal([3, 5, 6, 10], Reline::Unicode.ed_transpose_words('aa bb cc ', 9)) - ['sjis', 'utf-8'].each do |encoding| - texts = ['fooあ', 'barあbaz', 'aaa -', '- -', '- bbb'] - word1, word2, left, middle, right = texts.map { |text| text.encode(encoding) } - expected = [left.bytesize, (left + word1).bytesize, (left + word1 + middle).bytesize, (left + word1 + middle + word2).bytesize] - assert_equal(expected, Reline::Unicode.ed_transpose_words(left + word1 + middle + word2 + right, left.bytesize + word1.bytesize)) - assert_equal(expected, Reline::Unicode.ed_transpose_words(left + word1 + middle + word2 + right, left.bytesize + word1.bytesize + middle.bytesize)) - assert_equal(expected, Reline::Unicode.ed_transpose_words(left + word1 + middle + word2 + right, left.bytesize + word1.bytesize + middle.bytesize + word2.bytesize - 1)) - end - end - - def test_vi_big_forward_word - assert_equal(18, Reline::Unicode.vi_big_forward_word('abc---fooあbar-baz xyz', 3)) - assert_equal(8, Reline::Unicode.vi_big_forward_word('abcfooあ --', 3)) - assert_equal(7, Reline::Unicode.vi_big_forward_word('abcfooあ --'.encode('sjis'), 3)) - assert_equal(6, Reline::Unicode.vi_big_forward_word('abcfooあ', 3)) - assert_equal(3, Reline::Unicode.vi_big_forward_word('abc- ', 3)) - assert_equal(0, Reline::Unicode.vi_big_forward_word('abc', 3)) - end - - def test_vi_big_forward_end_word - assert_equal(4, Reline::Unicode.vi_big_forward_end_word('a bb c', 0)) - assert_equal(4, Reline::Unicode.vi_big_forward_end_word('- bb c', 0)) - assert_equal(1, Reline::Unicode.vi_big_forward_end_word('-a b', 0)) - assert_equal(1, Reline::Unicode.vi_big_forward_end_word('a- b', 0)) - assert_equal(1, Reline::Unicode.vi_big_forward_end_word('aa b', 0)) - assert_equal(3, Reline::Unicode.vi_big_forward_end_word(' aa b', 0)) - assert_equal(15, Reline::Unicode.vi_big_forward_end_word('abc---fooあbar-baz xyz', 3)) - assert_equal(14, Reline::Unicode.vi_big_forward_end_word('abc---fooあbar-baz xyz'.encode('sjis'), 3)) - assert_equal(3, Reline::Unicode.vi_big_forward_end_word('abcfooあ --', 3)) - assert_equal(3, Reline::Unicode.vi_big_forward_end_word('abcfooあ', 3)) - assert_equal(2, Reline::Unicode.vi_big_forward_end_word('abc- ', 3)) - assert_equal(0, Reline::Unicode.vi_big_forward_end_word('abc', 3)) - end - - def test_vi_big_backward_word - assert_equal(16, Reline::Unicode.vi_big_backward_word('abc foo-barあbaz--- xyz', 20)) - assert_equal(15, Reline::Unicode.vi_big_backward_word('abc foo-barあbaz--- xyz'.encode('sjis'), 19)) - assert_equal(2, Reline::Unicode.vi_big_backward_word(' ', 2)) - assert_equal(2, Reline::Unicode.vi_big_backward_word('ab', 2)) - assert_equal(0, Reline::Unicode.vi_big_backward_word('ab', 0)) - end - - def test_vi_forward_word - assert_equal(3, Reline::Unicode.vi_forward_word('abc---fooあbar-baz', 3)) - assert_equal(9, Reline::Unicode.vi_forward_word('abc---fooあbar-baz', 6)) - assert_equal(8, Reline::Unicode.vi_forward_word('abc---fooあbar-baz'.encode('sjis'), 6)) - assert_equal(6, Reline::Unicode.vi_forward_word('abcfooあ', 3)) - assert_equal(3, Reline::Unicode.vi_forward_word('abc---', 3)) - assert_equal(0, Reline::Unicode.vi_forward_word('abc', 3)) - assert_equal(2, Reline::Unicode.vi_forward_word('abc def', 1, true)) - assert_equal(5, Reline::Unicode.vi_forward_word('abc def', 1, false)) - end - - def test_vi_forward_end_word - assert_equal(2, Reline::Unicode.vi_forward_end_word('abc---fooあbar-baz', 3)) - assert_equal(8, Reline::Unicode.vi_forward_end_word('abc---fooあbar-baz', 6)) - assert_equal(7, Reline::Unicode.vi_forward_end_word('abc---fooあbar-baz'.encode('sjis'), 6)) - assert_equal(3, Reline::Unicode.vi_forward_end_word('abcfooあ', 3)) - assert_equal(2, Reline::Unicode.vi_forward_end_word('abc---', 3)) - assert_equal(0, Reline::Unicode.vi_forward_end_word('abc', 3)) - end - - def test_vi_backward_word - assert_equal(3, Reline::Unicode.vi_backward_word('abc foo-barあbaz--- xyz', 20)) - assert_equal(9, Reline::Unicode.vi_backward_word('abc foo-barあbaz--- xyz', 17)) - assert_equal(8, Reline::Unicode.vi_backward_word('abc foo-barあbaz--- xyz'.encode('sjis'), 16)) - assert_equal(2, Reline::Unicode.vi_backward_word(' ', 2)) - assert_equal(2, Reline::Unicode.vi_backward_word('ab', 2)) - assert_equal(0, Reline::Unicode.vi_backward_word('ab', 0)) - end - - def test_vi_first_print - assert_equal(3, Reline::Unicode.vi_first_print(' abcdefg')) - assert_equal(3, Reline::Unicode.vi_first_print(' ')) - assert_equal(0, Reline::Unicode.vi_first_print('abc')) - assert_equal(0, Reline::Unicode.vi_first_print('あ')) - assert_equal(0, Reline::Unicode.vi_first_print('あ'.encode('sjis'))) - assert_equal(0, Reline::Unicode.vi_first_print('')) - end - - def test_character_type - assert(Reline::Unicode.word_character?('a')) - assert(Reline::Unicode.word_character?('あ')) - assert(Reline::Unicode.word_character?('あ'.encode('sjis'))) - refute(Reline::Unicode.word_character?(33345.chr('sjis'))) - refute(Reline::Unicode.word_character?('-')) - refute(Reline::Unicode.word_character?(nil)) - - assert(Reline::Unicode.space_character?(' ')) - refute(Reline::Unicode.space_character?('あ')) - refute(Reline::Unicode.space_character?('あ'.encode('sjis'))) - refute(Reline::Unicode.space_character?(33345.chr('sjis'))) - refute(Reline::Unicode.space_character?('-')) - refute(Reline::Unicode.space_character?(nil)) - end -end diff --git a/test/reline/test_within_pipe.rb b/test/reline/test_within_pipe.rb deleted file mode 100644 index e5d0c12b9c..0000000000 --- a/test/reline/test_within_pipe.rb +++ /dev/null @@ -1,77 +0,0 @@ -require_relative 'helper' - -class Reline::WithinPipeTest < Reline::TestCase - def setup - Reline.send(:test_mode) - @encoding = Reline.core.encoding - @input_reader, @writer = IO.pipe(@encoding) - Reline.input = @input_reader - @reader, @output_writer = IO.pipe(@encoding) - @output = Reline.output = @output_writer - @config = Reline.core.config - @config.keyseq_timeout *= 600 if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? # for --jit-wait CI - @line_editor = Reline.core.line_editor - end - - def teardown - Reline.input = STDIN - Reline.output = STDOUT - Reline.point = 0 - Reline.delete_text - @input_reader.close - @writer.close - @reader.close - @output_writer.close - @config.reset - Reline.test_reset - end - - def test_simple_input - @writer.write("abc\n") - assert_equal 'abc', Reline.readmultiline(&proc{ true }) - end - - def test_unknown_macro - @config.add_default_key_binding('abc'.bytes, :unknown_macro) - @writer.write("abcd\n") - assert_equal 'd', Reline.readmultiline(&proc{ true }) - end - - def test_macro_commands_for_moving - @config.add_default_key_binding("\C-x\C-a".bytes, :beginning_of_line) - @config.add_default_key_binding("\C-x\C-e".bytes, :end_of_line) - @config.add_default_key_binding("\C-x\C-f".bytes, :forward_char) - @config.add_default_key_binding("\C-x\C-b".bytes, :backward_char) - @config.add_default_key_binding("\C-x\ef".bytes, :forward_word) - @config.add_default_key_binding("\C-x\eb".bytes, :backward_word) - @writer.write(" def\C-x\C-aabc\C-x\C-e ghi\C-x\C-a\C-x\C-f\C-x\C-f_\C-x\C-b\C-x\C-b_\C-x\C-f\C-x\C-f\C-x\C-f\C-x\ef_\C-x\eb\n") - assert_equal 'a_b_c def_ ghi', Reline.readmultiline(&proc{ true }) - end - - def test_macro_commands_for_editing - @config.add_default_key_binding("\C-x\C-d".bytes, :delete_char) - @config.add_default_key_binding("\C-x\C-h".bytes, :backward_delete_char) - @config.add_default_key_binding("\C-x\C-v".bytes, :quoted_insert) - #@config.add_default_key_binding("\C-xa".bytes, :self_insert) - @config.add_default_key_binding("\C-x\C-t".bytes, :transpose_chars) - @config.add_default_key_binding("\C-x\et".bytes, :transpose_words) - @config.add_default_key_binding("\C-x\eu".bytes, :upcase_word) - @config.add_default_key_binding("\C-x\el".bytes, :downcase_word) - @config.add_default_key_binding("\C-x\ec".bytes, :capitalize_word) - @writer.write("abcde\C-b\C-b\C-b\C-x\C-d\C-x\C-h\C-x\C-v\C-a\C-f\C-f EF\C-x\C-t gh\C-x\et\C-b\C-b\C-b\C-b\C-b\C-b\C-b\C-b\C-x\eu\C-x\el\C-x\ec\n") - assert_equal "a\C-aDE gh Fe", Reline.readmultiline(&proc{ true }) - end - - def test_delete_text_in_multiline - @writer.write("abc\ndef\nxyz\n") - result = Reline.readmultiline(&proc{ |str| - if str.include?('xyz') - Reline.delete_text - true - else - false - end - }) - assert_equal "abc\ndef", result - end -end diff --git a/test/reline/windows/test_key_event_record.rb b/test/reline/windows/test_key_event_record.rb deleted file mode 100644 index 25c860606a..0000000000 --- a/test/reline/windows/test_key_event_record.rb +++ /dev/null @@ -1,41 +0,0 @@ -require_relative '../helper' -return unless Reline.const_defined?(:Windows) - -class Reline::Windows - class KeyEventRecord::Test < Reline::TestCase - - def setup - # Ctrl+A - @key = Reline::Windows::KeyEventRecord.new(0x41, 1, Reline::Windows::LEFT_CTRL_PRESSED) - end - - def test_matches__with_no_arguments_raises_error - assert_raise(ArgumentError) { @key.match? } - end - - def test_matches_char_code - assert @key.match?(char_code: 0x1) - end - - def test_matches_virtual_key_code - assert @key.match?(virtual_key_code: 0x41) - end - - def test_matches_control_keys - assert @key.match?(control_keys: :CTRL) - end - - def test_doesnt_match_alt - refute @key.match?(control_keys: :ALT) - end - - def test_doesnt_match_empty_control_key - refute @key.match?(control_keys: []) - end - - def test_matches_control_keys_and_virtual_key_code - assert @key.match?(control_keys: :CTRL, virtual_key_code: 0x41) - end - - end -end diff --git a/test/reline/yamatanooroti/multiline_repl b/test/reline/yamatanooroti/multiline_repl deleted file mode 100755 index 8b82be60f4..0000000000 --- a/test/reline/yamatanooroti/multiline_repl +++ /dev/null @@ -1,257 +0,0 @@ -#!/usr/bin/env ruby - - -require 'bundler' -Bundler.require - -require 'reline' -require 'optparse' -require_relative 'termination_checker' - -opt = OptionParser.new -opt.on('--dynamic-prompt') { - Reline.prompt_proc = proc { |lines| - lines.each_with_index.map { |l, i| - "\e[1m[%04d]>\e[m " % i - } - } -} -opt.on('--broken-dynamic-prompt') { - Reline.prompt_proc = proc { |lines| - range = lines.size > 1 ? (0..(lines.size - 2)) : (0..0) - lines[range].each_with_index.map { |l, i| - '[%04d]> ' % i - } - } -} -opt.on('--dynamic-prompt-returns-empty') { - Reline.prompt_proc = proc { |l| [] } -} -opt.on('--dynamic-prompt-with-newline') { - Reline.prompt_proc = proc { |lines| - range = lines.size > 1 ? (0..(lines.size - 2)) : (0..0) - lines[range].each_with_index.map { |l, i| - '[%04d\n]> ' % i - } - } -} -opt.on('--broken-dynamic-prompt-assert-no-escape-sequence') { - Reline.prompt_proc = proc { |lines| - has_escape_sequence = lines.join.include?("\e") - (lines.size + 1).times.map { |i| - has_escape_sequence ? 'error>' : '[%04d]> ' % i - } - } -} -opt.on('--color-bold') { - Reline.output_modifier_proc = ->(output, complete:){ - output.gsub(/./) { |c| "\e[1m#{c}\e[0m" } - } -} -opt.on('--dynamic-prompt-show-line') { - Reline.prompt_proc = proc { |lines| - lines.map { |l| - '[%4.4s]> ' % l - } - } -} - -def assert_auto_indent_params(lines, line_index, byte_pointer, is_newline) - raise 'Wrong lines type' unless lines.all?(String) - - line = lines[line_index] - raise 'Wrong line_index value' unless line - - # The condition `byte_pointer <= line.bytesize` is not satisfied. Maybe bug. - # Instead, loose constraint `byte_pointer <= line.bytesize + 1` seems to be satisfied when is_newline is false. - return if is_newline - - raise 'byte_pointer out of bounds' unless byte_pointer <= line.bytesize + 1 - raise 'Invalid byte_pointer' unless line.byteslice(0, byte_pointer).valid_encoding? -end - -opt.on('--auto-indent') { - Reline.auto_indent_proc = lambda do |lines, line_index, byte_pointer, is_newline| - assert_auto_indent_params(lines, line_index, byte_pointer, is_newline) - AutoIndent.calculate_indent(lines, line_index, byte_pointer, is_newline) - end -} -opt.on('--dialog VAL') { |v| - Reline.add_dialog_proc(:simple_dialog, lambda { - return nil if v.include?('nil') - if v.include?('simple') - contents = <<~RUBY.split("\n") - Ruby is... - A dynamic, open source programming - language with a focus on simplicity - and productivity. It has an elegant - syntax that is natural to read and - easy to write. - RUBY - elsif v.include?('long') - contents = <<~RUBY.split("\n") - Ruby is... - A dynamic, open - source programming - language with a - focus on simplicity - and productivity. - It has an elegant - syntax that is - natural to read - and easy to write. - RUBY - elsif v.include?('fullwidth') - contents = <<~RUBY.split("\n") - Rubyとは... - - オープンソースの動的なプログラミン - グ言語で、シンプルさと高い生産性を - 備えています。エレガントな文法を持 - ち、自然に読み書きができます。 - RUBY - end - if v.include?('scrollkey') - dialog.trap_key = nil - if key and key.match?(dialog.name) - if dialog.pointer.nil? - dialog.pointer = 0 - elsif dialog.pointer >= (contents.size - 1) - dialog.pointer = 0 - else - dialog.pointer += 1 - end - end - dialog.trap_key = [?j.ord] - height = 4 - end - scrollbar = false - if v.include?('scrollbar') - scrollbar = true - end - if v.include?('alt-scrollbar') - scrollbar = true - end - Reline::DialogRenderInfo.new(pos: cursor_pos, contents: contents, height: height, scrollbar: scrollbar, face: :completion_dialog) - }) - if v.include?('alt-scrollbar') - ENV['RELINE_ALT_SCROLLBAR'] = '1' - end -} -opt.on('--complete') { - Reline.completion_proc = lambda { |target, preposing = nil, postposing = nil| - %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| - %w{ - String - Struct - Symbol - StopIteration - SystemCallError - SystemExit - SystemStackError - ScriptError - SyntaxError - Signal - SizedQueue - Set - SecureRandom - Socket - StringIO - StringScanner - Shellwords - Syslog - Singleton - SDBM - }.select{ |c| c.start_with?(target) } - } -} -opt.on('--autocomplete-super-long') { - Reline.autocompletion = true - Reline.completion_proc = lambda { |target, preposing = nil, postposing = nil| - c = +'A' - 2000.times.map{ s = "Str_#{c}"; c.succ!; s }.select{ |c| c.start_with?(target) } - } -} - -opt.on('--autocomplete-width-long') { - Reline.autocompletion = true - Reline.completion_proc = lambda { |target, preposing = nil, postposing = nil| - %w{ - remove_instance_variable - respond_to? - ruby2_keywords - rand - readline - readlines - require - require_relative - raise - respond_to_missing? - redo - rescue - retry - return - }.select{ |c| c.start_with?(target) } - } -} -opt.parse!(ARGV) - -begin - stty_save = `stty -g`.chomp -rescue -end - -begin - 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 - when 'exit', 'quit', 'q' - exit 0 - when '' - # NOOP - else - begin - result = eval(code) - puts "=> #{result.inspect}" - rescue ScriptError, StandardError => e - puts "Traceback (most recent call last):" - e.backtrace.reverse_each do |f| - puts " #{f}" - end - puts e.message - end - end - end -rescue Interrupt - puts '^C' - `stty #{stty_save}` if stty_save - exit 0 -ensure - `stty #{stty_save}` if stty_save -end -begin - puts -rescue Errno::EIO - # Maybe the I/O has been closed. -end diff --git a/test/reline/yamatanooroti/termination_checker.rb b/test/reline/yamatanooroti/termination_checker.rb deleted file mode 100644 index b97c798c59..0000000000 --- a/test/reline/yamatanooroti/termination_checker.rb +++ /dev/null @@ -1,26 +0,0 @@ -require 'ripper' - -module TerminationChecker - def self.terminated?(code) - Ripper.sexp(code) ? true : false - end -end - -module AutoIndent - def self.calculate_indent(lines, line_index, byte_pointer, is_newline) - if is_newline - 2 * nesting_level(lines[0..line_index - 1]) - else - lines = lines.dup - lines[line_index] = lines[line_index]&.byteslice(0, byte_pointer) - prev_level = nesting_level(lines[0..line_index - 1]) - level = nesting_level(lines[0..line_index]) - 2 * level if level < prev_level - end - end - - def self.nesting_level(lines) - code = lines.join("\n") - code.scan(/if|def|\(|\[|\{/).size - code.scan(/end|\)|\]|\}/).size - end -end diff --git a/test/reline/yamatanooroti/test_rendering.rb b/test/reline/yamatanooroti/test_rendering.rb deleted file mode 100644 index aff5c0462b..0000000000 --- a/test/reline/yamatanooroti/test_rendering.rb +++ /dev/null @@ -1,1915 +0,0 @@ -require 'reline' - -begin - require 'yamatanooroti' - - class Reline::RenderingTest < Yamatanooroti::TestCase - - FACE_CONFIGS = { no_config: "", valid_config: <<~VALID_CONFIG, incomplete_config: <<~INCOMPLETE_CONFIG } - require "reline" - Reline::Face.config(:completion_dialog) do |face| - face.define :default, foreground: :white, background: :blue - face.define :enhanced, foreground: :white, background: :magenta - face.define :scrollbar, foreground: :white, background: :blue - end - VALID_CONFIG - require "reline" - Reline::Face.config(:completion_dialog) do |face| - face.define :default, foreground: :white, background: :black - face.define :scrollbar, foreground: :white, background: :cyan - end - INCOMPLETE_CONFIG - - def iterate_over_face_configs(&block) - FACE_CONFIGS.each do |config_name, face_config| - config_file = Tempfile.create(%w{face_config- .rb}) - config_file.write face_config - block.call(config_name, config_file) - ensure - config_file.close - File.delete(config_file) - end - end - - def setup - @pwd = Dir.pwd - suffix = '%010d' % Random.rand(0..65535) - @tmpdir = File.join(File.expand_path(Dir.tmpdir), "test_reline_config_#{$$}_#{suffix}") - begin - Dir.mkdir(@tmpdir) - rescue Errno::EEXIST - FileUtils.rm_rf(@tmpdir) - Dir.mkdir(@tmpdir) - end - @inputrc_backup = ENV['INPUTRC'] - @inputrc_file = ENV['INPUTRC'] = File.join(@tmpdir, 'temporaty_inputrc') - File.unlink(@inputrc_file) if File.exist?(@inputrc_file) - end - - def teardown - FileUtils.rm_rf(@tmpdir) - ENV['INPUTRC'] = @inputrc_backup - ENV.delete('RELINE_TEST_PROMPT') if ENV['RELINE_TEST_PROMPT'] - end - - def test_history_back - start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write(":a\n") - write("\C-p") - assert_screen(<<~EOC) - Multiline REPL. - prompt> :a - => :a - prompt> :a - EOC - close - end - - def test_backspace - start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write(":abc\C-h\n") - assert_screen(<<~EOC) - Multiline REPL. - prompt> :ab - => :ab - prompt> - EOC - close - end - - def test_autowrap - start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write('01234567890123456789012') - assert_screen(<<~EOC) - Multiline REPL. - prompt> 0123456789012345678901 - 2 - EOC - close - end - - def test_fullwidth - start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write(":あ\n") - assert_screen(<<~EOC) - Multiline REPL. - prompt> :あ - => :あ - prompt> - EOC - close - end - - def test_two_fullwidth - start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write(":あい\n") - assert_screen(<<~EOC) - Multiline REPL. - prompt> :あい - => :あい - prompt> - EOC - close - end - - def test_finish_autowrapped_line - start_terminal(10, 40, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write("[{'user'=>{'email'=>'a@a', 'id'=>'ABC'}, 'version'=>4, 'status'=>'succeeded'}]\n") - expected = [{'user'=>{'email'=>'a@a', 'id'=>'ABC'}, 'version'=>4, 'status'=>'succeeded'}].inspect - assert_screen(<<~EOC) - Multiline REPL. - prompt> [{'user'=>{'email'=>'a@a', 'id'= - >'ABC'}, 'version'=>4, 'status'=>'succee - ded'}] - #{fold_multiline("=> " + expected, 40)} - prompt> - EOC - close - end - - def test_finish_autowrapped_line_in_the_middle_of_lines - start_terminal(20, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write("[{'user'=>{'email'=>'abcdef@abcdef', 'id'=>'ABC'}, 'version'=>4, 'status'=>'succeeded'}]#{"\C-b"*7}") - write("\n") - expected = [{'user'=>{'email'=>'abcdef@abcdef', 'id'=>'ABC'}, 'version'=>4, 'status'=>'succeeded'}].inspect - assert_screen(<<~EOC) - Multiline REPL. - prompt> [{'user'=>{'email'=>'a - bcdef@abcdef', 'id'=>'ABC'}, ' - version'=>4, 'status'=>'succee - ded'}] - #{fold_multiline("=> " + expected, 30)} - prompt> - EOC - close - end - - def test_finish_autowrapped_line_in_the_middle_of_multilines - omit if RUBY_VERSION < '2.7' - start_terminal(30, 16, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write("<<~EOM\n ABCDEFG\nEOM\n") - assert_screen(<<~'EOC') - Multiline REPL. - prompt> <<~EOM - prompt> ABCDEF - G - prompt> EOM - => "ABCDEFG\n" - prompt> - EOC - close - end - - def test_prompt - write_inputrc <<~'LINES' - "abc": "123" - LINES - start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write("abc\n") - assert_screen(<<~EOC) - Multiline REPL. - prompt> 123 - => 123 - prompt> - EOC - close - end - - def test_mode_string_emacs - write_inputrc <<~LINES - set show-mode-in-prompt on - LINES - start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - assert_screen(<<~EOC) - Multiline REPL. - @prompt> - EOC - close - end - - def test_mode_string_vi - write_inputrc <<~LINES - set editing-mode vi - set show-mode-in-prompt on - 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") - assert_screen(<<~EOC) - (ins)prompt> :a - => :a - (ins)prompt> :a - => :a - (cmd)prompt> :a - EOC - close - end - - def test_original_mode_string_emacs - write_inputrc <<~LINES - set show-mode-in-prompt on - set emacs-mode-string [emacs] - LINES - start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - assert_screen(<<~EOC) - Multiline REPL. - [emacs]prompt> - EOC - close - end - - def test_original_mode_string_with_quote - write_inputrc <<~LINES - set show-mode-in-prompt on - set emacs-mode-string "[emacs]" - LINES - start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - assert_screen(<<~EOC) - Multiline REPL. - [emacs]prompt> - EOC - close - end - - def test_original_mode_string_vi - write_inputrc <<~LINES - set editing-mode vi - set show-mode-in-prompt on - set vi-ins-mode-string "{InS}" - set vi-cmd-mode-string "{CmD}" - 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") - assert_screen(<<~EOC) - Multiline REPL. - {InS}prompt> :a - => :a - {CmD}prompt> :a - EOC - close - end - - def test_mode_string_vi_changing - write_inputrc <<~LINES - set editing-mode vi - set show-mode-in-prompt on - LINES - start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write(":a\C-[ab\C-[ac\C-h\C-h\C-h\C-h:a") - assert_screen(<<~EOC) - Multiline REPL. - (ins)prompt> :a - EOC - close - end - - def test_esc_input - omit if Reline::IOGate.win? - start_terminal(5, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write("def\C-aabc") - write("\e") # single ESC - sleep 1 - write("A") - write("B\eAC") # ESC + A (M-A, no key specified in Reline::KeyActor::Emacs) - assert_screen(<<~EOC) - Multiline REPL. - prompt> abcABCdef - EOC - close - end - - def test_prompt_with_escape_sequence - ENV['RELINE_TEST_PROMPT'] = "\1\e[30m\2prompt> \1\e[m\2" - start_terminal(5, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write("123\n") - assert_screen(<<~EOC) - Multiline REPL. - prompt> 123 - => 123 - prompt> - EOC - close - end - - def test_prompt_with_escape_sequence_and_autowrap - ENV['RELINE_TEST_PROMPT'] = "\1\e[30m\2prompt> \1\e[m\2" - start_terminal(5, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write("1234567890123\n") - assert_screen(<<~EOC) - Multiline REPL. - prompt> 123456789012 - 3 - => 1234567890123 - prompt> - EOC - close - 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") - assert_screen(<<~EOC) - => :foo - [0000]> Reline.readline('prompt> ') - prompt> def foo - bar - end - EOC - close - 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") - assert_screen(<<~EOC) - Multiline REPL. - prompt> def aaaaaaaa - aa - prompt> puts 333333 - 33 - prompt> 888888888888 - 888 - prompt> e - nd - EOC - close - end - - def test_multiline_add_new_line_and_autowrap - start_terminal(10, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write("def aaaaaaaaaa") - write("\n") - write(" bbbbbbbbbbbb") - write("\n") - assert_screen(<<~EOC) - Multiline REPL. - prompt> def aaaaaaaa - aa - prompt> bbbbbbbbbb - bb - prompt> - EOC - close - end - - def test_clear - start_terminal(10, 15, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write("3\C-l") - assert_screen(<<~EOC) - prompt> 3 - EOC - close - end - - def test_clear_multiline_and_autowrap - start_terminal(10, 15, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write("def aaaaaa\n 3\n\C-lend") - assert_screen(<<~EOC) - prompt> def aaa - aaa - prompt> 3 - prompt> end - EOC - close - end - - def test_nearest_cursor - start_terminal(10, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write("def ああ\n :いい\nend\C-pbb\C-pcc") - assert_screen(<<~EOC) - Multiline REPL. - prompt> def ccああ - prompt> :bbいい - prompt> end - EOC - close - end - - def test_delete_line - start_terminal(10, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write("def a\n\nend\C-p\C-h") - assert_screen(<<~EOC) - Multiline REPL. - prompt> def a - prompt> end - EOC - close - end - - def test_last_line_of_screen - start_terminal(5, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write("\n\n\n\n\ndef a\nend") - assert_screen(<<~EOC) - prompt> - prompt> - prompt> - prompt> def a - prompt> end - EOC - close - end - - # c17a09b7454352e2aff5a7d8722e80afb73e454b - def test_autowrap_at_last_line_of_screen - start_terminal(5, 15, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write("def a\nend\n\C-p") - assert_screen(<<~EOC) - prompt> def a - prompt> end - => :a - prompt> def a - prompt> end - EOC - close - end - - # f002483b27cdb325c5edf9e0fe4fa4e1c71c4b0e - def test_insert_line_in_the_middle_of_line - start_terminal(5, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write("333\C-b\C-b\e\C-m8") - assert_screen(<<~EOC) - Multiline REPL. - prompt> 3 - prompt> 833 - EOC - close - end - - # 9d8978961c5de5064f949d56d7e0286df9e18f43 - def test_insert_line_in_the_middle_of_line_at_last_line_of_screen - start_terminal(3, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write("333333333333333\C-a\C-f\e\C-m") - assert_screen(<<~EOC) - prompt> 3 - prompt> 333333333333 - 33 - EOC - close - end - - def test_insert_after_clear - start_terminal(10, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write("def a\n 01234\nend\C-l\C-p5678") - assert_screen(<<~EOC) - prompt> def a - prompt> 056781234 - prompt> end - EOC - close - end - - def test_foced_newline_insertion - start_terminal(10, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - #write("def a\nend\C-p\C-e\e\C-m 3") - write("def a\nend\C-p\C-e\e\x0D") - assert_screen(<<~EOC) - Multiline REPL. - prompt> def a - prompt> - prompt> end - EOC - close - end - - def test_multiline_incremental_search - start_terminal(6, 25, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write("def a\n 8\nend\ndef b\n 3\nend\C-s8") - assert_screen(<<~EOC) - prompt> 8 - prompt> end - => :a - (i-search)`8'def a - (i-search)`8' 8 - (i-search)`8'end - EOC - close - end - - def test_multiline_incremental_search_finish - start_terminal(6, 25, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write("def a\n 8\nend\ndef b\n 3\nend\C-r8\C-j") - assert_screen(<<~EOC) - prompt> 8 - prompt> end - => :a - prompt> def a - prompt> 8 - prompt> end - EOC - close - end - - def test_binding_for_vi_movement_mode - write_inputrc <<~LINES - set editing-mode vi - "\\C-a": vi-movement-mode - LINES - start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write(":1234\C-ahhhi0") - assert_screen(<<~EOC) - Multiline REPL. - prompt> :01234 - EOC - close - 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") - assert_screen(<<~EOC) - Multiline REPL. - [0000]> def hoge - [0001]> 3 - [0001]> end - EOC - close - end - - def test_no_escape_sequence_passed_to_dynamic_prompt - start_terminal(10, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete --color-bold --broken-dynamic-prompt-assert-no-escape-sequence}, startup_message: 'Multiline REPL.') - write("%[ S") - write("\n") - assert_screen(<<~EOC) - Multiline REPL. - [0000]> %[ S - [0001]> - EOC - close - end - - def test_bracketed_paste - 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("\e[200~def hoge\r\t3\rend\e[201~") - assert_screen(<<~EOC) - Multiline REPL. - prompt> def hoge - prompt> 3 - prompt> end - EOC - write("\e[200~.tap do\r\t4\r\t5\rend\e[201~") - assert_screen(<<~EOC) - prompt> 3 - prompt> end.tap do - prompt> 4 - prompt> 5 - prompt> end - EOC - close - end - - def test_bracketed_paste_with_undo_redo - 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-_") - assert_screen(<<~EOC) - Multiline REPL. - prompt> abc - EOC - write("\e\C-_") - assert_screen(<<~EOC) - Multiline REPL. - prompt> abcdef hoge - prompt> 3 - prompt> end - EOC - close - 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") - write("\C-h\C-h\C-h") - assert_screen(<<~EOC) - Multiline REPL. - prompt> - EOC - close - end - - def test_longer_than_screen_height - start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write(<<~EOC.chomp) - def each_top_level_statement - initialize_input - catch(:TERM_INPUT) do - loop do - begin - prompt - unless l = lex - throw :TERM_INPUT if @line == '' - else - @line_no += l.count("\n") - next if l == "\n" - @line.concat l - if @code_block_open or @ltype or @continue or @indent > 0 - next - end - end - if @line != "\n" - @line.force_encoding(@io.encoding) - yield @line, @exp_line_no - end - break if @io.eof? - @line = '' - @exp_line_no = @line_no - # - @indent = 0 - rescue TerminateLineInput - initialize_input - prompt - end - end - end - end - EOC - assert_screen(<<~EOC) - prompt> prompt - prompt> end - prompt> end - prompt> end - prompt> end - EOC - write("\C-p" * 6) - assert_screen(<<~EOC) - prompt> rescue Terminate - LineInput - prompt> initialize_inp - ut - prompt> prompt - EOC - write("\C-n" * 4) - assert_screen(<<~EOC) - prompt> initialize_inp - ut - prompt> prompt - prompt> end - prompt> end - EOC - close - end - - def test_longer_than_screen_height_nearest_cursor_with_scroll_back - start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write(<<~EOC.chomp) - if 1 - if 2 - if 3 - if 4 - puts - end - end - end - end - EOC - write("\C-p" * 4 + "\C-e" + "\C-p" * 4) - write("2") - assert_screen(<<~EOC) - prompt> if 12 - prompt> if 2 - prompt> if 3 - prompt> if 4 - prompt> puts - EOC - close - end - - def test_update_cursor_correctly_when_just_cursor_moving - start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write("def hoge\n 01234678") - write("\C-p") - write("\C-b") - write("\C-n") - write('5') - write("\C-e") - write('9') - assert_screen(<<~EOC) - Multiline REPL. - prompt> def hoge - prompt> 0123456789 - EOC - close - end - - def test_auto_indent - start_terminal(10, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --auto-indent}, startup_message: 'Multiline REPL.') - "def hoge\nputs(\n1,\n2\n)\nend".lines do |line| - write line - end - assert_screen(<<~EOC) - Multiline REPL. - prompt> def hoge - prompt> puts( - prompt> 1, - prompt> 2 - prompt> ) - prompt> end - EOC - close - end - - def test_auto_indent_when_inserting_line - start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --auto-indent}, startup_message: 'Multiline REPL.') - write 'aa(bb(cc(dd(ee(' - write "\C-b" * 5 + "\n" - assert_screen(<<~EOC) - Multiline REPL. - prompt> aa(bb(cc(d - prompt> d(ee( - EOC - close - end - - def test_auto_indent_multibyte_insert_line - start_terminal(10, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --auto-indent}, startup_message: 'Multiline REPL.') - write "if true\n" - write "あいうえお\n" - 4.times { write "\C-b\C-b\C-b\C-b\e\r" } - assert_screen(<<~EOC) - Multiline REPL. - prompt> if true - prompt> あ - prompt> い - prompt> う - prompt> え - prompt> お - prompt> - EOC - close - end - - def test_newline_after_wrong_indent - start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --auto-indent}, startup_message: 'Multiline REPL.') - write "if 1\n aa" - write "\n" - assert_screen(<<~EOC) - Multiline REPL. - prompt> if 1 - prompt> aa - prompt> - EOC - close - end - - def test_suppress_auto_indent_just_after_pasted - start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --auto-indent}, startup_message: 'Multiline REPL.') - write("def hoge\n [[\n 3]]\ned") - write("\C-bn") - assert_screen(<<~EOC) - Multiline REPL. - prompt> def hoge - prompt> [[ - prompt> 3]] - prompt> end - EOC - close - end - - def test_suppress_auto_indent_for_adding_newlines_in_pasting - start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --auto-indent}, startup_message: 'Multiline REPL.') - write("<<~Q\n") - write("{\n #\n}") - write("#") - assert_screen(<<~EOC) - Multiline REPL. - prompt> <<~Q - prompt> { - prompt> # - prompt> }# - EOC - close - 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)" - assert_screen(<<~EOC) - Multiline REPL. - prompt> ( - prompt> ^K - prompt> ) - EOC - close - 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") - %w{h i}.each do |c| - write(c) - end - assert_screen(<<~EOC) - Multiline REPL. - prompt> def abcdefgh - i; end - EOC - close - end - - def test_newline_in_the_middle_of_lines - start_terminal(5, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write("def hoge\n 1\n 2\n 3\n 4\nend\n") - write("\C-p\C-p\C-p\C-e\n") - assert_screen(<<~EOC) - prompt> def hoge - prompt> 1 - prompt> 2 - prompt> 3 - prompt> - EOC - close - end - - def test_ed_force_submit_in_the_middle_of_lines - write_inputrc <<~LINES - "\\C-a": ed_force_submit - LINES - start_terminal(5, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write("def hoge\nend") - write("\C-p\C-a") - assert_screen(<<~EOC) - Multiline REPL. - prompt> def hoge - prompt> end - => :hoge - prompt> - EOC - close - end - - def test_dynamic_prompt_returns_empty - start_terminal(5, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --dynamic-prompt-returns-empty}, startup_message: 'Multiline REPL.') - write("def hoge\nend\n") - assert_screen(<<~EOC) - Multiline REPL. - prompt> def hoge - prompt> end - => :hoge - prompt> - EOC - close - end - - def test_reset_rest_height_when_clear_screen - start_terminal(5, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write("\n\n\n\C-l3\n") - assert_screen(<<~EOC) - prompt> 3 - => 3 - prompt> - EOC - close - end - - def test_meta_key - start_terminal(30, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write("def ge\ebho") - assert_screen(<<~EOC) - Multiline REPL. - prompt> def hoge - EOC - close - end - - def test_not_meta_key - start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write("おだんご") # "だ" in UTF-8 contains "\xA0" - assert_screen(<<~EOC) - Multiline REPL. - prompt> おだんご - EOC - close - end - - def test_force_enter - start_terminal(30, 120, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write("def hoge\nend\C-p\C-e") - write("\e\x0D") - assert_screen(<<~EOC) - Multiline REPL. - prompt> def hoge - prompt> - prompt> end - EOC - close - end - - def test_nontty - omit if Reline.core.io_gate.win? - cmd = %Q{ruby -e 'puts(%Q{ello\C-ah\C-e})' | ruby -I#{@pwd}/lib -rreline -e 'p Reline.readline(%{> })' | ruby -e 'print STDIN.read'} - start_terminal(40, 50, ['bash', '-c', cmd]) - assert_screen(<<~'EOC') - > hello - "hello" - EOC - close - end - - 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]) - assert_screen(<<~'EOC') - > abc def - "abc def " - EOC - close - 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]) - assert_screen(<<~'EOC') - > hello - "hello" - EOC - close - 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\eb\eb\e\x20\eb\C-x\C-xX\C-x\C-xY") - assert_screen(<<~'EOC') - Multiline REPL. - prompt> aaa Ybbb Xccc ddd - EOC - close - end - - def test_multiline_completion - start_terminal(10, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --complete}, startup_message: 'Multiline REPL.') - write("def hoge\n St\n St\C-p\t") - assert_screen(<<~'EOC') - Multiline REPL. - prompt> def hoge - prompt> String - prompt> St - EOC - close - end - - def test_completion_journey_2nd_line - write_inputrc <<~LINES - set editing-mode vi - LINES - start_terminal(10, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --complete}, startup_message: 'Multiline REPL.') - write("def hoge\n S\C-n") - assert_screen(<<~'EOC') - Multiline REPL. - prompt> def hoge - prompt> String - EOC - close - end - - def test_completion_journey_with_empty_line - write_inputrc <<~LINES - set editing-mode vi - LINES - start_terminal(10, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --complete}, startup_message: 'Multiline REPL.') - write("\C-n\C-p") - assert_screen(<<~'EOC') - Multiline REPL. - prompt> - EOC - close - 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") - assert_screen(<<~'EOC') - Multiline REPL. - prompt> S - ScriptError String - Signal SyntaxError - EOC - close - 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") - assert_screen(<<~'EOC') - Multiline REPL. - prompt> S - ScriptError String - Signal SyntaxError - EOC - close - 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") - assert_screen(<<~'EOC') - Multiline REPL. - prompt> abs - abs abs2 - EOC - close - 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.') - write('a') - write('b') - write('c') - write("\C-h") - close - assert_screen(<<~'EOC', "Failed with `#{config_name}` in Face") - Multiline REPL. - prompt> ab - Ruby is... - A dynamic, open source programming - language with a focus on simplicity - and productivity. It has an elegant - syntax that is natural to read and - easy to write. - EOC - end - end - - def test_simple_dialog_at_right_edge - iterate_over_face_configs do |config_name, config_file| - start_terminal(20, 40, %W{ruby -I#{@pwd}/lib -r#{config_file.path} #{@pwd}/test/reline/yamatanooroti/multiline_repl --dialog simple}, startup_message: 'Multiline REPL.') - write('a') - write('b') - write('c') - write("\C-h") - close - assert_screen(<<~'EOC') - Multiline REPL. - prompt> ab - Ruby is... - A dynamic, open source programming - language with a focus on simplicity - and productivity. It has an elegant - syntax that is natural to read and - easy to write. - EOC - end - end - - def test_dialog_scroll_pushup_condition - 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 sSts\nend") - write("\C-p\C-h\C-e\C-h") - assert_screen(<<~'EOC') - prompt> - prompt> - prompt> - prompt> - prompt> - prompt> - prompt> if 1 - prompt> St - prompt> enString - Struct - EOC - close - end - end - - def test_simple_dialog_with_scroll_screen - iterate_over_face_configs do |config_name, config_file| - start_terminal(5, 50, %W{ruby -I#{@pwd}/lib -r#{config_file.path} #{@pwd}/test/reline/yamatanooroti/multiline_repl --dialog simple}, startup_message: /prompt>/) - write("if 1\n 2\n 3\n 4\n 5\n 6") - write("\C-p\C-n\C-p\C-p\C-p#") - close - assert_screen(<<~'EOC') - prompt> 2 - prompt> 3# - prompt> 4 - prompt> 5 Ruby is... - prompt> 6 A dynamic, open source programming - EOC - end - end - - def test_autocomplete_at_bottom - start_terminal(15, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.') - write('def hoge' + "\C-m" * 10 + "end\C-p ") - write('S') - assert_screen(<<~'EOC') - Multiline REPL. - prompt> def hoge - prompt> - prompt> - prompt> String - prompt> Struct - prompt> Symbol - prompt> ScriptError - prompt> SyntaxError - prompt> Signal - prompt> S - prompt> end - EOC - close - end - - def test_autocomplete_return_to_original - start_terminal(20, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.') - write('S') - write('t') - write('r') - 3.times{ write("\C-i") } - assert_screen(<<~'EOC') - Multiline REPL. - prompt> Str - String - Struct - EOC - close - end - - def test_autocomplete_target_is_wrapped - start_terminal(20, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.') - write(' ') - write('S') - write('t') - write('r') - assert_screen(<<~'EOC') - Multiline REPL. - prompt> St - r - String - Struct - EOC - close - 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") - assert_screen(<<~'EOC') - Multiline REPL. - prompt> Str - ing String - Struct - EOC - close - 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") - assert_screen(<<~'EOC') - Multiline REPL. - prompt> Stri - ng String - Struct - EOC - close - 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") - assert_screen(<<~'EOC') - Multiline REPL. - prompt> Sy;Struct - String - Struct - EOC - close - 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') - 5.times{ write('j') } - assert_screen(<<~'EOC') - Multiline REPL. - prompt> a - A dynamic, open - source programming - language with a - focus on simplicity - EOC - close - end - - def test_simple_dialog_scrollbar_with_moving_to_right - start_terminal(20, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --dialog long,scrollkey,scrollbar}, startup_message: 'Multiline REPL.') - 6.times{ write('j') } - write('a') - assert_screen(<<~'EOC') - Multiline REPL. - prompt> a - source programming ▄ - language with a █ - focus on simplicity - and productivity. - EOC - close - end - - def test_simple_dialog_scrollbar_with_moving_to_left - start_terminal(20, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --dialog long,scrollkey,scrollbar}, startup_message: 'Multiline REPL.') - write('a') - 6.times{ write('j') } - write("\C-h") - assert_screen(<<~'EOC') - Multiline REPL. - prompt> - source programming ▄ - language with a █ - focus on simplicity - and productivity. - EOC - close - end - - def test_dialog_with_fullwidth_chars - ENV['RELINE_TEST_PROMPT'] = '> ' - start_terminal(20, 5, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --dialog fullwidth,scrollkey,scrollbar}, startup_message: 'Multiline REPL.') - 6.times{ write('j') } - assert_screen(<<~'EOC') - Multi - line - REPL. - > - オー - グ言▄ - 備え█ - ち、█ - EOC - close - end - - def test_dialog_with_fullwidth_chars_split - ENV['RELINE_TEST_PROMPT'] = '> ' - start_terminal(20, 6, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --dialog fullwidth,scrollkey,scrollbar}, startup_message: 'Multiline REPL.') - 6.times{ write('j') } - assert_screen(<<~'EOC') - Multil - ine RE - PL. - > - オー - グ言 ▄ - 備え █ - ち、 █ - EOC - close - end - - def test_autocomplete_empty - start_terminal(20, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.') - write('Street') - assert_screen(<<~'EOC') - Multiline REPL. - prompt> Street - EOC - close - end - - def test_autocomplete - start_terminal(20, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.') - write('Str') - assert_screen(<<~'EOC') - Multiline REPL. - prompt> Str - String - Struct - EOC - close - 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") - assert_screen(<<~'EOC') - Multiline REPL. - prompt> String - String █ - Struct ▀ - Symbol - EOC - close - 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") - assert_screen(<<~'EOC') - Multiline REPL. - prompt> 2.times do - prompt> puts - prompt> puts - prompt> end - EOC - close - 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") - assert_screen(<<~'EOC') - Multiline REPL. - prompt> def hoge - prompt> Str - String - Struct - EOC - close - end - - def test_autocomplete_rerender_under_dialog - start_terminal(20, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.') - write("def hoge\n\n 123456\n 456789\nend\C-p\C-p\C-p a = Str") - write('i') - assert_screen(<<~'EOC') - Multiline REPL. - prompt> def hoge - prompt> a = Stri - prompt> 1234String - prompt> 456789 - prompt> end - EOC - close - end - - def test_rerender_multiple_dialog - start_terminal(20, 60, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete --dialog simple}, startup_message: 'Multiline REPL.') - write("if\n abcdef\n 123456\n 456789\nend\C-p\C-p\C-p\C-p Str") - write("\t") - assert_screen(<<~'EOC') - Multiline REPL. - prompt> if String - prompt> aStringRuby is... - prompt> 1StructA dynamic, open source programming - prompt> 456789 language with a focus on simplicity - prompt> end and productivity. It has an elegant - syntax that is natural to read and - easy to write. - EOC - close - end - - def test_autocomplete_long_with_scrollbar - start_terminal(20, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete-long}, startup_message: 'Multiline REPL.') - write('S') - assert_screen(<<~'EOC') - Multiline REPL. - prompt> S - String █ - Struct █ - Symbol █ - StopIteration █ - SystemCallError █ - SystemExit █ - SystemStackError█ - ScriptError █ - SyntaxError █ - Signal █ - SizedQueue █ - Set - SecureRandom - Socket - StringIO - EOC - write("\C-i" * 16) - assert_screen(<<~'EOC') - Multiline REPL. - prompt> StringScanner - Struct ▄ - Symbol █ - StopIteration █ - SystemCallError █ - SystemExit █ - SystemStackError█ - ScriptError █ - SyntaxError █ - Signal █ - SizedQueue █ - Set █ - SecureRandom ▀ - Socket - StringIO - StringScanner - EOC - close - end - - def test_autocomplete_super_long_scroll_to_bottom - start_terminal(20, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete-super-long}, startup_message: 'Multiline REPL.') - shift_tab = [27, 91, 90] - write('S' + shift_tab.map(&:chr).join) - assert_screen(<<~'EOC') - Multiline REPL. - prompt> Str_BXX - Str_BXJ - Str_BXK - Str_BXL - Str_BXM - Str_BXN - Str_BXO - Str_BXP - Str_BXQ - Str_BXR - Str_BXS - Str_BXT - Str_BXU - Str_BXV - Str_BXW - Str_BXX▄ - EOC - close - end - - def test_autocomplete_super_long_and_backspace - start_terminal(20, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete-super-long}, startup_message: 'Multiline REPL.') - shift_tab = [27, 91, 90] - write('S' + shift_tab.map(&:chr).join) - write("\C-h") - assert_screen(<<~'EOC') - Multiline REPL. - prompt> Str_BX - Str_BX █ - Str_BXA█ - Str_BXB█ - Str_BXC█ - Str_BXD█ - Str_BXE█ - Str_BXF█ - Str_BXG█ - Str_BXH█ - Str_BXI - Str_BXJ - Str_BXK - Str_BXL - Str_BXM - Str_BXN - EOC - close - end - - def test_dialog_callback_returns_nil - start_terminal(20, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --dialog nil}, startup_message: 'Multiline REPL.') - write('a') - assert_screen(<<~'EOC') - Multiline REPL. - prompt> a - EOC - close - end - - def test_dialog_narrower_than_screen - start_terminal(20, 11, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --dialog simple}, startup_message: 'Multiline REPL.') - assert_screen(<<~'EOC') - Multiline R - EPL. - prompt> - Ruby is... - A dynamic, - language wi - and product - syntax that - easy to wri - EOC - close - end - - def test_dialog_narrower_than_screen_with_scrollbar - start_terminal(20, 11, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete-long}, startup_message: 'Multiline REPL.') - write('S' + "\C-i" * 3) - assert_screen(<<~'EOC') - Multiline R - EPL. - prompt> Sym - String █ - Struct █ - Symbol █ - StopIterat█ - SystemCall█ - SystemExit█ - SystemStac█ - ScriptErro█ - SyntaxErro█ - Signal █ - SizedQueue█ - Set - SecureRand - Socket - StringIO - EOC - close - end - - def test_dialog_with_fullwidth_scrollbar - start_terminal(20, 40, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --dialog simple,scrollkey,alt-scrollbar}, startup_message: 'Multiline REPL.') - assert_screen(<<~'EOC') - Multiline REPL. - prompt> - Ruby is... :: - A dynamic, open source programming :: - language with a focus on simplicity'' - and productivity. It has an elegant - EOC - close - end - - def test_rerender_argument_prompt_after_pasting - start_terminal(20, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write('abcdef') - write("\e3\C-h") - assert_screen(<<~'EOC') - Multiline REPL. - prompt> abc - EOC - close - end - - def test_autocomplete_old_dialog_width_greater_than_dialog_width - start_terminal(40, 40, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete-width-long}, startup_message: 'Multiline REPL.') - write("0+ \n12345678901234") - write("\C-p") - write("r") - write("a") - assert_screen(<<~'EOC') - Multiline REPL. - prompt> 0+ ra - prompt> 123rand 901234 - raise - EOC - close - end - - def test_scroll_at_bottom_for_dialog - start_terminal(10, 40, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.') - write("\n\n\n\n\n\n\n\n\n\n\n") - write("def hoge\n\nend\C-p\C-e") - write(" S") - assert_screen(<<~'EOC') - prompt> - prompt> - prompt> - prompt> - prompt> - prompt> def hoge - prompt> S - prompt> enString █ - Struct ▀ - Symbol - EOC - close - end - - def test_clear_dialog_in_pasting - start_terminal(10, 40, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.') - write("S") - write("tring ") - assert_screen(<<~'EOC') - Multiline REPL. - prompt> String - EOC - close - end - - def test_prompt_with_newline - ENV['RELINE_TEST_PROMPT'] = "::\n> " - start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write("def hoge\n 3\nend") - assert_screen(<<~'EOC') - Multiline REPL. - ::\n> def hoge - ::\n> 3 - ::\n> end - EOC - close - end - - def test_dynamic_prompt_with_newline - start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --dynamic-prompt-with-newline}, startup_message: 'Multiline REPL.') - write("def hoge\n 3\nend") - assert_screen(<<~'EOC') - Multiline REPL. - [0000\n]> def hoge - [0001\n]> 3 - [0001\n]> end - EOC - close - end - - def test_lines_passed_to_dynamic_prompt - start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --dynamic-prompt-show-line}, startup_message: 'Multiline REPL.') - write("if true") - write("\n") - assert_screen(<<~EOC) - Multiline REPL. - [if t]> if true - [ ]> - EOC - close - end - - def test_clear_dialog_when_just_move_cursor_at_last_line - start_terminal(10, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.') - write("class A\n 3\nend\n\n\n") - write("\C-p\C-p\C-e; S") - assert_screen(/String/) - write("\C-n") - write(";") - assert_screen(<<~'EOC') - prompt> 3 - prompt> end - => 3 - prompt> - prompt> - prompt> class A - prompt> 3; S - prompt> end; - EOC - close - end - - def test_clear_dialog_when_adding_new_line_to_end_of_buffer - start_terminal(10, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.') - write("class A\n def a\n 3\n 3\n end\nend") - write("\n") - write("class S") - write("\n") - write(" 3") - assert_screen(<<~'EOC') - prompt> def a - prompt> 3 - prompt> 3 - prompt> end - prompt> end - => :a - prompt> class S - prompt> 3 - EOC - close - end - - def test_insert_newline_in_the_middle_of_buffer_just_after_dialog - start_terminal(10, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.') - write("class A\n def a\n 3\n end\nend") - write("\n") - write("\C-p\C-p\C-p\C-p\C-p\C-e\C-hS") - write("\e\x0D") - write(" 3") - assert_screen(<<~'EOC') - prompt> 3 - prompt> end - prompt> end - => :a - prompt> class S - prompt> 3 - prompt> def a - prompt> 3 - prompt> end - prompt> end - EOC - close - end - - def test_incremental_search_on_not_last_line - start_terminal(10, 40, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.') - write("def abc\nend\n") - write("def def\nend\n") - write("\C-p\C-p\C-e") - write("\C-r") - write("a") - write("\n\n") - assert_screen(<<~'EOC') - prompt> def abc - prompt> end - => :abc - prompt> def def - prompt> end - => :def - prompt> def abc - prompt> end - => :abc - prompt> - EOC - close - end - - def test_bracket_newline_indent - start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --auto-indent}, startup_message: 'Multiline REPL.') - write("[\n") - write("1") - assert_screen(<<~EOC) - Multiline REPL. - prompt> [ - prompt> 1 - EOC - close - end - - def test_repeated_input_delete - start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write("a\C-h" * 4000) - assert_screen(<<~'EOC') - Multiline REPL. - prompt> - EOC - close - end - - def test_exit_with_ctrl_d - start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --auto-indent}, startup_message: 'Multiline REPL.') - begin - write("\C-d") - close - rescue EOFError - # EOFError is raised when process terminated. - end - assert_screen(<<~EOC) - Multiline REPL. - prompt> - EOC - close - end - - def test_quoted_insert_intr_keys - 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 '"' - write "\C-v" - write "\C-c" - write "\C-v" - write "\C-z" - write "\C-v" - write "\C-\\" - write "\".bytes\n" - assert_screen(<<~EOC) - Multiline REPL. - prompt> "^C^Z^\\\".bytes - => [3, 26, 28] - prompt> - EOC - close - end - - def test_print_before_readline - code = <<~RUBY - puts 'Multiline REPL.' - 2.times do - print 'a' * 10 - Reline.readline '>' - end - RUBY - start_terminal(6, 30, ['ruby', "-I#{@pwd}/lib", '-rreline', '-e', code], startup_message: 'Multiline REPL.') - write "x\n" - assert_screen(<<~EOC) - Multiline REPL. - >x - > - EOC - close - end - - def test_pre_input_hook_with_redisplay - code = <<~'RUBY' - puts 'Multiline REPL.' - Reline.pre_input_hook = -> do - Reline.insert_text 'abc' - Reline.redisplay # Reline doesn't need this but Readline requires calling redisplay - end - Reline.readline('prompt> ') - RUBY - start_terminal(6, 30, ['ruby', "-I#{@pwd}/lib", '-rreline', '-e', code], startup_message: 'Multiline REPL.') - assert_screen(<<~EOC) - Multiline REPL. - prompt> abc - EOC - close - end - - def test_pre_input_hook_with_multiline_text_insert - # Frequently used pattern of pre_input_hook - code = <<~'RUBY' - puts 'Multiline REPL.' - Reline.pre_input_hook = -> do - Reline.insert_text "abc\nef" - end - Reline.readline('>') - RUBY - start_terminal(6, 30, ['ruby', "-I#{@pwd}/lib", '-rreline', '-e', code], startup_message: 'Multiline REPL.') - write("\C-ad") - assert_screen(<<~EOC) - Multiline REPL. - >abc - def - EOC - close - 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") - assert_screen(<<~EOC) - >exit - >exit - => 2 - prompt> 42 - => 42 - prompt> - EOC - close - end - - def test_user_defined_winch - omit if Reline.core.io_gate.win? - pidfile = Tempfile.create('pidfile') - rubyfile = Tempfile.create('rubyfile') - rubyfile.write <<~RUBY - File.write(#{pidfile.path.inspect}, Process.pid) - winch_called = false - Signal.trap(:WINCH, ->(_arg){ winch_called = true }) - p Reline.readline('>') - puts "winch: \#{winch_called}" - RUBY - rubyfile.close - - start_terminal(10, 50, %W{ruby -I#{@pwd}/lib -rreline #{rubyfile.path}}) - assert_screen(/^>/) - write 'a' - assert_screen(/^>a/) - pid = pidfile.tap(&:rewind).read.to_i - Process.kill(:WINCH, pid) unless pid.zero? - write "b\n" - assert_screen(/"ab"\nwinch: true/) - close - ensure - File.delete(rubyfile.path) if rubyfile - pidfile.close if pidfile - File.delete(pidfile.path) if pidfile - end - - def test_stop_continue - omit if Reline.core.io_gate.win? - pidfile = Tempfile.create('pidfile') - rubyfile = Tempfile.create('rubyfile') - rubyfile.write <<~RUBY - File.write(#{pidfile.path.inspect}, Process.pid) - cont_called = false - Signal.trap(:CONT, ->(_arg){ cont_called = true }) - Reline.readmultiline('>'){|input| input.match?(/ghi/) } - puts "cont: \#{cont_called}" - RUBY - rubyfile.close - start_terminal(10, 50, ['bash']) - write "ruby -I#{@pwd}/lib -rreline #{rubyfile.path}\n" - assert_screen(/^>/) - write "abc\ndef\nhi" - pid = pidfile.tap(&:rewind).read.to_i - Process.kill(:STOP, pid) unless pid.zero? - write "fg\n" - assert_screen(/fg\n.*>/m) - write "\ebg" - assert_screen(/>abc\n>def\n>ghi\n/) - write "\n" - assert_screen(/cont: true/) - close - ensure - File.delete(rubyfile.path) if rubyfile - pidfile.close if pidfile - File.delete(pidfile.path) if pidfile - end - - def write_inputrc(content) - File.open(@inputrc_file, 'w') do |f| - f.write content - end - end - - def fold_multiline(str, width) - str.scan(/.{1,#{width}}/).each(&:rstrip!).join("\n") - end - end -rescue LoadError, NameError - # On Ruby repository, this test suit doesn't run because Ruby repo doesn't - # have the yamatanooroti gem. -end |