forked from jugglerchris/textadept-vi
-
Notifications
You must be signed in to change notification settings - Fork 0
/
vi_find_files.lua
198 lines (178 loc) · 5.88 KB
/
vi_find_files.lua
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
-- Search for file by name/pattern.
local M = {}
local lfs = _G.lfs
local vi_regex = require('regex.regex')
-- Escape a Lua pattern to make it an exact match.
function M.luapat_escape(s)
-- replace metacharacters
s = s:gsub("[%(%)%%%.%[%]%*%+%-%?]", function (s) return "%"..s end)
-- ^ and $ only apply at the start/end
if s:sub(1,1) == "^" then s = "%" .. s end
if s:sub(-1,-1) == "$" then s = s:sub(1,-2) .. "%$" end
return s
end
local function mkmatch_luapat(pat, allow_wild_end)
local fullpat = '^' .. pat
if allow_wild_end then
fullpat = fullpat .. '.*'
end
fullpat = fullpat .. '$'
return function(text)
local result = text:match(fullpat)
return result
end
end
local function mkmatch_null(pat, allow_wild_end)
local escaped_pat = '^' .. M.luapat_escape(pat)
if allow_wild_end then
escaped_pat = escaped_pat .. '.*'
end
escaped_pat = escaped_pat .. '$'
return function(text)
local result = text:match(escaped_pat)
return result
end
end
local ignore_complete_files = { ['.'] = 1 }
function do_matching_files(text, mk_matcher, escape)
local patparts = {} -- the pieces of the pattern
-- Split the pattern into parts separated by /
if text then
for part in text:gmatch('[^/]+') do
table.insert(patparts, part)
end
-- If tab on trailing /, then will want to complete on files in the
-- directory.
if text:sub(-1) == '/' then
table.insert(patparts, '')
end
end
-- partmatches[n] is a list of matches for patparts[n] at that level
local parts = { }
-- Set of directories to look in
local dirs = { }
-- The start depends on whether the path is absolute or relative
if text and text:sub(1, 1) == '/' then
table.insert(dirs, '/')
elseif patparts[1] == '~' then
-- Handle ~/...
table.insert(dirs, os.getenv("HOME") .. "/")
-- Remove the initial ~
table.remove(patparts, 1)
else
table.insert(dirs, './')
end
-- For each path section
for level, patpart in ipairs(patparts) do
local last = (level == #patparts)
-- If the last part, then allow trailing parts
-- TODO: if we complete from a middle-part, then
-- this test should be for where the cursor is.
local allow_wild_end = last
-- The set of paths for the following loop
local newdirs = {}
local matcher = mk_matcher(patpart, allow_wild_end)
-- For each possible directory at this level
for _,dir in ipairs(dirs) do
for fname in lfs.dir(dir) do
if not ignore_complete_files[fname] and matcher(fname) then
local fullpath
if dir == "./" then
fullpath = fname
else
fullpath = dir .. fname
end
local isdir = lfs.attributes(fullpath, 'mode') == 'directory'
-- Record this path if it's not a non-directory with more path
-- parts to go.
if lfs.attributes(fullpath, 'mode') == 'directory' then
table.insert(newdirs, fullpath .. '/')
elseif last then
table.insert(newdirs, fullpath)
end
end
end
end
-- Switch to the next level of items
dirs = newdirs
end -- loop through pattern parts
-- Find out the set of components at each level
-- parts[level] is a table { fname=1,fname2=1, fname,fname2}
local parts = {}
for _,res in ipairs(dirs) do
local level = 1
for piece in res:gmatch('[^/]*') do
ps = parts[level] or {}
parts[level] = ps
if ps[piece] == nil then
ps[piece] = 1
table.insert(ps, piece)
end
end
end
-- Now rebuild the pattern, with some ambiguities removed
local narrowed = false -- whether we've added more unambiguous info
local newparts = {}
-- keep absolute or relative
if text:sub(1,1) == '/' then
table.insert(newparts, '/')
end
for level,matches in ipairs(parts) do
local last = (level == #parts)
if #matches == 1 then
-- Only one thing, so use that.
local newpart = escape(matches[1])
if newpart ~= patparts[level] then
narrowed = true
end
table.insert(newparts, newpart)
-- matches[fname] is true if all options are directories
if last and matches[matches[1]] then
table.insert(newparts, '/')
end
else
table.insert(newparts, patparts[level])
end
if not last then table.insert(newparts, '/') end
end
local files
if narrowed then
files = { table.concat(newparts) }
else
files = {}
table.sort(dirs)
for i,d in ipairs(dirs) do
files[i] = escape(d)
end
end
return files
end
-- Match filename exactly, with no escaping or wildcards etc.
function M.matching_files_nopat(text)
local escape = function(s) return s end
return do_matching_files(text, mkmatch_null, escape)
end
-- Find files with patterns
function M.matching_files(text, doescape)
-- Escape by default
local escape
if doescape == nil or doescape then
escape = M.luapat_escape
else
escape = function(s) return s end
end
return do_matching_files(text, mkmatch_luapat, escape)
end
-- Find files matching a Regex pattern (or a string match)
function M.find_matching_files(pattern)
local results = {}
local pat = vi_regex.compile(pattern)
local function f(filename)
if (pat and pat:match(filename)) or filename:find(pattern, 1, true) then
results[#results+1] = filename
end
end
lfs.dir_foreach('.', f, { folders = { "build"}}, false)
return results
end
return M