-
-
Notifications
You must be signed in to change notification settings - Fork 93
/
finders.lua
264 lines (243 loc) · 8.62 KB
/
finders.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
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
---@brief
--- The file browser finders power the picker with both a file and folder browser.
local fb_utils = require "telescope._extensions.file_browser.utils"
local fb_make_entry = require "telescope._extensions.file_browser.make_entry"
local fb_git = require "telescope._extensions.file_browser.git"
local async_oneshot_finder = require "telescope.finders.async_oneshot_finder"
local finders = require "telescope.finders"
local scan = require "plenary.scandir"
local Path = require "plenary.path"
local Job = require "plenary.job"
local os_sep = Path.path.sep
local fb_finders = {}
local function hidden_opts(opts)
if type(opts.hidden) == "boolean" then
return opts.hidden
end
if opts.files then
return opts.hidden.file_browser
else
return opts.hidden.folder_browser
end
end
local has_fd_cache = nil
local has_fd = function()
if has_fd_cache == nil then
has_fd_cache = vim.fn.executable "fd" == 1
end
return has_fd_cache
end
local use_fd = function(opts)
return opts.use_fd and has_fd()
end
local function fd_file_args(opts)
local args
if opts.files then
args = {
"--absolute-path",
"--path-separator=" .. os_sep,
"--type",
"file",
}
if opts.add_dirs then
table.insert(args, "--type")
table.insert(args, "directory")
end
if type(opts.depth) == "number" then
table.insert(args, "--maxdepth")
table.insert(args, opts.depth)
end
else
args = { "--type", "directory", "--absolute-path" }
end
if hidden_opts(opts) then
table.insert(args, "--hidden")
end
if not opts.respect_gitignore then
table.insert(args, "--no-ignore-vcs")
end
if opts.no_ignore then
table.insert(args, "--no-ignore")
end
if opts.follow_symlinks then
table.insert(args, "--follow")
end
return args
end
local function git_args()
-- use dot here to also catch renames which also require the old filename
-- to properly show it as a rename.
local args = { "status", "--porcelain", "--", "." }
return args
end
--- Returns a finder that is populated with files and folders in `path`.
---@note Uses `fd` if available for more async-ish browsing and speed-ups
---@param opts telescope-file-browser.FinderOpts?: options to pass to the finder
fb_finders.browse_files = function(opts)
opts = opts or {}
-- returns copy with properly set cwd for entry maker
local parent_path = Path:new(opts.path):parent():absolute()
local needs_sync = opts.grouped or opts.select_buffer or opts.git_status
local data
if use_fd(opts) then
if not needs_sync then
local entry_maker = opts.entry_maker { cwd = opts.path, git_file_status = {} }
return async_oneshot_finder {
fn_command = function()
return { command = "fd", args = fd_file_args(opts), cwd = opts.path }
end,
entry_maker = entry_maker,
results = not opts.hide_parent_dir and { entry_maker(parent_path) } or {},
cwd = opts.path,
}
else
data = fb_utils.job("fd", fd_file_args(opts), opts.path)
end
else
data = scan.scan_dir(opts.path, {
add_dirs = opts.add_dirs,
depth = opts.depth,
hidden = hidden_opts(opts),
respect_gitignore = opts.respect_gitignore,
})
end
local git_file_status = {}
if opts.git_status then
local git_root = fb_git.find_root(opts.path)
if git_root ~= nil then
local git_status = Job:new({ cwd = opts.path, command = "git", args = git_args() }):sync()
git_file_status = fb_git.parse_status_output(git_status, git_root)
end
end
if opts.path ~= os_sep and not opts.hide_parent_dir then
table.insert(data, 1, parent_path)
end
if opts.grouped then
fb_utils.group_by_type(data)
end
return finders.new_table {
results = data,
entry_maker = opts.entry_maker { cwd = opts.path, git_file_status = git_file_status },
}
end
--- Returns a finder that is populated with (sub-)folders of `cwd`.
---@note Uses `fd` if available for more async-ish browsing and speed-ups
---@param opts telescope-file-browser.FinderOpts?: options to pass to the finder
fb_finders.browse_folders = function(opts)
opts = opts or {}
-- returns copy with properly set cwd for entry maker
local cwd = opts.cwd_to_path and opts.path or opts.cwd
local entry_maker = opts.entry_maker { cwd = cwd }
if use_fd(opts) then
return async_oneshot_finder {
fn_command = function()
return { command = "fd", args = fd_file_args(opts) }
end,
entry_maker = entry_maker,
results = { entry_maker(cwd) },
cwd = cwd,
}
else
local data = scan.scan_dir(cwd, {
hidden = hidden_opts(opts),
only_dirs = true,
respect_gitignore = opts.respect_gitignore,
})
table.insert(data, 1, cwd)
return finders.new_table { results = data, entry_maker = entry_maker }
end
end
---@class telescope-file-browser.FinderOpts : telescope-file-browser.PickerOpts
---@field entry_maker fun(opts: table): function entry maker for the finder (advanced)
---@field _entry_cache table<string, table>
--- Returns a finder that combines |fb_finders.browse_files| and |fb_finders.browse_folders| into a unified finder.
---@param opts telescope-file-browser.FinderOpts?: options to pass to the picker
---@return table # telescope finder
fb_finders.finder = function(opts)
opts = opts or {}
-- cache entries such that multi selections are maintained across {file, folder}_browsers
-- otherwise varying metatables misalign selections
opts._entry_cache = {}
local hidden_default = { file_browser = false, folder_browser = false }
local hidden = vim.F.if_nil(opts.hidden, hidden_default)
if type(hidden) == "table" then
hidden = vim.tbl_extend("keep", hidden, hidden_default)
end
local cwd = opts.cwd_to_path and opts.path or opts.cwd
return setmetatable({
cwd_to_path = opts.cwd_to_path,
cwd = cwd,
path = vim.F.if_nil(opts.path, opts.cwd), -- current path for file browser
add_dirs = vim.F.if_nil(opts.add_dirs, true),
hidden = hidden,
depth = vim.F.if_nil(opts.depth, 1), -- depth for file browser
auto_depth = vim.F.if_nil(opts.auto_depth, false), -- depth for file browser
respect_gitignore = vim.F.if_nil(opts.respect_gitignore, has_fd()),
no_ignore = vim.F.if_nil(opts.no_ignore, false),
follow_symlinks = vim.F.if_nil(opts.follow_symlinks, false),
files = vim.F.if_nil(opts.files, true), -- file or folders mode
grouped = vim.F.if_nil(opts.grouped, false),
quiet = vim.F.if_nil(opts.quiet, false),
select_buffer = vim.F.if_nil(opts.select_buffer, false),
hide_parent_dir = vim.F.if_nil(opts.hide_parent_dir, false),
collapse_dirs = vim.F.if_nil(opts.collapse_dirs, false),
git_status = vim.F.if_nil(opts.git_status, fb_git.find_root(cwd) ~= nil),
create_from_prompt = vim.F.if_nil(opts.create_from_prompt, true),
-- ensure we forward make_entry opts adequately
entry_maker = vim.F.if_nil(opts.entry_maker, function(local_opts)
return fb_make_entry(vim.tbl_extend("force", opts, local_opts))
end),
_browse_files = vim.F.if_nil(opts.browse_files, fb_finders.browse_files),
_browse_folders = vim.F.if_nil(opts.browse_folders, fb_finders.browse_folders),
close = function(self)
self._finder = nil
end,
prompt_title = opts._custom_prompt_title,
results_title = opts._custom_results_title,
prompt_path = opts.prompt_path,
use_fd = vim.F.if_nil(opts.use_fd, true),
}, {
__call = function(self, ...)
if self.files and self.auto_depth then
local prompt = select(1, ...)
if prompt ~= "" then
if self.__depth == nil then
self.__depth = self.depth
self.__grouped = self.grouped
-- math.huge for upper limit does not work
self.depth = type(self.auto_depth) == "number" and self.auto_depth or 100000000
self.grouped = false
self:close()
end
else
if self.__depth ~= nil then
self.depth = self.__depth
self.grouped = self.__grouped
self.__depth = nil
self.__grouped = nil
self:close()
end
end
end
-- (re-)initialize finder on first start or refresh due to action
if not self._finder then
if self.files then
self._finder = self:_browse_files()
else
self._finder = self:_browse_folders()
end
end
self._finder(...)
end,
__index = function(self, k)
-- finder pass through for e.g. results
if rawget(self, "_finder") then
local finder_val = self._finder[k]
if finder_val ~= nil then
return finder_val
end
end
end,
})
end
return fb_finders