# frozen_string_literal: true class Pry class Command class Edit < Pry::ClassCommand match 'edit' group 'Editing' description 'Invoke the default editor on a file.' banner <<-'BANNER' Usage: edit [--no-reload|--reload|--patch] [--line LINE] [--temp|--ex|FILE[:LINE]|OBJECT|--in N] Open a text editor. When no FILE is given, edits the pry input buffer. When a method/module/command is given, the code is opened in an editor. Ensure `Pry.config.editor` or `pry_instance.config.editor` is set to your editor of choice. edit sample.rb edit -p MyClass#my_method edit sample.rb --line 105 edit MyClass edit MyClass#my_method edit --ex edit --method edit --ex -p https://github.com/pry/pry/wiki/Editor-integration#wiki-Edit_command BANNER def options(opt) opt.on :e, :ex, "Open the file that raised the most recent exception " \ "(_ex_.file)", optional_argument: true, as: Integer opt.on :i, :in, "Open a temporary file containing the Nth input " \ "expression. N may be a range", optional_argument: true, as: Range, default: -1..-1 opt.on :t, :temp, "Open an empty temporary file" opt.on :l, :line, "Jump to this line in the opened file", argument: true, as: Integer opt.on :n, :"no-reload", "Don't automatically reload the edited file" opt.on :c, :current, "Open the current __FILE__ and at __LINE__ (as " \ "returned by `whereami`)" opt.on :r, :reload, "Reload the edited code immediately (default for " \ "ruby files)" opt.on :p, :patch, "Instead of editing the object's file, try to edit " \ "in a tempfile and apply as a monkey patch" opt.on :m, :method, "Explicitly edit the _current_ method (when " \ "inside a method context)." end def process if bad_option_combination? raise CommandError, "Only one of --ex, --temp, --in, --method and " \ "FILE may be specified." end if repl_edit? # code defined in pry, eval'd within pry. repl_edit elsif runtime_patch? # patch code without persisting changes, implies future changes are patches apply_runtime_patch else # code stored in actual files, eval'd at top-level file_edit end end def repl_edit? !opts.present?(:ex) && !opts.present?(:current) && !opts.present?(:method) && filename_argument.empty? end def repl_edit content = Pry::Editor.new(pry_instance).edit_tempfile_with_content( initial_temp_file_content, initial_temp_file_content.lines.count ) pry_instance.eval_string = content Pry.history.push(content) end def file_based_exception? opts.present?(:ex) && !opts.present?(:patch) end def runtime_patch? !file_based_exception? && (opts.present?(:patch) || previously_patched?(code_object) || pry_method?(code_object)) end def apply_runtime_patch if patch_exception? ExceptionPatcher.new( pry_instance, state, file_and_line_for_current_exception ).perform_patch elsif code_object.is_a?(Pry::Method) code_object.redefine( Pry::Editor.new(pry_instance).edit_tempfile_with_content( code_object.source ) ) else raise NotImplementedError, "Cannot yet patch #{code_object} objects!" end end def ensure_file_name_is_valid(file_name) unless file_name raise CommandError, "Cannot find a valid file for #{filename_argument}" end return unless not_a_real_file?(file_name) raise CommandError, "#{file_name} is not a valid file name, cannot edit!" end def file_and_line_for_current_exception FileAndLineLocator.from_exception(pry_instance.last_exception, opts[:ex].to_i) end def file_and_line file_name, line = if opts.present?(:current) FileAndLineLocator.from_binding(target) elsif opts.present?(:ex) file_and_line_for_current_exception elsif code_object FileAndLineLocator.from_code_object(code_object, filename_argument) else # when file and line are passed as a single arg, e.g my_file.rb:30 FileAndLineLocator.from_filename_argument(filename_argument) end [file_name, opts.present?(:line) ? opts[:l].to_i : line] end def file_edit file_name, line = file_and_line ensure_file_name_is_valid(file_name) Pry::Editor.new(pry_instance).invoke_editor(file_name, line, reload?(file_name)) set_file_and_dir_locals(file_name) return unless reload?(file_name) silence_warnings { load(file_name) } end def filename_argument args.join(' ') end def code_object @code_object ||= !probably_a_file?(filename_argument) && Pry::CodeObject.lookup(filename_argument, pry_instance) end def pry_method?(code_object) code_object.is_a?(Pry::Method) && code_object.pry_method? end def previously_patched?(code_object) code_object.is_a?(Pry::Method) && Pry::Method::Patcher.code_for(code_object.source_location.first) end def patch_exception? opts.present?(:ex) && opts.present?(:patch) end def bad_option_combination? [ opts.present?(:ex), opts.present?(:temp), opts.present?(:in), opts.present?(:method), !filename_argument.empty? ].count(true) > 1 end def input_expression case opts[:i] when Range (pry_instance.input_ring[opts[:i]] || []).join when Integer pry_instance.input_ring[opts[:i]] || "" else raise Pry::CommandError, "Not a valid range: #{opts[:i]}" end end def reloadable? opts.present?(:reload) || opts.present?(:ex) end def never_reload? opts.present?(:'no-reload') || pry_instance.config.disable_auto_reload end def reload?(file_name = "") (reloadable? || file_name.end_with?(".rb")) && !never_reload? end def initial_temp_file_content if opts.present?(:temp) "" elsif opts.present?(:in) input_expression elsif eval_string.strip != "" eval_string else pry_instance.input_ring.to_a.reverse_each.find { |x| x && x.strip != "" } || "" end end def probably_a_file?(str) [".rb", ".c", ".py", ".yml", ".gemspec"].include?(File.extname(str)) || str =~ %r{/|\\} end end Pry::Commands.add_command(Pry::Command::Edit) end end