Skip to content

Commit 8e54c8f

Browse files
committed
Add path completion to Readline.
Arguably the most common use for tab completion is paths, so let's support it! Calls to #ask can now take `:path => true` which will enable tab completion relative to the current working directory on systems that have Readline support.
1 parent 481e624 commit 8e54c8f

File tree

3 files changed

+62
-1
lines changed

3 files changed

+62
-1
lines changed

lib/thor/line_editor/readline.rb

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ def self.available?
88
end
99

1010
def readline
11+
::Readline.completion_append_character = nil
1112
::Readline.completion_proc = completion_proc
1213
::Readline.readline(prompt, add_to_history?)
1314
end
@@ -19,7 +20,9 @@ def add_to_history?
1920
end
2021

2122
def completion_proc
22-
if completion_options.any?
23+
if use_path_completion?
24+
Proc.new { |text| PathCompletion.new(text).matches }
25+
elsif completion_options.any?
2326
Proc.new do |text|
2427
completion_options.select { |option| option.start_with?(text) }
2528
end
@@ -29,6 +32,46 @@ def completion_proc
2932
def completion_options
3033
options.fetch(:limited_to, [])
3134
end
35+
36+
def use_path_completion?
37+
options.fetch(:path, false)
38+
end
39+
40+
class PathCompletion
41+
def initialize(text)
42+
@text = text
43+
end
44+
45+
def matches
46+
relative_matches
47+
end
48+
49+
private
50+
51+
attr_reader :text
52+
53+
def relative_matches
54+
absolute_matches.map { |path| path.sub(base_path, '') }
55+
end
56+
57+
def absolute_matches
58+
Dir[glob_pattern].map do |path|
59+
if File.directory?(path)
60+
"#{path}/"
61+
else
62+
path
63+
end
64+
end
65+
end
66+
67+
def glob_pattern
68+
"#{base_path}#{text}*"
69+
end
70+
71+
def base_path
72+
"#{Dir.pwd}/"
73+
end
74+
end
3275
end
3376
end
3477
end

lib/thor/shell/basic.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,20 @@ def padding=(value)
4343
# If asking for sensitive information, the :echo option can be set
4444
# to false to mask user input from $stdin.
4545
#
46+
# If the required input is a path, then set the path option to
47+
# true. This will enable tab completion for file paths relative
48+
# to the current working directory on systems that support
49+
# Readline.
50+
#
4651
# ==== Example
4752
# ask("What is your name?")
4853
#
4954
# ask("What is your favorite Neopolitan flavor?", :limited_to => ["strawberry", "chocolate", "vanilla"])
5055
#
5156
# ask("What is your password?", :echo => false)
5257
#
58+
# ask("Where should the file be saved?", :path => true)
59+
#
5360
def ask(statement, *args)
5461
options = args.last.is_a?(Hash) ? args.pop : {}
5562
color = args.first

spec/line_editor/readline_spec.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
before do
55
unless defined? ::Readline
66
::Readline = double('Readline')
7+
allow(::Readline).to receive(:completion_append_character=).with(nil)
78
end
89
end
910

@@ -45,5 +46,15 @@
4546
editor = Thor::LineEditor::Readline.new('Best food: ', :limited_to => ['Apples', 'Chicken', 'Chocolate'])
4647
editor.readline
4748
end
49+
50+
it 'provides path tab completion when given the path option' do
51+
expect(::Readline).to receive(:readline)
52+
expect(::Readline).to receive(:completion_proc=) do |proc|
53+
expect(proc.call('../line_ed').sort).to eq ['../line_editor/', '../line_editor_spec.rb'].sort
54+
end
55+
56+
editor = Thor::LineEditor::Readline.new('Path to file: ', :path => true)
57+
Dir.chdir(File.dirname(__FILE__)) { editor.readline }
58+
end
4859
end
4960
end

0 commit comments

Comments
 (0)