diff options
author | Stan Lo <stan.lo@shopify.com> | 2022-11-20 04:47:51 +0000 |
---|---|---|
committer | git <svn-admin@ruby-lang.org> | 2022-11-20 04:47:54 +0000 |
commit | 180ed611b238db48db9feb1449c4e3b563d2dce0 (patch) | |
tree | bfd4d7be6b7c0d17773eeead6ea0f6c63186e342 /lib | |
parent | 439990318d90a689b2ac067b41c3462ddda60ae5 (diff) |
[ruby/irb] Add edit command (https://github.com/ruby/irb/pull/453)
* Add edit command
* Make find_source a public singleton method
* Add document for the edit command
* Make find_end private
* Remove duplicated private
https://github.com/ruby/irb/commit/4321674aa7
Co-authored-by: Takashi Kokubun <takashikkbn@gmail.com>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/irb/cmd/edit.rb | 65 | ||||
-rw-r--r-- | lib/irb/cmd/show_source.rb | 87 | ||||
-rw-r--r-- | lib/irb/extend-command.rb | 4 |
3 files changed, 113 insertions, 43 deletions
diff --git a/lib/irb/cmd/edit.rb b/lib/irb/cmd/edit.rb new file mode 100644 index 0000000000..8d3fab3273 --- /dev/null +++ b/lib/irb/cmd/edit.rb @@ -0,0 +1,65 @@ +require 'shellwords' +require_relative "nop" + +module IRB + # :stopdoc: + + module ExtendCommand + class Edit < Nop + class << self + def transform_args(args) + # Return a string literal as is for backward compatibility + if args.nil? || args.empty? || string_literal?(args) + args + else # Otherwise, consider the input as a String for convenience + args.strip.dump + end + end + + private + + def string_literal?(args) + sexp = Ripper.sexp(args) + sexp && sexp.size == 2 && sexp.last&.first&.first == :string_literal + end + end + + def execute(*args) + path = args.first + + if path.nil? && (irb_path = @irb_context.irb_path) + path = irb_path + end + + if !File.exist?(path) + require_relative "show_source" + + source = + begin + ShowSource.find_source(path, @irb_context) + rescue NameError + # if user enters a path that doesn't exist, it'll cause NameError when passed here because find_source would try to evaluate it as well + # in this case, we should just ignore the error + end + + if source && File.exist?(source.file) + path = source.file + else + puts "Can not find file: #{path}" + return + end + end + + if editor = ENV['EDITOR'] + puts "command: '#{editor}'" + puts " path: #{path}" + system(*Shellwords.split(editor), path) + else + puts "Can not find editor setting: ENV['EDITOR']" + end + end + end + end + + # :startdoc: +end diff --git a/lib/irb/cmd/show_source.rb b/lib/irb/cmd/show_source.rb index 1fcff3e897..03c21b78c7 100644 --- a/lib/irb/cmd/show_source.rb +++ b/lib/irb/cmd/show_source.rb @@ -19,8 +19,50 @@ module IRB end end + def find_source(str, irb_context) + case str + when /\A[A-Z]\w*(::[A-Z]\w*)*\z/ # Const::Name + eval(str, irb_context.workspace.binding) # trigger autoload + base = irb_context.workspace.binding.receiver.yield_self { |r| r.is_a?(Module) ? r : Object } + file, line = base.const_source_location(str) if base.respond_to?(:const_source_location) # Ruby 2.7+ + when /\A(?<owner>[A-Z]\w*(::[A-Z]\w*)*)#(?<method>[^ :.]+)\z/ # Class#method + owner = eval(Regexp.last_match[:owner], irb_context.workspace.binding) + method = Regexp.last_match[:method] + if owner.respond_to?(:instance_method) && owner.instance_methods.include?(method.to_sym) + file, line = owner.instance_method(method).source_location + end + when /\A((?<receiver>.+)(\.|::))?(?<method>[^ :.]+)\z/ # method, receiver.method, receiver::method + receiver = eval(Regexp.last_match[:receiver] || 'self', irb_context.workspace.binding) + method = Regexp.last_match[:method] + file, line = receiver.method(method).source_location if receiver.respond_to?(method) + end + if file && line + Source.new(file: file, first_line: line, last_line: find_end(file, line)) + end + end + private + def find_end(file, first_line) + return first_line unless File.exist?(file) + lex = RubyLex.new + lines = File.read(file).lines[(first_line - 1)..-1] + tokens = RubyLex.ripper_lex_without_warning(lines.join) + prev_tokens = [] + + # chunk with line number + tokens.chunk { |tok| tok.pos[0] }.each do |lnum, chunk| + code = lines[0..lnum].join + prev_tokens.concat chunk + continue = lex.process_continue(prev_tokens) + code_block_open = lex.check_code_block(code, prev_tokens) + if !continue && !code_block_open + return first_line + lnum + end + end + first_line + end + def string_literal?(args) sexp = Ripper.sexp(args) sexp && sexp.size == 2 && sexp.last&.first&.first == :string_literal @@ -32,7 +74,8 @@ module IRB puts "Error: Expected a string but got #{str.inspect}" return end - source = find_source(str) + + source = self.class.find_source(str, @irb_context) if source && File.exist?(source.file) show_source(source) else @@ -53,48 +96,6 @@ module IRB puts end - def find_source(str) - case str - when /\A[A-Z]\w*(::[A-Z]\w*)*\z/ # Const::Name - eval(str, irb_context.workspace.binding) # trigger autoload - base = irb_context.workspace.binding.receiver.yield_self { |r| r.is_a?(Module) ? r : Object } - file, line = base.const_source_location(str) if base.respond_to?(:const_source_location) # Ruby 2.7+ - when /\A(?<owner>[A-Z]\w*(::[A-Z]\w*)*)#(?<method>[^ :.]+)\z/ # Class#method - owner = eval(Regexp.last_match[:owner], irb_context.workspace.binding) - method = Regexp.last_match[:method] - if owner.respond_to?(:instance_method) && owner.instance_methods.include?(method.to_sym) - file, line = owner.instance_method(method).source_location - end - when /\A((?<receiver>.+)(\.|::))?(?<method>[^ :.]+)\z/ # method, receiver.method, receiver::method - receiver = eval(Regexp.last_match[:receiver] || 'self', irb_context.workspace.binding) - method = Regexp.last_match[:method] - file, line = receiver.method(method).source_location if receiver.respond_to?(method) - end - if file && line - Source.new(file: file, first_line: line, last_line: find_end(file, line)) - end - end - - def find_end(file, first_line) - return first_line unless File.exist?(file) - lex = RubyLex.new - lines = File.read(file).lines[(first_line - 1)..-1] - tokens = RubyLex.ripper_lex_without_warning(lines.join) - prev_tokens = [] - - # chunk with line number - tokens.chunk { |tok| tok.pos[0] }.each do |lnum, chunk| - code = lines[0..lnum].join - prev_tokens.concat chunk - continue = lex.process_continue(prev_tokens) - code_block_open = lex.check_code_block(code, prev_tokens) - if !continue && !code_block_open - return first_line + lnum - end - end - first_line - end - def bold(str) Color.colorize(str, [:BOLD]) end diff --git a/lib/irb/extend-command.rb b/lib/irb/extend-command.rb index 94fd9c8bb4..7e120cf510 100644 --- a/lib/irb/extend-command.rb +++ b/lib/irb/extend-command.rb @@ -121,6 +121,10 @@ module IRB # :nodoc: [:debug, NO_OVERRIDE], ], [ + :irb_edit, :Edit, "cmd/edit", + [:edit, NO_OVERRIDE], + ], + [ :irb_help, :Help, "cmd/help", [:help, NO_OVERRIDE], ], |