class TTY::Editor
A class responsible for launching an editor
@api public
Constants
- EXECUTABLES
List possible command line text editors
@return [Array<String>]
@api public
- Error
- InvalidArgumentError
Raised when user provides unnexpected or incorrect argument
- VERSION
Public Class Methods
Find available text editors
@param [Array<String>] commands
the commands to use intstead of defaults
@return [Array<String>]
the existing editor commands
@api public
# File lib/tty/editor.rb, line 86 def self.available(*commands) if commands.any? execs = search_executables(commands.map(&:to_s)) return execs unless execs.empty? end if from_env.any? execs = search_executables(from_env) return execs unless execs.empty? end search_executables(EXECUTABLES) end
Check if editor command exists
@example
exist?("vim") # => true
@example
exist?("/usr/local/bin/vim") # => true
@example
exist?("C:\\Program Files\\Vim\\vim.exe") # => true
@param [String] command
the command to check for the existence
@return [Boolean]
@api private
# File lib/tty/editor.rb, line 54 def self.exist?(command) path = Pathname(command) exts = [""].concat(ENV.fetch("PATHEXT", "").split(::File::PATH_SEPARATOR)) if path.absolute? return exts.any? { |ext| ::File.exist?("#{command}#{ext}") } end ENV.fetch("PATH", "").split(::File::PATH_SEPARATOR).any? do |dir| file = ::File.join(dir, command) exts.any? { |ext| ::File.exist?("#{file}#{ext}") } end end
Check editor from environment variables
@return [Array<String>]
@api public
# File lib/tty/editor.rb, line 73 def self.from_env [ENV["VISUAL"], ENV["EDITOR"]].compact end
Initialize an Editor
@example
TTY::Editor.new(command: "vim")
@param [String] command
the editor command to use, by default auto detects
@param [Hash] env
environment variables to forward to the editor
@param [IO] input
the standard input
@param [IO] output
the standard output
@param [Boolean] raise_on_failure
whether or not raise on command failure, false by default
@param [Boolean] hide_menu
whether or not to hide commands menu, false by default
@param [Boolean] enable_color
disable or force prompt coloring, defaults to nil
@param [Symbol] menu_interrupt
how to handle Ctrl+C key interrupt out of :error, :signal, :exit, :noop
@api public
# File lib/tty/editor.rb, line 162 def initialize(command: nil, raise_on_failure: false, hide_menu: false, prompt: "Select an editor?", env: {}, enable_color: nil, input: $stdin, output: $stdout, menu_interrupt: :error, &block) @env = env @command = nil @input = input @output = output @raise_on_failure = raise_on_failure @enable_color = enable_color @hide_menu = hide_menu @prompt = prompt @menu_interrupt = menu_interrupt block.(self) if block command(*Array(command)) end
Open file in system editor
@example
TTY::Editor.open("/path/to/filename")
@example
TTY::Editor.open("file1", "file2", "file3")
@example
TTY::Editor.open(text: "Some text")
@param [Array<String>] files
the files to open in an editor
@param [String] command
the editor command to use, by default auto detects
@param [String] text
the text to edit in an editor
@param [Hash] env
environment variables to forward to the editor
@return [Object]
@api public
# File lib/tty/editor.rb, line 134 def self.open(*files, text: nil, **options, &block) editor = new(**options, &block) editor.open(*files, text: text) end
Private Class Methods
Search for existing executables
@return [Array<String>]
@api private
# File lib/tty/editor.rb, line 105 def self.search_executables(execs) execs.compact.map(&:strip).reject(&:empty?).uniq .select { |exec| exist?(exec.split.first) } end
Public Instance Methods
Finds command using a configured command(s) or detected shell commands
@example
editor.command("vim")
@param [Array<String>] commands
the optional command to use, by default auto detecting
@raise [TTY::Editor::CommandInvocationError]
@return [String]
@api public
# File lib/tty/editor.rb, line 211 def command(*commands) return @command if @command && commands.empty? execs = self.class.available(*commands) if execs.empty? raise EditorNotFoundError, "could not find a text editor to use. Please specify $VISUAL or "\ "$EDITOR or install one of the following editors: " \ "#{EXECUTABLES.map { |ed| ed.split.first }.join(", ")}." end @command = choose_exec_from(execs) end
Read or update environment vars
@example
editor.env({"FOO" => "bar"})
@param [Hash{String => String}] value
the environment variables to use
@return [Hash]
@api public
# File lib/tty/editor.rb, line 192 def env(value = (not_set = true)) return @env if not_set @env = value end
Run editor command in a shell
@param [Array<String>] files
the files to open in an editor
@param [String] text
the text to edit in an editor
@raise [TTY::Editor::CommandInvocationError]
@return [Boolean]
whether editor command suceeded or not
@api private
# File lib/tty/editor.rb, line 237 def open(*files, text: nil) validate_arguments(files, text) text_written = false filepaths = files.reduce([]) do |paths, filename| if !::File.exist?(filename) ::File.write(filename, text || "") text_written = true end paths + [filename] end if !text.nil? && !text_written tempfile = create_tempfile(text) filepaths << tempfile.path end run(filepaths) ensure tempfile.unlink if tempfile end
Private Instance Methods
Render an editor selection prompt to the terminal
@return [String]
the chosen editor
@api private
# File lib/tty/editor.rb, line 320 def choose_exec_from(execs) if !@hide_menu && execs.size > 1 prompt = TTY::Prompt.new(input: @input, output: @output, env: @env, enable_color: @enable_color, interrupt: @menu_interrupt) exec = prompt.enum_select(@prompt, execs) @output.print(prompt.cursor.up + prompt.cursor.clear_line) exec else execs[0] end end
Create tempfile with text
@param [String] text
@return [Tempfile]
@api private
# File lib/tty/editor.rb, line 306 def create_tempfile(text) tempfile = Tempfile.new("tty-editor") tempfile << text tempfile.flush tempfile.close tempfile end
Run editor command with file arguments
@param [Array<String>] filepaths
the file paths to open in an editor
@return [Boolean]
whether command succeeded or not
@api private
# File lib/tty/editor.rb, line 270 def run(filepaths) command_path = "#{command} #{filepaths.shelljoin}" status = system(env, *Shellwords.split(command_path)) if @raise_on_failure && !status raise CommandInvocationError, "`#{command_path}` failed with status: #{$? ? $?.exitstatus : nil}" end !!status end
Check if filename and text arguments are valid
@raise [InvalidArgumentError]
@return [nil]
@api private
# File lib/tty/editor.rb, line 287 def validate_arguments(files, text) return if files.empty? if files.all? { |file| ::File.exist?(file) } && !text.nil? raise InvalidArgumentError, "cannot give a path to an existing file and text at the same time." elsif filename = files.find { |file| ::File.exist?(file) && !::FileTest.file?(file) } raise InvalidArgumentError, "don't know how to handle `#{filename}`. " \ "Please provide a file path or text" end end