tty-editor 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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