-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSimpleFuzzy.py
241 lines (206 loc) · 8.07 KB
/
SimpleFuzzy.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
import sublime
import sublime_plugin
import threading
import os
import re
import subprocess
log_enable = False
def debug_log(string):
if log_enable:
print(string)
class SimpleFuzzyDebugToggleCommand(sublime_plugin.WindowCommand):
def run(self):
global log_enable
log_enable = not log_enable
class EditorLineInputHandler(sublime_plugin.ListInputHandler):
def __init__(self, view):
self.view = view
self._backup_region = view.sel()[0]
self._init = True
def name(self):
return "pos"
def placeholder(self):
return "Search content line..."
def list_items(self):
regions = self.view.find_all('.+\n')
lines = [self.view.substr(region).strip().replace('\t', '') for region in regions]
positions = [r.begin() for r in regions]
return [
sublime.ListInputItem(
text=line_str,
value=pos,
) for pos, line_str in zip(positions, lines)
if re.match('\s*\d+$', line_str) is None and len(line_str)
]
def cancel(self):
self.view.sel().clear()
self.view.sel().add(self._backup_region)
self.view.show_at_center(self._backup_region)
def preview(self, pos):
if self._init and pos == 0:
return
self._init = False
row, col = self.view.rowcol(pos)
self.view.run_command("goto_line", {"line": row+1})
class FuzzyCurrentFileCommand(sublime_plugin.TextCommand):
def run(self, edit, pos):
row, col = self.view.rowcol(pos)
self.view.run_command("goto_line", {"line": row+1})
def input(self, args):
if "pos" not in args:
return EditorLineInputHandler(self.view)
else:
return None
class GrepFileLinesThread(threading.Thread):
def __init__(self, folder, filename, encoding='UTF-8', timeout=30):
self.folder = folder
self.filename = filename
self.encoding = encoding
self.rel_filename = self.filename.replace(folder, '')
self.timeout = timeout
self.result = None
threading.Thread.__init__(self)
def run(self):
self.result = self._read_filelines(self.filename)
def _read_filelines(self, filename):
with open(self.filename, 'r', encoding=self.encoding) as fs:
try:
lines = [
l.strip().replace('\t', '')
for l in fs.readlines()
]
return [
sublime.ListInputItem(
text=line_str,
value=(self.filename, line_no + 1),
annotation='%s:%s'%(self.rel_filename, line_no+1),
) for line_no, line_str in enumerate(lines)
if len(line_str) > 0
]
except UnicodeDecodeError:
return []
def _await_view_goto_line(view, line):
if view.is_loading():
sublime.set_timeout_async(lambda: _await_view_goto_line(view, line), 50)
return
# wait for view rendering current line in center
sublime.set_timeout_async(lambda: view.run_command("goto_line", {"line": line}), 10)
class FolderLineInputHandler(sublime_plugin.ListInputHandler):
def __init__(self, window):
self.window = window
self.view = self.window.active_view()
self._backup_region = self.view.sel()[0]
self._init = True
def name(self):
return "file_lines"
def placeholder(self):
return "Search content line..."
def cancel(self):
self.window.focus_view(self.view)
self.view.sel().clear()
self.view.sel().add(self._backup_region)
self.view.show_at_center(self._backup_region)
def preview(self, file_lines):
file = file_lines[0]
line = file_lines[1]
view = self.window.open_file(file, sublime.TRANSIENT)
_await_view_goto_line(view, line)
def list_items(self):
folders = self.window.folders()
if len(folders) == 0:
sublime.error_message('No project folder found for Fuzzy Project Line search.')
return []
active_folder = next(
(f for f in folders if f in (self.view.file_name() or '')),
folders[0]
)
encoding = self.view.encoding() if self.view.encoding() != 'Undefined' else 'UTF-8'
debug_log('fuzzy project in: %s with Encoding=%s'%(active_folder, encoding))
file_list = self._list_files(active_folder, encoding)
threads = []
lines = []
for file in file_list:
if not os.path.exists(file):
continue
view = self.window.find_open_file(file)
if view == None:
thread = GrepFileLinesThread(active_folder, file, encoding)
thread.start()
threads.append(thread)
else:
lines += self._grep_view_lines(active_folder, view)
for thread in threads:
thread.join()
lines += thread.result
return lines
# return filenames including folder name
def _list_files(self, folder, encoding='UTF-8'):
user_pref_cmd = self.view.settings().get('simple_fuzzy_ls_cmd', '')
user_pref_chk = self.view.settings().get('simple_fuzzy_chk_cmd', '')
def _fmt_cmd(fmt):
return '{_fmt}'.format(_fmt=fmt).format(folder=folder)
def _ls_dir(check_cmd, ls_cmd):
OK = 0
if len(check_cmd):
debug_log('check_cmd: {!r}'.format(_fmt_cmd(check_cmd)))
if os.system(_fmt_cmd(check_cmd)) != OK:
return []
debug_log('ls_cmd: {!r}'.format(_fmt_cmd(ls_cmd)))
try:
f_list = subprocess.check_output(_fmt_cmd(ls_cmd), shell=True).splitlines()
except subprocess.CalledProcessError:
f_list = []
debug_log('ls_cmd: {} file(s) found'.format(len(f_list)))
return [f.decode(encoding) for f in f_list]
def _builtin_ls():
# default fallback for listing files in folder
f_list = []
for root, dirs, files in os.walk(folder):
f_list += [os.path.join(root, f) for f in files]
return f_list
default_cmds = {
'rg': lambda: _ls_dir('', 'rg --files "{folder}"'),
'git': lambda: _ls_dir('git -C "{folder}" status', 'git -C "{folder}" ls-files'),
'built-in': _builtin_ls,
}
file_list = []
if user_pref_cmd in default_cmds:
file_list = default_cmds[user_pref_cmd]()
elif len(user_pref_cmd):
chk_cmd = user_pref_chk
file_list = _ls_dir(chk_cmd, user_pref_cmd)
for cmd in ('rg', 'git', 'built-in'):
if len(file_list):
break
file_list = default_cmds[cmd]()
if len(file_list) and not os.path.exists(file_list[0]):
# relative -> fullpath
file_list = [os.path.join(folder, f) for f in file_list]
return [f for f in file_list if os.path.isfile(f)]
def _grep_view_lines(self, folder, view):
filename = view.file_name()
rel_filename = filename.replace(folder, '')
regions = view.find_all('.*\n')
lines = [
(line_no + 1, view.substr(region).strip().replace('\t', ''))
for line_no, region in enumerate(regions)
]
return [
sublime.ListInputItem(
text=line_str,
value=(filename, line_no),
annotation='%s:%s'%(rel_filename, line_no),
) for line_no, line_str in lines
if len(line_str.strip()) > 0
]
class FuzzyActiveProjectCommand(sublime_plugin.WindowCommand):
def run(self, file_lines):
file = file_lines[0]
line = file_lines[1]
view = self.window.open_file(file)
_await_view_goto_line(view, line)
def input(self, args):
if "file_lines" not in args:
return FolderLineInputHandler(self.window)
else:
return None