yap-shell 0.3.1 → 0.4.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 +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
|