yap-shell 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/addons/history/README.md +16 -0
- data/addons/history/history.rb +31 -77
- data/addons/keyboard_macros/keyboard_macros.rb +115 -43
- data/addons/keyboard_macros/lib/keyboard_macros/cycle.rb +38 -0
- data/addons/prompt_updates/prompt_updates.rb +1 -1
- data/addons/tab_completion/lib/tab_completion/basic_completion.rb +151 -0
- data/addons/tab_completion/tab_completion.rb +11 -7
- data/bin/yap +1 -12
- data/bin/yap-dev +12 -0
- data/lib/yap/configuration.rb +33 -0
- data/lib/yap/shell/aliases.rb +5 -1
- data/lib/yap/shell/builtins/alias.rb +2 -1
- data/lib/yap/shell/builtins.rb +2 -2
- data/lib/yap/shell/commands.rb +4 -4
- data/lib/yap/shell/evaluation.rb +22 -11
- data/lib/yap/shell/execution/builtin_command_execution.rb +2 -2
- data/lib/yap/shell/execution/file_system_command_execution.rb +2 -1
- data/lib/yap/shell/execution.rb +8 -8
- data/lib/yap/shell/repl.rb +13 -2
- data/lib/yap/shell/version.rb +1 -1
- data/lib/yap/shell.rb +3 -2
- data/lib/yap/world.rb +30 -5
- data/lib/yap.rb +9 -7
- data/rcfiles/.yaprc +42 -10
- data/yap-shell.gemspec +44 -4
- metadata +27 -51
- data/addons/history/Gemfile +0 -2
- data/addons/history/lib/history/buffer.rb +0 -204
- data/addons/history/lib/history/events.rb +0 -13
- data/addons/tab_completion/lib/tab_completion/file_completion.rb +0 -75
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 70c4b7a17173c5d2f16dd2d318ad7fca64121c2b
|
4
|
+
data.tar.gz: f17c759ac10afbb5ed4d9bac3c47cdfc2e8590f8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e998bdac5eccd88ff29438bed0dc970371453a11e300049988b328174940a179c94d3f3ce90e061500ff3c87022517baa7380ae7d88d41304c31c234d092fea9
|
7
|
+
data.tar.gz: 6c7b6164c54f5dc7a3a1cca6a2a78d15dd7a31af58782cb7a7f4b0ff57cce86d3893aa920d668d3f054602a48e80be5b7ef331ca1720af0ca637d766c2ee3e69
|
data/Gemfile
CHANGED
@@ -6,4 +6,4 @@ gemspec
|
|
6
6
|
# gem 'yap-shell-parser', git: 'git@github.com:zdennis/yap-shell-parser.git'
|
7
7
|
# gem "rawline", git: 'git@github.com:zdennis/rawline.git', branch: 'issues/input-issues'
|
8
8
|
# gem "rawline", path: '/Users/zdennis/source/opensource_projects/rawline'
|
9
|
-
|
9
|
+
gem "pry-byebug"
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# History Addon
|
2
|
+
|
3
|
+
The History addon is provides primitive history capabilities:
|
4
|
+
|
5
|
+
* Loads history from ~/.yap/history when yap is loaded
|
6
|
+
* Saves history to ~/.yap/history when yap exits
|
7
|
+
* Saving is an append-only operation
|
8
|
+
* ~/.yap/history is in YAML format
|
9
|
+
|
10
|
+
## Shell Functions
|
11
|
+
|
12
|
+
The history addon provides the `history` shell function that prints the contents of the history.
|
13
|
+
|
14
|
+
## Limitations
|
15
|
+
|
16
|
+
The history addon currently does not support any kind of advanced history operations.
|
data/addons/history/history.rb
CHANGED
@@ -1,101 +1,55 @@
|
|
1
|
-
require 'forwardable'
|
2
|
-
require 'term/ansicolor'
|
3
|
-
require 'ostruct'
|
4
|
-
|
5
1
|
class History < Addon
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
Color = Object.extend Term::ANSIColor
|
10
|
-
|
11
|
-
class << self
|
12
|
-
attr_accessor :history_item_formatter, :ignore_history_item
|
13
|
-
end
|
14
|
-
|
15
|
-
self.ignore_history_item = ->(item:) do
|
16
|
-
item.command == "exit"
|
17
|
-
end
|
18
|
-
|
19
|
-
self.history_item_formatter = ->(item:, options:{}) do
|
20
|
-
if item.duration
|
21
|
-
sprintf(
|
22
|
-
"%#{options[:max_position_width]}d %-s %s",
|
23
|
-
item.position,
|
24
|
-
item.command,
|
25
|
-
Color.negative(Color.intense_black(item.duration))
|
26
|
-
)
|
27
|
-
else
|
28
|
-
sprintf("%#{options[:max_position_width]}d %-s", item.position, item.command)
|
29
|
-
end
|
30
|
-
end
|
2
|
+
attr_reader :file
|
3
|
+
attr_reader :position
|
31
4
|
|
32
5
|
def initialize_world(world)
|
33
6
|
@world = world
|
34
|
-
load_history
|
35
|
-
|
36
|
-
world.editor.bind(:ctrl_h) { show_history(@world.editor) }
|
37
7
|
|
38
|
-
world.
|
39
|
-
|
40
|
-
case first_arg
|
41
|
-
when String
|
42
|
-
if first_arg.start_with?("/")
|
43
|
-
regex = first_arg.gsub(/(^\|\/$)/, '')
|
44
|
-
ignore_history_item = ->(item:, options:{}) do
|
45
|
-
item.command !~ /#{regex}/
|
46
|
-
end
|
47
|
-
else
|
48
|
-
ignore_history_item = ->(item:, options:{}) do
|
49
|
-
item.command !~ /#{first_arg}/
|
50
|
-
end
|
51
|
-
end
|
52
|
-
show_history(world.editor, redraw_prompt:false, ignore_history_item:ignore_history_item)
|
53
|
-
else
|
54
|
-
show_history(world.editor, redraw_prompt:false)
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
8
|
+
@file = world.configuration.path_for('history')
|
9
|
+
@position = 0
|
58
10
|
|
59
|
-
|
60
|
-
editor.puts @history
|
61
|
-
end
|
11
|
+
load_history
|
62
12
|
|
63
|
-
|
64
|
-
|
65
|
-
|
13
|
+
world.func(:history) do |args:, stdin:, stdout:, stderr:|
|
14
|
+
history_length = @world.editor.history.length
|
15
|
+
first_arg = args.first.to_i
|
16
|
+
size = first_arg > 0 ? first_arg + 1 : history_length
|
66
17
|
|
67
|
-
|
68
|
-
|
18
|
+
# start from -2 since we don't want to include the current history
|
19
|
+
# command being run.
|
20
|
+
stdout.puts @world.editor.history[history_length-size..-2]
|
21
|
+
end
|
69
22
|
end
|
70
23
|
|
71
24
|
def save
|
72
|
-
File.open(
|
25
|
+
File.open(file, "a") do |file|
|
73
26
|
# Don't write the YAML header because we're going to append to the
|
74
27
|
# history file, not overwrite. YAML works fine without it.
|
75
|
-
|
28
|
+
unwritten_history = @world.editor.history.to_a[@position..-1]
|
29
|
+
if unwritten_history.any?
|
30
|
+
contents = unwritten_history
|
31
|
+
.each_with_object([]) { |line, arr| arr << line unless line == arr.last }
|
32
|
+
.map { |str| str.respond_to?(:without_ansi) ? str.without_ansi : str }
|
33
|
+
.to_yaml
|
34
|
+
.gsub(/^---.*?^/m, '')
|
35
|
+
file.write contents
|
36
|
+
end
|
76
37
|
end
|
77
38
|
end
|
78
39
|
|
79
40
|
private
|
80
41
|
|
81
|
-
def history
|
82
|
-
@history
|
83
|
-
end
|
84
|
-
|
85
|
-
def history_file
|
86
|
-
@history_file ||= File.expand_path('~') + '/.yap-history'
|
87
|
-
end
|
88
|
-
|
89
42
|
def load_history
|
90
|
-
@world.editor.history = @history = History::Buffer.new(Float::INFINITY)
|
91
|
-
@history_start_position = 0
|
92
|
-
|
93
43
|
at_exit { save }
|
94
44
|
|
95
|
-
|
45
|
+
if File.exists?(file) && File.readable?(file)
|
46
|
+
history = YAML.load_file(file) || []
|
47
|
+
|
48
|
+
# History starts at the end of the history loaded from file.
|
49
|
+
@position = history.length
|
96
50
|
|
97
|
-
|
98
|
-
|
99
|
-
|
51
|
+
# Rely on the builtin history for now.
|
52
|
+
@world.editor.history.replace(history)
|
53
|
+
end
|
100
54
|
end
|
101
55
|
end
|
@@ -1,4 +1,6 @@
|
|
1
1
|
class KeyboardMacros < Addon
|
2
|
+
require 'keyboard_macros/cycle'
|
3
|
+
|
2
4
|
DEFAULT_TRIGGER_KEY = :ctrl_g
|
3
5
|
DEFAULT_CANCEL_KEY = " "
|
4
6
|
DEFAULT_TIMEOUT_IN_MS = 500
|
@@ -35,7 +37,8 @@ class KeyboardMacros < Addon
|
|
35
37
|
configuration = Configuration.new(
|
36
38
|
keymap: world.editor.terminal.keys,
|
37
39
|
trigger_key: trigger_key,
|
38
|
-
cancellation: Cancellation.new(cancel_key: cancel_key, &cancel_blk)
|
40
|
+
cancellation: Cancellation.new(cancel_key: cancel_key, &cancel_blk),
|
41
|
+
editor: world.editor,
|
39
42
|
)
|
40
43
|
|
41
44
|
blk.call configuration if blk
|
@@ -43,7 +46,8 @@ class KeyboardMacros < Addon
|
|
43
46
|
world.unbind(trigger_key)
|
44
47
|
world.bind(trigger_key) do
|
45
48
|
begin
|
46
|
-
@
|
49
|
+
@previous_result = nil
|
50
|
+
@stack << OpenStruct.new(configuration: configuration)
|
47
51
|
configuration.start.call if configuration.start
|
48
52
|
world.editor.keyboard_input_processors.push(self)
|
49
53
|
world.editor.input.wait_timeout_in_seconds = 0.1
|
@@ -55,28 +59,47 @@ class KeyboardMacros < Addon
|
|
55
59
|
@configurations << configuration
|
56
60
|
end
|
57
61
|
|
62
|
+
def cycle(name, &cycle_thru_blk)
|
63
|
+
@cycles ||= {}
|
64
|
+
if block_given?
|
65
|
+
cycle = KeyboardMacros::Cycle.new(
|
66
|
+
cycle_proc: cycle_thru_blk,
|
67
|
+
on_cycle_proc: -> (old_value, new_value) {
|
68
|
+
@world.editor.delete_n_characters(old_value.to_s.length)
|
69
|
+
process_result(new_value)
|
70
|
+
}
|
71
|
+
)
|
72
|
+
@cycles[name] = cycle
|
73
|
+
else
|
74
|
+
@cycles.fetch(name)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
58
78
|
#
|
59
79
|
# InputProcessor Methods
|
60
80
|
#
|
61
81
|
|
62
82
|
def read_bytes(bytes)
|
63
|
-
|
64
|
-
|
83
|
+
if @stack.last
|
84
|
+
current_definition = @stack.last
|
85
|
+
configuration = current_definition.configuration
|
86
|
+
end
|
87
|
+
|
88
|
+
bytes.each_with_index do |byte, i|
|
65
89
|
definition = configuration[byte]
|
66
90
|
if !definition
|
67
91
|
cancel_processing if cancel_on_unknown_sequences
|
68
92
|
break
|
69
93
|
end
|
94
|
+
|
70
95
|
configuration = definition.configuration
|
71
|
-
if configuration
|
72
|
-
|
73
|
-
@stack << configuration
|
74
|
-
end
|
96
|
+
configuration.start.call if configuration.start
|
97
|
+
@stack << definition
|
75
98
|
|
76
99
|
result = definition.process
|
77
100
|
|
78
101
|
if result =~ /\n$/
|
79
|
-
world.editor.write result.chomp
|
102
|
+
world.editor.write result.chomp, add_to_line_history: false
|
80
103
|
world.editor.event_loop.clear @event_id if @event_id
|
81
104
|
cancel_processing
|
82
105
|
world.editor.newline # add_to_history
|
@@ -84,18 +107,31 @@ class KeyboardMacros < Addon
|
|
84
107
|
break
|
85
108
|
end
|
86
109
|
|
87
|
-
|
110
|
+
if i == bytes.length - 1
|
111
|
+
while @stack.last && @stack.last.fragment?
|
112
|
+
@stack.pop
|
113
|
+
end
|
114
|
+
end
|
88
115
|
|
89
116
|
if @event_id
|
90
117
|
world.editor.event_loop.clear @event_id
|
91
118
|
@event_id = queue_up_remove_input_processor
|
92
119
|
end
|
93
|
-
|
120
|
+
|
121
|
+
process_result(result)
|
94
122
|
end
|
95
123
|
end
|
96
124
|
|
97
125
|
private
|
98
126
|
|
127
|
+
def process_result(result)
|
128
|
+
if result.is_a?(String)
|
129
|
+
$z.puts "WRITING: #{result}"
|
130
|
+
@world.editor.write result, add_to_line_history: false
|
131
|
+
@previous_result = result
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
99
135
|
def queue_up_remove_input_processor(&blk)
|
100
136
|
return unless @timeout_in_ms
|
101
137
|
|
@@ -111,8 +147,8 @@ class KeyboardMacros < Addon
|
|
111
147
|
|
112
148
|
def cancel_processing
|
113
149
|
@event_id = nil
|
114
|
-
@stack.reverse.each do |
|
115
|
-
configuration.stop.call if configuration.stop
|
150
|
+
@stack.reverse.each do |definition|
|
151
|
+
definition.configuration.stop.call if definition.configuration.stop
|
116
152
|
end
|
117
153
|
@stack.clear
|
118
154
|
if world.editor.keyboard_input_processors.last == self
|
@@ -137,13 +173,15 @@ class KeyboardMacros < Addon
|
|
137
173
|
class Configuration
|
138
174
|
attr_reader :cancellation, :trigger_key, :keymap
|
139
175
|
|
140
|
-
def initialize(cancellation: nil, keymap: {}, trigger_key: nil)
|
176
|
+
def initialize(cancellation: nil, editor:, keymap: {}, trigger_key: nil)
|
141
177
|
@cancellation = cancellation
|
178
|
+
@editor = editor
|
142
179
|
@keymap = keymap
|
143
180
|
@trigger_key = trigger_key
|
144
181
|
@storage = {}
|
145
182
|
@on_start_blk = nil
|
146
183
|
@on_stop_blk = nil
|
184
|
+
@cycles = {}
|
147
185
|
|
148
186
|
if @cancellation
|
149
187
|
define @cancellation.cancel_key, -> { @cancellation.call }
|
@@ -160,13 +198,25 @@ class KeyboardMacros < Addon
|
|
160
198
|
@on_stop_blk
|
161
199
|
end
|
162
200
|
|
163
|
-
def
|
164
|
-
|
165
|
-
|
201
|
+
def cycle(name, &cycle_thru_blk)
|
202
|
+
if block_given?
|
203
|
+
cycle = KeyboardMacros::Cycle.new(
|
204
|
+
cycle_proc: cycle_thru_blk,
|
205
|
+
on_cycle_proc: -> (old_value, new_value) {
|
206
|
+
@editor.delete_n_characters(old_value.to_s.length)
|
207
|
+
}
|
208
|
+
)
|
209
|
+
@cycles[name] = cycle
|
210
|
+
else
|
211
|
+
@cycles.fetch(name)
|
166
212
|
end
|
167
213
|
end
|
168
214
|
|
169
|
-
def
|
215
|
+
def fragment(sequence, result)
|
216
|
+
define(sequence, result, fragment: true)
|
217
|
+
end
|
218
|
+
|
219
|
+
def define(sequence, result=nil, fragment: false, &blk)
|
170
220
|
unless result.respond_to?(:call)
|
171
221
|
string_result = result
|
172
222
|
result = -> { string_result }
|
@@ -178,6 +228,7 @@ class KeyboardMacros < Addon
|
|
178
228
|
self,
|
179
229
|
sequence.bytes,
|
180
230
|
result,
|
231
|
+
fragment: fragment,
|
181
232
|
&blk
|
182
233
|
)
|
183
234
|
when Symbol
|
@@ -187,10 +238,11 @@ class KeyboardMacros < Addon
|
|
187
238
|
fail "Cannot bind unknown sequence #{sequence.inspect}"
|
188
239
|
},
|
189
240
|
result,
|
241
|
+
fragment: fragment,
|
190
242
|
&blk
|
191
243
|
)
|
192
244
|
when Regexp
|
193
|
-
define_sequence_for_regex(sequence, result, &blk)
|
245
|
+
define_sequence_for_regex(sequence, result, fragment: fragment, &blk)
|
194
246
|
else
|
195
247
|
raise NotImplementedError, <<-EOT.gsub(/^\s*/, '')
|
196
248
|
Don't know how to define macro for sequence: #{sequence.inspect}
|
@@ -206,67 +258,87 @@ class KeyboardMacros < Addon
|
|
206
258
|
@storage[key] = definition
|
207
259
|
end
|
208
260
|
|
261
|
+
def inspect
|
262
|
+
str = @storage.map{ |k,v| "#{k}=#{v.inspect}" }.join("\n ")
|
263
|
+
num_items = @storage.reduce(0) { |s, arr| s + arr.length }
|
264
|
+
"<Configuration num_items=#{num_items} stored_keys=#{str}>"
|
265
|
+
end
|
266
|
+
|
209
267
|
private
|
210
268
|
|
211
|
-
def define_sequence_for_regex(regex, result, &blk)
|
269
|
+
def define_sequence_for_regex(regex, result, fragment: false, &blk)
|
212
270
|
@storage[regex] = Definition.new(
|
213
271
|
configuration: Configuration.new(
|
214
272
|
cancellation: @cancellation,
|
215
|
-
keymap: @keymap
|
273
|
+
keymap: @keymap,
|
274
|
+
editor: @editor
|
216
275
|
),
|
276
|
+
fragment: fragment,
|
217
277
|
sequence: regex,
|
218
278
|
result: result,
|
219
279
|
&blk
|
220
280
|
)
|
221
281
|
end
|
222
282
|
|
223
|
-
def recursively_define_sequence_for_bytes(configuration, bytes, result, &blk)
|
283
|
+
def recursively_define_sequence_for_bytes(configuration, bytes, result, fragment: false, &blk)
|
224
284
|
byte, rest = bytes[0], bytes[1..-1]
|
225
285
|
if rest.any?
|
226
|
-
definition =
|
227
|
-
configuration
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
286
|
+
definition = if configuration[byte]
|
287
|
+
configuration[byte]
|
288
|
+
else
|
289
|
+
Definition.new(
|
290
|
+
configuration: Configuration.new(
|
291
|
+
cancellation: @cancellation,
|
292
|
+
keymap: @keymap,
|
293
|
+
editor: @editor
|
294
|
+
),
|
295
|
+
fragment: fragment,
|
296
|
+
sequence: byte,
|
297
|
+
result: nil
|
298
|
+
)
|
299
|
+
end
|
300
|
+
blk.call(definition.configuration) if blk
|
235
301
|
configuration[byte] = definition
|
236
302
|
recursively_define_sequence_for_bytes(
|
237
303
|
definition.configuration,
|
238
304
|
rest,
|
239
305
|
result,
|
306
|
+
fragment: fragment,
|
240
307
|
&blk
|
241
308
|
)
|
242
309
|
else
|
243
|
-
|
244
|
-
configuration: Configuration.new(
|
310
|
+
definition = Definition.new(
|
311
|
+
configuration: Configuration.new(
|
312
|
+
keymap: @keymap,
|
313
|
+
editor: @editor
|
314
|
+
),
|
315
|
+
fragment: fragment,
|
245
316
|
sequence: byte,
|
246
|
-
result: result
|
247
|
-
&blk
|
317
|
+
result: result
|
248
318
|
)
|
319
|
+
configuration[byte] = definition
|
320
|
+
blk.call(definition.configuration) if blk
|
321
|
+
definition
|
249
322
|
end
|
250
323
|
end
|
251
324
|
end
|
252
325
|
|
253
326
|
class Definition
|
254
|
-
attr_reader :
|
327
|
+
attr_reader :configuration, :result, :sequence
|
255
328
|
|
256
|
-
def initialize(configuration: nil, sequence:, result: nil
|
257
|
-
@fragment =
|
329
|
+
def initialize(configuration: nil, fragment: false, sequence:, result: nil)
|
330
|
+
@fragment = fragment
|
258
331
|
@configuration = configuration
|
259
332
|
@sequence = sequence
|
260
333
|
@result = result
|
261
|
-
blk.call(@configuration) if blk
|
262
334
|
end
|
263
335
|
|
264
|
-
def
|
265
|
-
@fragment
|
336
|
+
def inspect
|
337
|
+
"<Definition fragment=#{@fragment.inspect} sequence=#{@sequence.inspect} result=#{@result.inspect} configuration=#{@configuration.inspect}>"
|
266
338
|
end
|
267
339
|
|
268
|
-
def fragment
|
269
|
-
@fragment
|
340
|
+
def fragment?
|
341
|
+
@fragment
|
270
342
|
end
|
271
343
|
|
272
344
|
def matches?(byte)
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class KeyboardMacros
|
2
|
+
class Cycle
|
3
|
+
def initialize(cycle_proc:, on_cycle_proc: nil)
|
4
|
+
@cycle_proc = cycle_proc
|
5
|
+
@on_cycle_proc = on_cycle_proc
|
6
|
+
@previous_result = nil
|
7
|
+
reset
|
8
|
+
end
|
9
|
+
|
10
|
+
def next
|
11
|
+
@index = -1 if @index >= cycle_values.length - 1
|
12
|
+
on_cycle cycle_values[@index += 1]
|
13
|
+
end
|
14
|
+
|
15
|
+
def previous
|
16
|
+
@index = cycle_values.length if @index < 0
|
17
|
+
on_cycle cycle_values[@index -= 1]
|
18
|
+
end
|
19
|
+
|
20
|
+
def reset
|
21
|
+
@index = -1
|
22
|
+
@previous_result = nil
|
23
|
+
@cycle_values = nil
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def cycle_values
|
29
|
+
@cycle_values ||= @cycle_proc.call
|
30
|
+
end
|
31
|
+
|
32
|
+
def on_cycle(new_value)
|
33
|
+
@on_cycle_proc.call(@previous_result, new_value) if @on_cycle_proc
|
34
|
+
@previous_result = new_value
|
35
|
+
new_value
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
class TabCompletion
|
2
|
+
class BasicCompletion
|
3
|
+
class << self
|
4
|
+
attr_accessor :priority
|
5
|
+
end
|
6
|
+
self.priority = 1
|
7
|
+
|
8
|
+
attr_reader :world
|
9
|
+
|
10
|
+
def initialize(world:, word_break_characters:, path:nil)
|
11
|
+
@world = world
|
12
|
+
@word_break_characters = word_break_characters
|
13
|
+
path ||= @world.env["PATH"]
|
14
|
+
@paths = path.split(":")
|
15
|
+
end
|
16
|
+
|
17
|
+
def completions_for(word, words, word_index)
|
18
|
+
completions_by_name = {}
|
19
|
+
if looking_for_command?(word, words, word_index)
|
20
|
+
# Lowest Priority
|
21
|
+
completions_by_name.merge! command_completion_matches_for(word, words)
|
22
|
+
|
23
|
+
# Low Priority
|
24
|
+
completions_by_name.merge! builtin_completion_matches_for(word, words)
|
25
|
+
|
26
|
+
# Medium Priority
|
27
|
+
completions_by_name.merge! executable_filename_completion_matches_for(word, words)
|
28
|
+
|
29
|
+
# High Priority
|
30
|
+
completions_by_name.merge! shell_command_completion_matches_for(word, words)
|
31
|
+
|
32
|
+
# Highest Priority
|
33
|
+
completions_by_name.merge! alias_completion_matches_for(word, words)
|
34
|
+
else
|
35
|
+
completions_by_name.merge! filename_completion_matches_for(word, words)
|
36
|
+
end
|
37
|
+
completions_by_name.merge! environment_variable_completions_for(word, words)
|
38
|
+
completions_by_name.values
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def looking_for_command?(word, words, word_index)
|
44
|
+
return false unless word_index
|
45
|
+
return true if word_index == 0
|
46
|
+
return true if words[word_index - 1] =~ /[;&]/
|
47
|
+
false
|
48
|
+
end
|
49
|
+
|
50
|
+
def alias_completion_matches_for(word, words)
|
51
|
+
@world.aliases.names.each_with_object({}) do |name, result|
|
52
|
+
if name =~ /^#{Regexp.escape(word)}/
|
53
|
+
result[name] ||= CompletionResult.new(
|
54
|
+
type: :alias,
|
55
|
+
text: name
|
56
|
+
)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def builtin_completion_matches_for(word, words)
|
62
|
+
@world.builtins.each_with_object({}) do |builtin, result|
|
63
|
+
if builtin =~ /^#{Regexp.escape(word)}/
|
64
|
+
result[builtin] ||= CompletionResult.new(
|
65
|
+
type: :builtin,
|
66
|
+
text: builtin
|
67
|
+
)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def command_completion_matches_for(word, words)
|
73
|
+
@paths.each_with_object({}) do |path, matches|
|
74
|
+
glob = File.join(path, "#{word}*")
|
75
|
+
arr = Dir[glob].select { |path| File.executable?(path) && File.file?(path) }
|
76
|
+
arr.map { |path| File.basename(path) }.uniq.each do |command|
|
77
|
+
matches[command] = CompletionResult.new(type: :command, text: command)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def environment_variable_completions_for(word, words)
|
83
|
+
return {} unless word =~ /^\$/
|
84
|
+
prefix, word_sans_prefix = word[0], word[1..-1]
|
85
|
+
@world.env.keys.each_with_object({}) do |env_var, result|
|
86
|
+
if env_var =~ /^#{Regexp.escape(word_sans_prefix)}/
|
87
|
+
result[env_var] ||= CompletionResult.new(
|
88
|
+
type: :env_var,
|
89
|
+
text: prefix + env_var
|
90
|
+
)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def executable_filename_completion_matches_for(word, words)
|
96
|
+
glob = "#{word}*"
|
97
|
+
glob.gsub!("~", world.env["HOME"])
|
98
|
+
Dir.glob(glob, File::FNM_CASEFOLD).each_with_object({}) do |path, result|
|
99
|
+
text = path.gsub(filtered_work_break_characters_rgx, '\\\\\1')
|
100
|
+
descriptive_text = File.basename(text)
|
101
|
+
if !File.directory?(path) && File.executable?(path)
|
102
|
+
result[path] = CompletionResult.new(
|
103
|
+
type: :command,
|
104
|
+
text: text,
|
105
|
+
descriptive_text: descriptive_text
|
106
|
+
)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def shell_command_completion_matches_for(word, words)
|
112
|
+
@world.shell_commands.each_with_object({}) do |shell_command, result|
|
113
|
+
if shell_command =~ /^#{Regexp.escape(word)}/
|
114
|
+
result[shell_command] ||= CompletionResult.new(
|
115
|
+
type: :shell_command,
|
116
|
+
text: shell_command
|
117
|
+
)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def filename_completion_matches_for(word, line)
|
123
|
+
glob = "#{word}*"
|
124
|
+
glob.gsub!("~", world.env["HOME"])
|
125
|
+
Dir.glob(glob, File::FNM_CASEFOLD).each_with_object({}) do |path, result|
|
126
|
+
text = path.gsub(filtered_work_break_characters_rgx, '\\\\\1')
|
127
|
+
descriptive_text = File.basename(text)
|
128
|
+
result[path] = if File.directory?(path)
|
129
|
+
CompletionResult.new(type: :directory, text: text, descriptive_text: descriptive_text)
|
130
|
+
elsif File.symlink?(path)
|
131
|
+
CompletionResult.new(type: :symlink, text: text, descriptive_text: descriptive_text)
|
132
|
+
elsif File.file?(path) && File.executable?(path)
|
133
|
+
CompletionResult.new(type: :command, text: text, descriptive_text: descriptive_text)
|
134
|
+
else
|
135
|
+
CompletionResult.new(type: :file, text: text, descriptive_text: descriptive_text)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Remove file separator and the back-slash from word break characters when determining
|
141
|
+
# the pre-word-context
|
142
|
+
def filtered_word_break_characters
|
143
|
+
@word_break_characters.sub(File::Separator, "").sub('\\', '')
|
144
|
+
end
|
145
|
+
|
146
|
+
def filtered_work_break_characters_rgx
|
147
|
+
/([#{Regexp.escape(filtered_word_break_characters)}])/
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
end
|