forked from math2001/FileManager
-
Notifications
You must be signed in to change notification settings - Fork 0
/
input_for_path.py
391 lines (332 loc) · 14.8 KB
/
input_for_path.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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
import sublime
import os
from .pathhelper import *
from .sublimefunctions import *
def isdigit(string):
try:
int(string)
except ValueError:
return False
else:
return True
def set_status(view, key, value):
if view:
view.set_status(key, value)
else:
sm(value)
def get_entire_text(view):
return view.substr(sublime.Region(0, view.size()))
class InputForPath(object):
STATUS_KEY = 'input_for_path'
def __init__(self,
caption,
initial_text,
on_done,
on_change,
on_cancel,
create_from,
with_files,
pick_first,
case_sensitive,
log_in_status_bar,
log_template,
browser_action={},
start_with_browser=False,
no_browser_action=False,
browser_index=None):
self.user_on_done = on_done
self.user_on_change = on_change
self.user_on_cancel = on_cancel
self.caption = caption
self.initial_text = initial_text
self.log_template = log_template
self.browser_action = browser_action
self.no_browser_action = no_browser_action
self.browser_index = browser_index
self.create_from = create_from
if self.create_from:
self.create_from = computer_friendly(self.create_from)
if not os.path.isdir(self.create_from):
if os.path.exists(self.create_from):
sublime.error_message(
"This path exists, but doesn't seem to be a directory. "
" Please report this (see link in the console)")
raise ValueError(
"This path exists, but doesn't seem to be a directory. "
"Here's the path {0}. Please report this bug here: "
"https://github.com/math2001/FileManager/issues"
.format(self.create_from))
sublime.error_message(
'The path `create_from` should exists. {0!r} does not '
" exists.".format(self.create_from))
raise ValueError(
'The path create from does not exists ({0!r})'.format(
self.create_from))
self.browser = StdClass('Browser')
self.browser.path = self.create_from
self.browser.items = []
self.with_files = with_files
self.pick_first = pick_first
self.case_sensitive = case_sensitive
self.path_to_create_choosed_from_browsing = False
self.window = sublime.active_window()
self.view = self.window.active_view()
self.log_in_status_bar = log_in_status_bar
if start_with_browser:
self.browsing_on_done()
else:
self.create_input()
def create_input(self):
self.prev_input_path = None
self.input = StdClass('input')
self.input.view = self.window.show_input_panel(
self.caption, self.initial_text, self.input_on_done,
self.input_on_change, self.input_on_cancel)
self.input.view.set_name('FileManager::input-for-path')
self.input.settings = self.input.view.settings()
self.input.settings.set('tab_completion', False)
if not isST3():
self.input.view.selection = self.input.view.sel()
def __get_completion_for(self, abspath, with_files, pick_first,
case_sensitive, can_add_slash):
"""Return a string and list: the prefix, and the list
of available completion in the right order"""
def sort_in_two_list(items, key):
first, second = [], []
for item in items:
first_list, item = key(item)
if first_list:
first.append(item)
else:
second.append(item)
return first, second
abspath = computer_friendly(abspath)
if abspath.endswith(os.path.sep):
prefix = ''
load_items_from = abspath
else:
load_items_from = os.path.dirname(abspath)
prefix = os.path.basename(abspath)
items = sorted(os.listdir(load_items_from))
items_with_right_prefix = []
if not case_sensitive:
prefix = prefix.lower()
for i, item in enumerate(items):
if not case_sensitive:
item = item.lower()
if item.startswith(prefix) and item != prefix:
# I add items[i] because it's case is never changed
items_with_right_prefix.append([
items[i],
os.path.isdir(os.path.join(load_items_from, items[i]))
])
folders, files = sort_in_two_list(items_with_right_prefix,
lambda item: [item[1], item[0]])
if can_add_slash:
folders = [folder + '/' for folder in folders]
if with_files:
if pick_first == 'folders':
return prefix, folders + files
elif pick_first == 'files':
return prefix, files + folders
elif pick_first == 'alphabetic':
return prefix, sorted(files + folders)
else:
sublime.error_message(
'The keyword {0!r} to define the order of completions is '
'not valid. See the default settings.'.format(pick_first))
raise ValueError(
'The keyword {0!r} to define the order of completions is '
'not valid. See the default settings.'.format(pick_first))
else:
return prefix, folders
def transform_aliases(self, string):
"""Transform aliases using the settings and the default variables
It's recursive, so you can use aliases *in* your aliases' values
"""
def has_unescaped_dollar(string):
start = 0
while True:
index = string.find('$', start)
if index < 0:
return False
elif string[index-1] == '\\':
start = index + 1
else:
return True
string = string.replace('$$', '\\$')
vars = self.window.extract_variables()
vars.update(get_settings().get('aliases'))
inifinite_loop_counter = 0
while has_unescaped_dollar(string):
inifinite_loop_counter += 1
if inifinite_loop_counter > 100:
sublime.error_message("Infinite loop: you better check your "
"aliases, they're calling each other "
"over and over again.")
if get_settings().get('open_help_on_alias_infinite_loop',
True) is True:
sublime.run_command('open_url', {
'url': 'https://github.com/math2001/ '
'FileManager/wiki/Aliases '
'#watch-out-for-infinite-loops'
})
return string
string = sublime.expand_variables(string, vars)
return string
def input_on_change(self, input_path):
self.input_path = user_friendly(input_path)
self.input_path = self.transform_aliases(self.input_path)
# get changed inputs and create_from from the on_change user function
if self.user_on_change:
new_values = self.user_on_change(
self.input_path, self.path_to_create_choosed_from_browsing)
if new_values is not None:
create_from, self.input_path = new_values
if create_from is not None:
self.create_from = computer_friendly(create_from)
def reset_settings():
self.input.settings.erase('completions')
self.input.settings.erase('completions_index')
def replace_with_completion(completions, index, prefix=None):
# replace the previous completion
# with the new one (completions[index+1])
region = [self.input.view.sel()[0].begin()]
# -1 because of the \t
region.append(region[0] - len(prefix if prefix is not None else
completions[index]) - 1)
if self.input.settings.get('go_backwards') is True:
index -= 1
self.input.settings.erase('go_backwards')
else:
index += 1
self.input.settings.set('completions_index', index)
# Running fm_edit_replace will trigger this function
# and because it is not going to find any \t
# it's going to erase the settings
# Adding this will prevent this behaviour
self.input.settings.set('just_completed', True)
self.input.view.run_command('fm_edit_replace', {
'region': region,
'text': completions[index]
})
self.prev_input_path = self.input.view.substr(
sublime.Region(0, self.input.view.size()))
if self.log_in_status_bar:
path = computer_friendly(
os.path.normpath(self.create_from + os.path.sep +
self.input_path))
if self.input_path != '' and self.input_path[-1] == '/':
path += os.path.sep
if self.log_in_status_bar == 'user':
path = user_friendly(path)
set_status(self.view, self.STATUS_KEY,
self.log_template.format(path))
if not hasattr(self.input, 'settings'):
return
if self.input.settings.get('ran_undo', False) is True:
return self.input.settings.erase('ran_undo')
completions = self.input.settings.get('completions', None)
index = self.input.settings.get('completions_index', None)
if index == 0 and len(completions) == 1:
reset_settings()
return
if completions is not None and index is not None:
# check if the user typed something after the completion
text = input_path
if text[-1] == '\t':
text = text[:-1]
if not text.endswith(tuple(completions)):
return reset_settings()
if '\t' in input_path:
# there is still some completions available
if len(completions) - 1 > index:
return replace_with_completion(completions, index)
if '\t' in input_path:
before, after = self.input_path.split('\t', 1)
prefix, completions = self.__get_completion_for(
abspath=computer_friendly(
os.path.join(self.create_from, before)),
with_files=self.with_files,
pick_first=self.pick_first,
case_sensitive=self.case_sensitive,
can_add_slash=after == '' or after[0] != '/')
if not completions:
return
self.input.settings.set('completions', completions)
self.input.settings.set('completions_index', -1)
replace_with_completion(completions, -1, prefix)
def input_on_done(self, input_path):
if self.log_in_status_bar:
set_status(self.view, self.STATUS_KEY, '')
# use the one returned by the on change function
input_path = self.input_path
computer_path = computer_friendly(
os.path.join(self.create_from, input_path))
# open browser
if os.path.isdir(computer_path):
self.browser.path = computer_path
return self.browsing_on_done()
else:
self.user_on_done(computer_path, input_path)
def input_on_cancel(self):
active_view = self.window.active_view()
if active_view.file_name() in [os.path.join(self.browser.path, item)
for item in self.browser.items]:
active_view.close()
set_status(self.view, self.STATUS_KEY, '')
if self.user_on_cancel:
self.user_on_cancel()
def open_in_transient(self, index):
if self.no_browser_action is False and index < 2:
return
if not os.path.isfile(os.path.join(self.browser.path, self.browser.items[index])):
return
self.window.open_file(os.path.join(self.browser.path, self.browser.items[index]),
sublime.TRANSIENT)
def browsing_on_done(self, index=None):
if index == -1:
return self.input_on_cancel()
if self.no_browser_action is False and index == 0:
# create from the position in the browser
self.create_from = self.browser.path
self.path_to_create_choosed_from_browsing = True
if self.browser_action.get('func', None) is None:
return self.create_input()
else:
return self.browser_action['func'](self.create_from, None)
elif (self.no_browser_action is True and index == 0) or (
index == 1 and self.no_browser_action is False):
self.browser.path = os.path.normpath(
os.path.join(self.browser.path, '..'))
elif index is not None:
self.browser.path = os.path.join(self.browser.path,
self.browser.items[index])
if os.path.isfile(self.browser.path):
set_status(self.view, self.STATUS_KEY, '')
return self.window.open_file(self.browser.path)
folders, files = [], []
for item in os.listdir(self.browser.path):
if os.path.isdir(os.path.join(self.browser.path, item)):
folders.append(item + '/')
else:
files.append(item)
if self.no_browser_action:
self.browser.items = ['[cmd] ..'] + folders + files
elif self.browser_action.get('title', None) is not None:
self.browser.items = [
'[cmd] ' + self.browser_action['title'], '[cmd] ..'
] + folders + files
else:
self.browser.items = ['[cmd] Create from here', '[cmd] ..'
] + folders + files
set_status(self.view, self.STATUS_KEY,
'Browsing at: {0}'.format(user_friendly(self.browser.path)))
if self.browser_index is not None:
index = self.browser_index
elif self.no_browser_action:
index = 1
else:
index = 2
self.window.show_quick_panel(self.browser.items, self.browsing_on_done,
sublime.KEEP_OPEN_ON_FOCUS_LOST, index, self.open_in_transient)