tty-editor 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7d7c645222f24a5cef4cf15e61bf3dff1d2513312cd68e108639b99e32f445e9
4
- data.tar.gz: 857b47f67928ff366acb4064431765e2a3db718a458933ac1d9a9368122a6bdf
3
+ metadata.gz: 43382f86c223e305662a67b003a4337ce5946adaee09dcd48879714a91e45eae
4
+ data.tar.gz: fd58b6084c4cbedd605c09ce30fca817ea7f4d467e32ddfd558e3c7eb972f0a4
5
5
  SHA512:
6
- metadata.gz: 5c8872fa73934f4cb626a8f57fd15e7378c8e77f7bd7138d209f2e3c134922e736f887c6dac4edb90d37b82e2cb1245b8d2d73552b5b138b59356f8a93db7c5d
7
- data.tar.gz: c768f3a8b70beb0058ab6d1ced349a36751cea6a4c43a70713d1baf5f63e350993ab7b1d7fb5e4700a20b0ff7589edbdfaf5c6b7813e89f0817b79ef0157bb54
6
+ metadata.gz: 31385f2ece1195d0c3801e495830c182f6808050c510ae83f3c78f48117a6a6a088ffce3177ea5283507a7e67cc235f6e580261e73ce03d0916044eeaeed0209
7
+ data.tar.gz: ddf5f4e74879e30a641ca4d3c7af06ffb0b7e36f8dd989915d599b283855e1fee77843d4788dd60eb7be93b1ef47b77285abb4a06b60528a36aefae75717dead
@@ -1,5 +1,26 @@
1
1
  # Change log
2
2
 
3
+ ## [v0.6.0] - 2020-09-22
4
+
5
+ ### Added
6
+ * Add ability to edit multiple files
7
+ * Add ability to configure input and output
8
+ * Add :raise_on_failure configuration option to control editor failure to run
9
+ * Add :show_menu configuration option to disable editor menu choice
10
+ * Add :prompt to configure an editor choice menu prompt
11
+
12
+ ### Changed
13
+ * Change Editor#exist? to use direct path env var search
14
+ * Change Editor#new to stop accepting filename and text arguments
15
+ * Change Editor#new to select available text editors
16
+ * Change Editor#open to accept keyword arguments
17
+ * Change to stop raising when editor command cannot be run and return false instead
18
+ * Remove tty-which dependency
19
+ * Update tty-prompt dependency
20
+
21
+ ### Fixed
22
+ * Fix to allow setting editor commands with flags
23
+
3
24
  ## [v0.5.1] - 2019-08-06
4
25
 
5
26
  ### Changed
@@ -63,6 +84,7 @@
63
84
 
64
85
  * Initial implementation and release
65
86
 
87
+ [v0.6.0]: https://github.com/piotrmurach/tty-editor/compare/v0.5.1...v0.6.0
66
88
  [v0.5.1]: https://github.com/piotrmurach/tty-editor/compare/v0.5.0...v0.5.1
67
89
  [v0.5.0]: https://github.com/piotrmurach/tty-editor/compare/v0.4.1...v0.5.0
68
90
  [v0.4.1]: https://github.com/piotrmurach/tty-editor/compare/v0.4.0...v0.4.1
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <div align="center">
2
- <a href="https://piotrmurach.github.io/tty" target="_blank"><img width="130" src="https://cdn.rawgit.com/piotrmurach/tty/master/images/tty.png" alt="tty logo" /></a>
2
+ <a href="https://ttytoolkit.org" target="_blank"><img width="130" src="https://github.com/piotrmurach/tty/raw/master/images/tty.png" alt="TTY Toolkit logo" /></a>
3
3
  </div>
4
4
 
5
5
  # TTY::Editor [![Gitter](https://badges.gitter.im/Join%20Chat.svg)][gitter]
@@ -28,7 +28,7 @@
28
28
  Add this line to your application's Gemfile:
29
29
 
30
30
  ```ruby
31
- gem 'tty-editor'
31
+ gem "tty-editor"
32
32
  ```
33
33
 
34
34
  And then execute:
@@ -39,84 +39,206 @@ Or install it yourself as:
39
39
 
40
40
  $ gem install tty-editor
41
41
 
42
- ## Usage
42
+ ## Contents
43
43
 
44
- To edit a file in default editor:
44
+ * [1. Usage](#1-usage)
45
+ * [2. API](#2-api)
46
+ * [2.1 new](#21-new)
47
+ * [2.1.1 :command](#211-command)
48
+ * [2.1.2 :env](#212-env)
49
+ * [2.1.3 :raise_on_failure](#213-raise_on_failure)
50
+ * [2.1.4 :prompt](#214-prompt)
51
+ * [2.2 open](#22-open)
52
+
53
+ ## 1. Usage
54
+
55
+ To edit a file in a default text editor do:
45
56
 
46
57
  ```ruby
47
- TTY::Editor.open('hello.rb')
58
+ TTY::Editor.open("/path/to/file")
48
59
  ```
49
60
 
50
- To edit content in a default editor:
61
+ To edit text in a default editor:
51
62
 
52
63
  ```ruby
53
- TTY::Editor.open(content: "some text")
64
+ TTY::Editor.open(text: "Some text")
54
65
  ```
55
66
 
56
- You can also set your preferred editor command:
67
+ You can also open multiple existing and/or new files:
57
68
 
58
69
  ```ruby
59
- TTY::Editor.open('hello.rb', command: :vim)
70
+ TTY::Editor.open("file_1", "file_2", "new_file_3")
60
71
  ```
61
72
 
62
- Also, the `VISUAL` or `EDITOR` shell environment variables take precedence when auto detecting available editors.
73
+ Note that the `VISUAL` or `EDITOR` shell environment variables take precedence when auto detecting available editors.
74
+
75
+ You can also set your preferred editor command(s) and ignore `VISUAL` and `EDITOR` as well as other user preferences:
63
76
 
64
- ## Interface
77
+ ```ruby
78
+ TTY::Editor.open("/path/to/file", command: "vim -f")
79
+ ```
65
80
 
66
- ### open
81
+ When `VISUAL` or `EDITOR` are not specified, a selection menu will be presented to the user.
82
+
83
+ For example, if an user has `vim`, `emacs` and `code` editors available on their system, they will see the following menu:
84
+
85
+ ```
86
+ # Select an editor?
87
+ # 1) vim
88
+ # 2) emacs
89
+ # 3) code
90
+ # Choose 1-2 [1]:
91
+ ```
67
92
 
68
- If you wish to open editor with no file or content do:
93
+ You can further customise this behaviour with [:prompt](#214-prompt).
94
+
95
+ ## 2. API
96
+
97
+ ### 2.1 new
98
+
99
+ Instantiation of an editor will trigger automatic search for available command-line editors:
69
100
 
70
101
  ```ruby
71
- TTY::Editor.open
102
+ editor = TTY::Editor.new
103
+ ```
104
+
105
+ You can change default search with the `:command` keyword argument.
106
+
107
+ #### 2.1.1 :command
108
+
109
+ You can force to always use a specific editor by passing `:command` option:
110
+
111
+ ```ruby
112
+ editor = TTY::Editor.new(command: "vim")
72
113
  ```
73
114
 
74
- To open a file at a path pass it as a first argument:
115
+ Or you can specify multiple commands and give a user a choice:
75
116
 
76
117
  ```ruby
77
- TTY::Editor.open('../README.md')
118
+ editor = TTY::Editor.new(command: ["vim", "emacs"])
78
119
  ```
79
120
 
80
- When editor successfully opens file or content then `true` is returned.
121
+ The class-level `open` method accepts the same parameters:
81
122
 
82
- If the editor cannot be opened, a `TTY::Editor::CommandInvocation` error is raised.
123
+ ```ruby
124
+ TTY::Editor.open("/path/to/file", command: "vim")
125
+ ```
83
126
 
84
- In order to open text content inside an editor do:
127
+ #### 2.1.2 :env
128
+
129
+ Use `:env` key to forward environment variables to the text editor launch command:
85
130
 
86
131
  ```ruby
87
- TTY::Editor.open(content: 'text')
132
+ TTY::Editor.new(env: {"FOO" => "bar"})
88
133
  ```
89
134
 
90
- You can also provide filename that will be created with specified content before editor is opened:
135
+ The class-level `open` method accepts the same parameters:
91
136
 
92
137
  ```ruby
93
- TTY::Editor.open('new.rb', content: 'text')
138
+ TTY::Editor.open("/path/to/file", env: {"FOO" => "bar"})
94
139
  ```
95
140
 
96
- If you open a filename with already existing content then new content gets appended at the end of the file.
141
+ #### 2.1.3 :raise_on_failure
97
142
 
98
- ### :env
143
+ By default when editor fails to open a `false` status is returned:
99
144
 
100
- Use `:env` key to forward environment variables to the editor.
145
+ ```ruby
146
+ TTY::Editor.open("/path/to/unknown/file") # => false
147
+ ```
148
+
149
+ Alternatively, you can use `:raise_on_failure` to raise an error on failure to open a file.
150
+
151
+ The `TTY::Editor::CommandInvocationError` will be raised anytime an editor fails to open a file:
101
152
 
102
153
  ```ruby
103
- TTY::Editor.open('hello.rb', env: {"FOO" => "bar"})
154
+ editor = TTY::Editor.new(raise_on_failure: true)
155
+ editor.open("/path/to/unknown/file")
156
+ # => raises TTY::Editor::ComandInvocationError
104
157
  ```
105
158
 
106
- ### :command
159
+ #### 2.1.4 :prompt
107
160
 
108
- You can force to always use a specific editor by passing `:command` option:
161
+ When more than one editor is available and user hasn't specified their preferred choice via `VISUAL` or `EDITOR` variables, a selection menu is presented.
162
+
163
+ For example, when `vim`, `emacs` and `code` executable exists on the system, the following menu will be displayed:
164
+
165
+ ```
166
+ # Select an editor?
167
+ # 1) vim
168
+ # 2) emacs
169
+ # 3) code
170
+ # Choose 1-2 [1]:
171
+ ```
172
+
173
+ If you would like to change the menu prompt use `:prompt` keyword:
109
174
 
110
175
  ```ruby
111
- TTY::Editor.open('hello.rb', command: :vim)
176
+ editor = TTY::Editor.new(prompt: "Which one do you fancy?")
177
+ editor.open("/path/to/file")
112
178
  ```
113
179
 
114
- To specify more than one command, and hence give a user a choice do:
180
+ This may produce the following in the terminal:
181
+
182
+ ```
183
+ # Which one do you fancy?
184
+ # 1) vim
185
+ # 2) emacs
186
+ # 3) code
187
+ # Choose 1-2 [1]:
188
+ ```
189
+
190
+ ### 2.2 open
191
+
192
+ There is a class-level and instance-level `open` method. These are equivalent:
193
+
194
+ ```ruby
195
+ editor = TTY::Editor.new
196
+ editor.open(...)
197
+ # or
198
+ TTY::Editor.open(...)
199
+ ```
200
+
201
+ Creating `TTY::Editor` instance means that the search for a command editor will be performed only once. Then the editor command will be shared between invocations of `open` call.
202
+
203
+ Conversely, the class-level `open` method will search for an editor each time it is invoked.
204
+
205
+ The following examples of using the `open` method apply to both the instance and class level invocations.
206
+
207
+ If you wish to open an editor without giving a file or content do:
208
+
209
+ ```ruby
210
+ TTY::Editor.open
211
+ ```
212
+
213
+ To open a file, pass a path as an argument to `open`:
214
+
215
+ ```ruby
216
+ TTY::Editor.open("../README.md")
217
+ # => true
218
+ ```
219
+
220
+ When editor successfully opens a file or content then `true` is returned, `false` otherwise.
221
+
222
+ You can change this with `:raise_on_failure` keyword to raise a `TTY::Editor::CommandInvocation` error when an editor cannot be opened.
223
+
224
+ In order to open text content inside an editor use `:text` keyword like so:
225
+
226
+ ```ruby
227
+ TTY::Editor.open(text: "Some text")
228
+ ```
229
+
230
+ You can also provide filename that will be created with specified content before editor is opened:
231
+
232
+ ```ruby
233
+ TTY::Editor.open("/path/to/new-file", text: "Some text")
234
+ ```
235
+
236
+ If you open a filename with already existing content then the new content will be appended at the end of the file.
237
+
238
+ You can also open multiple existing and non-existing files providing them as consecutive arguments:
115
239
 
116
240
  ```ruby
117
- TTY::Editor.open('hello.rb') do |editor|
118
- editor.command :vim, :emacs
119
- end
241
+ TTY::Editor.open("file_1", "file_2", "new_file_3")
120
242
  ```
121
243
 
122
244
  ## Development
@@ -1 +1 @@
1
- require_relative 'tty/editor'
1
+ require_relative "tty/editor"
@@ -1,40 +1,53 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'tty-prompt'
4
- require 'tty-which'
5
- require 'tempfile'
6
- require 'fileutils'
7
- require 'shellwords'
3
+ require "fileutils"
4
+ require "shellwords"
5
+ require "tempfile"
6
+ require "tty-prompt"
8
7
 
9
- require_relative 'editor/version'
8
+ require_relative "editor/version"
10
9
 
11
10
  module TTY
12
11
  # A class responsible for launching an editor
13
12
  #
14
13
  # @api public
15
14
  class Editor
15
+ Error = Class.new(StandardError)
16
+
17
+ # Raised when user provides unnexpected or incorrect argument
18
+ InvalidArgumentError = Class.new(Error)
19
+
16
20
  # Raised when command cannot be invoked
17
21
  class CommandInvocationError < RuntimeError; end
18
22
 
19
23
  # Raised when editor cannot be found
20
24
  class EditorNotFoundError < RuntimeError; end
21
25
 
22
- # Check if editor exists
26
+ # List possible command line text editors
23
27
  #
24
- # @return [Boolean]
28
+ # @return [Array[String]]
25
29
  #
26
- # @api private
27
- def self.exist?(cmd)
28
- TTY::Which.exist?(cmd)
29
- end
30
+ # @api public
31
+ EXECUTABLES = [
32
+ "nano -w", "notepad", "vim", "vi", "emacs",
33
+ "code", "subl -n -w", "mate -w", "atom",
34
+ "pico", "qe", "mg", "jed"
35
+ ].freeze
30
36
 
31
- # Check if Windowz
37
+ # Check if editor command exists
38
+ #
39
+ # @example
40
+ # exist?("vim") # => true
32
41
  #
33
42
  # @return [Boolean]
34
43
  #
35
- # @api public
36
- def self.windows?
37
- ::File::ALT_SEPARATOR == "\\"
44
+ # @api private
45
+ def self.exist?(command)
46
+ exts = ENV.fetch("PATHEXT", "").split(::File::PATH_SEPARATOR)
47
+ ENV.fetch("PATH", "").split(::File::PATH_SEPARATOR).any? do |dir|
48
+ file = ::File.join(dir, command)
49
+ ::File.exist?(file) || exts.any? { |ext| ::File.exist?("#{file}#{ext}") }
50
+ end
38
51
  end
39
52
 
40
53
  # Check editor from environment variables
@@ -43,90 +56,98 @@ module TTY
43
56
  #
44
57
  # @api public
45
58
  def self.from_env
46
- [ENV['VISUAL'], ENV['EDITOR']].compact
47
- end
48
-
49
- # List possible executable for editor command
50
- #
51
- # @return [Array[String]]
52
- #
53
- # @api public
54
- def self.executables
55
- ['vim', 'vi', 'emacs', 'nano', 'nano-tiny', 'pico', 'mate -w']
59
+ [ENV["VISUAL"], ENV["EDITOR"]].compact
56
60
  end
57
61
 
58
- # Find available command
62
+ # Find available text editors
59
63
  #
60
64
  # @param [Array[String]] commands
61
65
  # the commands to use intstead of defaults
62
66
  #
63
67
  # @return [Array[String]]
68
+ # the existing editor commands
64
69
  #
65
70
  # @api public
66
71
  def self.available(*commands)
67
- return commands unless commands.empty?
68
-
69
- if !from_env.all?(&:empty?)
70
- [from_env.find { |e| !e.empty? }]
71
- elsif windows?
72
- ['notepad']
73
- else
74
- executables.uniq.select(&method(:exist?))
75
- end
72
+ execs = if !commands.empty?
73
+ commands.map(&:to_s)
74
+ elsif from_env.any?
75
+ [from_env.first]
76
+ else
77
+ EXECUTABLES
78
+ end
79
+ execs.compact.map(&:strip).reject(&:empty?).uniq
80
+ .select { |exec| exist?(exec.split.first) }
76
81
  end
77
82
 
78
83
  # Open file in system editor
79
84
  #
80
85
  # @example
81
- # TTY::Editor.open('filename.rb')
86
+ # TTY::Editor.open("/path/to/filename")
82
87
  #
83
- # @param [String] file
84
- # the name of the file
88
+ # @example
89
+ # TTY::Editor.open("file1", "file2", "file3")
90
+ #
91
+ # @example
92
+ # TTY::Editor.open(text: "Some text")
93
+ #
94
+ # @param [Array<String>] files
95
+ # the files to open in an editor
96
+ # @param [String] :command
97
+ # the editor command to use, by default auto detects
98
+ # @param [String] :text
99
+ # the text to edit in an editor
100
+ # @param [Hash] :env
101
+ # environment variables to forward to the editor
85
102
  #
86
103
  # @return [Object]
87
104
  #
88
105
  # @api public
89
- def self.open(*args)
90
- editor = new(*args)
91
-
92
- yield(editor) if block_given?
93
-
94
- editor.open
106
+ def self.open(*files, text: nil, **options, &block)
107
+ editor = new(**options, &block)
108
+ editor.open(*files, text: text)
95
109
  end
96
110
 
97
111
  # Initialize an Editor
98
112
  #
99
- # @param [String] file
100
- # @param [Hash[Symbol]] options
101
- # @option options [Hash] :command
113
+ # @param [String] :command
102
114
  # the editor command to use, by default auto detects
103
- # @option options [Hash] :env
115
+ # @param [Hash] :env
104
116
  # environment variables to forward to the editor
117
+ # @param [IO] :input
118
+ # the standard input
119
+ # @param [IO] :output
120
+ # the standard output
121
+ # @param [Boolean] :raise_on_failure
122
+ # whether or not raise on command failure, false by default
123
+ # @param [Boolean] :show_menu
124
+ # whether or not show commands menu, true by default
105
125
  #
106
126
  # @api public
107
- def initialize(*args, **options)
108
- @filename = args.unshift.first
109
- @env = options.fetch(:env) { {} }
110
- @command = options[:command]
111
- if @filename
112
- if ::File.exist?(@filename) && !::FileTest.file?(@filename)
113
- raise ArgumentError, "Don't know how to handle `#{@filename}`. " \
114
- "Please provida a file path or content"
115
- elsif ::File.exist?(@filename) && !options[:content].to_s.empty?
116
- ::File.open(@filename, 'a') { |f| f.write(options[:content]) }
117
- elsif !::File.exist?(@filename)
118
- ::File.write(@filename, options[:content])
119
- end
120
- elsif options[:content]
121
- @filename = tempfile_path(options[:content])
122
- end
127
+ def initialize(command: nil, raise_on_failure: false, show_menu: true,
128
+ prompt: "Select an editor?", env: {},
129
+ input: $stdin, output: $stdout, &block)
130
+ @env = env
131
+ @command = nil
132
+ @input = input
133
+ @output = output
134
+ @raise_on_failure = raise_on_failure
135
+ @show_menu = show_menu
136
+ @prompt = prompt
137
+
138
+ block.(self) if block
139
+
140
+ command(*Array(command))
123
141
  end
124
142
 
125
143
  # Read or update environment vars
126
144
  #
145
+ # @return [Hash]
146
+ #
127
147
  # @api public
128
148
  def env(value = (not_set = true))
129
149
  return @env if not_set
150
+
130
151
  @env = value
131
152
  end
132
153
 
@@ -146,64 +167,116 @@ module TTY
146
167
  execs = self.class.available(*commands)
147
168
  if execs.empty?
148
169
  raise EditorNotFoundError,
149
- 'Could not find editor to use. Please specify $VISUAL or $EDITOR'
170
+ "could not find a text editor to use. Please specify $VISUAL or "\
171
+ "$EDITOR or install one of the following editors: " \
172
+ "#{EXECUTABLES.map { |ed| ed.split.first }.join(", ")}."
150
173
  end
151
- exec = choose_exec_from(execs)
152
- @command = TTY::Which.which(exec.to_s)
174
+ @command = choose_exec_from(execs)
153
175
  end
154
176
 
177
+ # Run editor command in a shell
178
+ #
179
+ # @param [Array<String>] files
180
+ # the files to open in an editor
181
+ # @param [String] :text
182
+ # the text to edit in an editor
183
+ #
184
+ # @raise [TTY::CommandInvocationError]
185
+ #
186
+ # @return [Boolean]
187
+ # whether editor command suceeded or not
188
+ #
155
189
  # @api private
156
- def choose_exec_from(execs)
157
- if execs.size > 1
158
- prompt = TTY::Prompt.new
159
- prompt.enum_select('Select an editor?', execs)
160
- else
161
- execs[0]
190
+ def open(*files, text: nil)
191
+ validate_arguments(files, text)
192
+ text_written = false
193
+
194
+ filepaths = files.reduce([]) do |paths, filename|
195
+ if !::File.exist?(filename)
196
+ ::File.write(filename, text || "")
197
+ text_written = true
198
+ end
199
+ paths + [filename]
200
+ end
201
+
202
+ if !text.nil? && !text_written
203
+ tempfile = create_tempfile(text)
204
+ filepaths << tempfile.path
162
205
  end
206
+
207
+ run(filepaths)
208
+ ensure
209
+ tempfile.unlink if tempfile
163
210
  end
164
211
 
165
- # Escape file path
212
+ private
213
+
214
+ # Run editor command with file arguments
215
+ #
216
+ # @param [Array<String>] filepaths
217
+ # the file paths to open in an editor
218
+ #
219
+ # @return [Boolean]
220
+ # whether command succeeded or not
166
221
  #
167
222
  # @api private
168
- def escape_file
169
- Shellwords.shellescape(@filename)
223
+ def run(filepaths)
224
+ command_path = "#{command} #{filepaths.shelljoin}"
225
+ status = system(env, *Shellwords.split(command_path))
226
+ if @raise_on_failure && !status
227
+ raise CommandInvocationError,
228
+ "`#{command_path}` failed with status: #{$? ? $?.exitstatus : nil}"
229
+ end
230
+ !!status
170
231
  end
171
232
 
172
- # Build command path to invoke
233
+ # Check if filename and text arguments are valid
173
234
  #
174
- # @return [String]
235
+ # @raise [InvalidArgumentError]
175
236
  #
176
237
  # @api private
177
- def command_path
178
- "#{command} #{escape_file}"
238
+ def validate_arguments(files, text)
239
+ return if files.empty?
240
+
241
+ if files.all? { |file| ::File.exist?(file) } && !text.nil?
242
+ raise InvalidArgumentError,
243
+ "cannot give a path to an existing file and text at the same time."
244
+ elsif filename = files.find { |file| ::File.exist?(file) && !::FileTest.file?(file) }
245
+ raise InvalidArgumentError, "don't know how to handle `#{filename}`. " \
246
+ "Please provide a file path or text"
247
+ end
179
248
  end
180
249
 
181
- # Create tempfile with content
250
+ # Create tempfile with text
182
251
  #
183
- # @param [String] content
252
+ # @param [String] text
253
+ #
254
+ # @return [Tempfile]
184
255
  #
185
- # @return [String]
186
256
  # @api private
187
- def tempfile_path(content)
188
- tempfile = Tempfile.new('tty-editor')
189
- tempfile << content
257
+ def create_tempfile(text)
258
+ tempfile = Tempfile.new("tty-editor")
259
+ tempfile << text
190
260
  tempfile.flush
191
- unless tempfile.nil?
192
- tempfile.close
193
- end
194
- tempfile.path
261
+ tempfile.close
262
+ tempfile
195
263
  end
196
264
 
197
- # Inovke editor command in a shell
265
+ # Render an editor selection prompt to the terminal
198
266
  #
199
- # @raise [TTY::CommandInvocationError]
267
+ # @return [String]
268
+ # the chosen editor
200
269
  #
201
270
  # @api private
202
- def open
203
- status = system(env, *Shellwords.split(command_path))
204
- return status if status
205
- fail CommandInvocationError,
206
- "`#{command_path}` failed with status: #{$? ? $?.exitstatus : nil}"
271
+ def choose_exec_from(execs)
272
+ if @show_menu && execs.size > 1
273
+ prompt = TTY::Prompt.new(input: @input, output: @output, env: @env)
274
+ exec = prompt.enum_select(@prompt, execs)
275
+ @output.print(prompt.cursor.up + prompt.cursor.clear_line)
276
+ exec
277
+ else
278
+ execs[0]
279
+ end
207
280
  end
208
281
  end # Editor
209
282
  end # TTY