forked from kublaios/dired
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathprompt.py
176 lines (133 loc) · 5.67 KB
/
prompt.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# Internal-only commands for dealing with the directory prompt. Due to Sublime Text's quirky
# design we need to re-issue commands over and over during a single prompt.
import sublime
from sublime import Region
from sublime_plugin import WindowCommand, EventListener, TextCommand
import os
from os.path import basename, join, isdir, dirname, expanduser
map_window_to_ctx = {}
# Map from window id that is displaying a prompt to its prompt context object.
def start(msg, window, path, callback):
"""
Starts the prompting process.
"""
if not path.endswith(os.sep):
path += os.sep
path = expanduser(path)
map_window_to_ctx[window.id()] = PromptContext(msg, path, callback)
window.run_command('dired_prompt')
class PromptContext:
def __init__(self, msg, path, callback):
self.msg = msg
self.path = path
# The path we are completing. This is updated as the user types, so it will be an
# invalid path at times.
self.callback = callback
self.completion_view = None
def __repr__(self):
return '{} {} view:{}'.format(self.path, bool(self.completion_view))
class DiredPromptCommand(WindowCommand):
"""
An internal-only command that separates prompt handling from external commands since each
tab completion requires another command.
A prompt context must already be registered in map_window_to_ctx when this is executed.
"""
def run(self):
ctx = map_window_to_ctx[self.window.id()]
self.window.show_input_panel(ctx.msg, ctx.path, self.on_done, self.on_change, self.on_cancel)
def on_done(self, value):
ctx = map_window_to_ctx.pop(self.window.id(), None)
self._close_completions()
ctx.callback(ctx.path)
def on_cancel(self):
self._close_completions()
def on_change(self, value):
# Keep the path in the ctx up to date in case Tab is pressed. It will cancel this completion
# and start another.
ctx = map_window_to_ctx.get(self.window.id())
if ctx:
ctx.path = value
def _close_completions(self):
ctx = map_window_to_ctx.pop(self.window.id(), None)
if ctx and ctx.completion_view:
self.window.focus_view(ctx.completion_view)
self.window.run_command('close_file')
class DiredEventListener(EventListener):
def on_query_context(self, view, key, operator, operand, match_all):
if not map_window_to_ctx or not key.startswith('dired_'):
return None
if key == 'dired_complete':
return True
return False
class DiredCompleteCommand(WindowCommand):
"""
An internal command executed when the user has pressed Tab in our directory prompt.
Since a prompt is already in progress, a completion info must already be registered for
this window. Update the path, kill the current prompt, and reprompt with the new path.
"""
def _needs_sep(self, path):
"""
Returns True if the current value is a complete directory name without a trailing
separator, and there are no other possible completions.
"""
if not isdir(path) or path.endswith(os.sep):
return False
partial = basename(path)
path = dirname(path)
if any(n for n in os.listdir(dirname(path)) if n != partial and n.startswith(partial) and isdir(join(path, n))):
# There are other completions.
return False
return True
def _parse_split(self, path):
"""
Split the path into the directory to search and the prefix to match in that directory.
If the path is completely invalid, (None, None) is returned.
"""
prefix = ''
if not path.endswith(os.sep):
prefix = basename(path)
path = dirname(path)
if not isdir(path):
return (None, None)
return (path, prefix)
def _close_completions(self, ctx):
if ctx.completion_view:
self.window.focus_view(ctx.completion_view)
self.window.run_command('close_file')
ctx.completion_view = None
def run(self):
ctx = map_window_to_ctx.get(self.window.id())
if not ctx:
return
path = expanduser(ctx.path)
path, prefix = self._parse_split(path)
if path is None:
print('Invalid:', ctx.path)
return
completions = [ n for n in os.listdir(path) if n.startswith(prefix) and isdir(join(path, n)) ]
if len(completions) == 0:
sublime.status_message('No matches')
self._close_completions(ctx)
return
if len(completions) == 1:
ctx.path = join(path, completions[0]) + os.sep
self.window.run_command('dired_prompt')
self._close_completions(ctx)
return
common = os.path.commonprefix(completions)
if common and common > prefix:
ctx.path = join(path, common)
self.window.run_command('dired_prompt')
self._close_completions(ctx)
return
# There are multiple possibilities. Display a completion view.
if not ctx.completion_view:
ctx.completion_view = self.window.new_file()
ctx.completion_view.set_scratch(True)
ctx.completion_view.set_name('*completions*')
ctx.completion_view.run_command('dired_show_completions', { "completions": completions })
self.window.focus_view(ctx.completion_view)
class DiredShowCompletionsCommand(TextCommand):
def run(self, edit, completions=None):
self.view.erase(edit, Region(0, self.view.size()))
self.view.insert(edit, 0, '\n'.join(completions))