From 213c5beaeeb28bf81b17af8c65423f10a2d59863 Mon Sep 17 00:00:00 2001 From: tompng Date: Tue, 22 Aug 2023 13:29:06 +0900 Subject: [PATCH 1/3] Remove instance variable prompt and line_no from RubyLex --- lib/irb.rb | 82 ++++++++++++++++++++++++--------------------- lib/irb/ruby-lex.rb | 26 -------------- 2 files changed, 43 insertions(+), 65 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index ba7feabf0..48c37e66c 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -437,6 +437,7 @@ def initialize(workspace = nil, input_method = nil) @context.workspace.load_commands_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 @@ -454,7 +455,7 @@ def debug_readline(binding) workspace = IRB::WorkSpace.new(binding) context.workspace = workspace context.workspace.load_commands_to_main - scanner.increase_line_no(1) + @line_no += 1 # When users run: # 1. Debugging commands, like `step 2` @@ -476,7 +477,7 @@ def debug_readline(binding) end if input&.include?("\n") - scanner.increase_line_no(input.count("\n") - 1) + @line_no += input.count("\n") - 1 end input @@ -513,34 +514,38 @@ def run(conf = IRB.conf) # The lexer used by this irb session attr_accessor :scanner - # Evaluates input for this session. - def eval_input - @scanner.set_prompt do - |ltype, indent, continue, line_no| - if ltype - f = @context.prompt_s - elsif continue - f = @context.prompt_c - else - f = @context.prompt_i - end - f = "" unless f - if @context.prompting? - @context.io.prompt = p = prompt(f, ltype, indent, line_no) - else - @context.io.prompt = p = "" - end - if @context.auto_indent_mode and !@context.io.respond_to?(:auto_indent) - unless ltype - prompt_i = @context.prompt_i.nil? ? "" : @context.prompt_i - ind = prompt(prompt_i, ltype, indent, line_no)[/.*\z/].size + - indent * 2 - p.size - @context.io.prompt = p + " " * ind if ind > 0 - end + def 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 !@context.io.respond_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 - @context.io.prompt end + p + end + # Evaluates input for this session. + def eval_input configure_io each_top_level_statement do |statement, line_no| @@ -572,8 +577,9 @@ def eval_input end end - def read_input + def read_input(prompt_string) signal_status(:IN_INPUT) do + @context.io.prompt = prompt_string if l = @context.io.gets print l if @context.verbose? else @@ -591,16 +597,16 @@ def read_input end def readmultiline - @scanner.save_prompt_to_context_io([], false, 0) + prompt_string = prompt([], false, 0) # multiline - return read_input if @context.io.respond_to?(:check_termination) + return read_input(prompt_string) if @context.io.respond_to?(:check_termination) # nomultiline code = '' line_offset = 0 loop do - line = read_input + line = read_input(prompt_string) unless line return code.empty? ? nil : code end @@ -615,7 +621,7 @@ def readmultiline line_offset += 1 continue = @scanner.should_continue?(tokens) - @scanner.save_prompt_to_context_io(opens, continue, line_offset) + prompt_string = prompt(opens, continue, line_offset) end end @@ -625,9 +631,9 @@ def each_top_level_statement break unless code if code != "\n" - yield build_statement(code), @scanner.line_no + yield build_statement(code), @line_no end - @scanner.increase_line_no(code.count("\n")) + @line_no += code.count("\n") rescue RubyLex::TerminateLineInput end end @@ -688,7 +694,7 @@ def configure_io tokens_until_line << token if token != tokens_until_line.last end continue = @scanner.should_continue?(tokens_until_line) - @scanner.prompt(next_opens, continue, line_num_offset) + prompt(next_opens, continue, line_num_offset) end end end @@ -883,9 +889,8 @@ def truncate_prompt_main(str) # :nodoc: end end - def prompt(prompt, ltype, indent, line_no) # :nodoc: - p = prompt.dup - p.gsub!(/%([0-9]+)?([a-zA-Z])/) do + def format_prompt(format, ltype, indent, line_no) # :nodoc: + format.gsub(/%([0-9]+)?([a-zA-Z])/) do case $2 when "N" @context.irb_name @@ -919,7 +924,6 @@ def prompt(prompt, ltype, indent, line_no) # :nodoc: "%" end end - p end def output_value(omit = false) # :nodoc: diff --git a/lib/irb/ruby-lex.rb b/lib/irb/ruby-lex.rb index 63756d8f8..4bce2aa6b 100644 --- a/lib/irb/ruby-lex.rb +++ b/lib/irb/ruby-lex.rb @@ -42,13 +42,6 @@ def initialize end end - attr_reader :line_no - - def initialize - @line_no = 1 - @prompt = nil - end - def self.compile_with_errors_suppressed(code, line_no: 1) begin result = yield code, line_no @@ -66,10 +59,6 @@ def self.compile_with_errors_suppressed(code, line_no: 1) result end - def set_prompt(&block) - @prompt = block - end - ERROR_TOKENS = [ :on_parse_error, :compile_error, @@ -145,12 +134,6 @@ def self.ripper_lex_without_warning(code, local_variables: []) $VERBOSE = verbose end - def prompt(opens, continue, line_num_offset) - ltype = ltype_from_open_tokens(opens) - indent_level = calc_indent_level(opens) - @prompt&.call(ltype, indent_level, opens.any? || continue, @line_no + line_num_offset) - 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) @@ -170,15 +153,6 @@ def code_terminated?(code, tokens, opens, local_variables:) end end - def save_prompt_to_context_io(opens, continue, line_num_offset) - # Implicitly saves prompt string to `@context.io.prompt`. This will be used in the next `@input.call`. - prompt(opens, continue, line_num_offset) - end - - def increase_line_no(addition) - @line_no += addition - 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. From f1aef4a3091abce0ac36c29ebbcb568860843fce Mon Sep 17 00:00:00 2001 From: tompng Date: Wed, 30 Aug 2023 18:46:40 +0900 Subject: [PATCH 2/3] Fix prompt test --- test/irb/test_context.rb | 8 ++++---- test/irb/test_irb.rb | 38 +++++++++++++++++++------------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/test/irb/test_context.rb b/test/irb/test_context.rb index 34bceea54..d42879b1f 100644 --- a/test/irb/test_context.rb +++ b/test/irb/test_context.rb @@ -614,21 +614,21 @@ def test_eval_input_with_long_exception 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.prompt('irb(%m)>', nil, 1, 1)) + assert_equal("irb(main )>", irb.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.prompt('irb(%M)>', nil, 1, 1)) + assert_equal("irb(main\\n main)>", irb.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.prompt('irb(%m)>', nil, 1, 1)) - assert_equal('irb("aaaaaaaaaaaaaaaaaaaaaaaaaaaa...)>', irb.prompt('irb(%M)>', nil, 1, 1)) + assert_equal('irb(aaaaaaaaaaaaaaaaaaaaaaaaaaaaa...)>', irb.format_prompt('irb(%m)>', nil, 1, 1)) + assert_equal('irb("aaaaaaaaaaaaaaaaaaaaaaaaaaaa...)>', irb.format_prompt('irb(%M)>', nil, 1, 1)) end def test_lineno diff --git a/test/irb/test_irb.rb b/test/irb/test_irb.rb index 4870f35f3..84cdc002b 100644 --- a/test/irb/test_irb.rb +++ b/test/irb/test_irb.rb @@ -75,7 +75,6 @@ class MockIO_AutoIndent def initialize(*params) @params = params - @calculated_indent end def auto_indent(&block) @@ -84,14 +83,14 @@ def auto_indent(&block) end class MockIO_DynamicPrompt + attr_reader :prompt_list + def initialize(params, &assertion) @params = params - @assertion = assertion end def dynamic_prompt(&block) - result = block.call(@params) - @assertion.call(result) + @prompt_list = block.call(@params) end end @@ -710,24 +709,25 @@ def test_dynamic_prompt_with_blank_line def assert_dynamic_prompt(input_with_prompt) expected_prompt_list, lines = input_with_prompt.transpose - dynamic_prompt_executed = false - io = MockIO_DynamicPrompt.new(lines) do |prompt_list| - error_message = <<~EOM - Expected dynamic prompt: - #{expected_prompt_list.join("\n")} - - Actual dynamic prompt: - #{prompt_list.join("\n")} - EOM - dynamic_prompt_executed = true - assert_equal(expected_prompt_list, prompt_list, error_message) - end - @irb.context.io = io - @irb.scanner.set_prompt do |ltype, indent, continue, line_no| + def @irb.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 - assert dynamic_prompt_executed, "dynamic_prompt's assertions were not executed." + + 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 From ab7a84078f4bfed9552ae6715ef3e763aa1a0fd4 Mon Sep 17 00:00:00 2001 From: tompng Date: Thu, 12 Oct 2023 02:03:32 +0900 Subject: [PATCH 3/3] Rename prompt generating method and make it private --- lib/irb.rb | 20 ++++++++++---------- test/irb/test_context.rb | 8 ++++---- test/irb/test_irb.rb | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index 48c37e66c..9d0588d01 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -514,7 +514,7 @@ def run(conf = IRB.conf) # The lexer used by this irb session attr_accessor :scanner - def prompt(opens, continue, line_offset) + 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 @@ -577,9 +577,9 @@ def eval_input end end - def read_input(prompt_string) + def read_input(prompt) signal_status(:IN_INPUT) do - @context.io.prompt = prompt_string + @context.io.prompt = prompt if l = @context.io.gets print l if @context.verbose? else @@ -597,16 +597,16 @@ def read_input(prompt_string) end def readmultiline - prompt_string = prompt([], false, 0) + prompt = generate_prompt([], false, 0) # multiline - return read_input(prompt_string) if @context.io.respond_to?(:check_termination) + return read_input(prompt) if @context.io.respond_to?(:check_termination) # nomultiline code = '' line_offset = 0 loop do - line = read_input(prompt_string) + line = read_input(prompt) unless line return code.empty? ? nil : code end @@ -621,7 +621,7 @@ def readmultiline line_offset += 1 continue = @scanner.should_continue?(tokens) - prompt_string = prompt(opens, continue, line_offset) + prompt = generate_prompt(opens, continue, line_offset) end end @@ -694,7 +694,7 @@ def configure_io tokens_until_line << token if token != tokens_until_line.last end continue = @scanner.should_continue?(tokens_until_line) - prompt(next_opens, continue, line_num_offset) + generate_prompt(next_opens, continue, line_num_offset) end end end @@ -880,7 +880,7 @@ def signal_status(status) end end - def truncate_prompt_main(str) # :nodoc: + private def truncate_prompt_main(str) # :nodoc: str = str.tr(CONTROL_CHARACTERS_PATTERN, ' ') if str.size <= PROMPT_MAIN_TRUNCATE_LENGTH str @@ -889,7 +889,7 @@ def truncate_prompt_main(str) # :nodoc: end end - def format_prompt(format, ltype, indent, line_no) # :nodoc: + private def format_prompt(format, ltype, indent, line_no) # :nodoc: format.gsub(/%([0-9]+)?([a-zA-Z])/) do case $2 when "N" diff --git a/test/irb/test_context.rb b/test/irb/test_context.rb index d42879b1f..af47bec9d 100644 --- a/test/irb/test_context.rb +++ b/test/irb/test_context.rb @@ -614,21 +614,21 @@ def test_eval_input_with_long_exception 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.format_prompt('irb(%m)>', nil, 1, 1)) + 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.format_prompt('irb(%M)>', nil, 1, 1)) + 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.format_prompt('irb(%m)>', nil, 1, 1)) - assert_equal('irb("aaaaaaaaaaaaaaaaaaaaaaaaaaaa...)>', irb.format_prompt('irb(%M)>', nil, 1, 1)) + 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_lineno diff --git a/test/irb/test_irb.rb b/test/irb/test_irb.rb index 84cdc002b..e89f2a1c7 100644 --- a/test/irb/test_irb.rb +++ b/test/irb/test_irb.rb @@ -709,7 +709,7 @@ def test_dynamic_prompt_with_blank_line def assert_dynamic_prompt(input_with_prompt) expected_prompt_list, lines = input_with_prompt.transpose - def @irb.prompt(opens, continue, line_offset) + 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