-
-
Notifications
You must be signed in to change notification settings - Fork 102
Expand file tree
/
Copy pathmake_entry.lua
More file actions
280 lines (253 loc) · 9.31 KB
/
make_entry.lua
File metadata and controls
280 lines (253 loc) · 9.31 KB
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
local fb_utils = require "telescope._extensions.file_browser.utils"
local fb_git = require "telescope._extensions.file_browser.git"
local fs_stat = require "telescope._extensions.file_browser.fs_stat"
local utils = require "telescope.utils"
local log = require "telescope.log"
local entry_display = require "telescope.pickers.entry_display"
local action_state = require "telescope.actions.state"
local state = require "telescope.state"
local strings = require "plenary.strings"
local Path = require "plenary.path"
local os_sep = Path.path.sep
local os_sep_len = #os_sep
local sep = " "
local stat_enum = {
size = fs_stat.size,
date = fs_stat.date,
mode = fs_stat.mode,
}
local get_fb_prompt = function()
local prompt_bufnr = vim.tbl_filter(function(b)
return vim.bo[b].filetype == "TelescopePrompt"
end, vim.api.nvim_list_bufs())
-- vim.ui.{input, select} might be telescope pickers
if #prompt_bufnr > 1 then
for _, buf in ipairs(prompt_bufnr) do
local current_picker = action_state.get_current_picker(prompt_bufnr)
if current_picker.finder._browse_files then
prompt_bufnr = buf
break
end
end
else
prompt_bufnr = prompt_bufnr[1]
end
return prompt_bufnr
end
-- Compute total file width of results buffer:
-- The results buffer typically splits like this with this notation {item, width}
-- {devicon, 1} { name, variable }, { stat, stat_width, typically right_justify }
-- file-browser tries to fully right justify the stat items to give maximum space to
-- name of files or directories
local function compute_file_width(status, opts)
local total_file_width = vim.api.nvim_win_get_width(status.results_win)
- #status.picker.selection_caret
- (opts.disable_devicons and 0 or (1 + #sep))
- (opts.git_status and (2 + #sep) or 0)
-- Apply stat defaults:
-- opts.display_stat can be typically either
-- { stat = true } or stat = { width = 5 }
-- where the defaults are added in addition to passed configuration
if opts.display_stat then
for key, value in pairs(opts.display_stat) do
local default = stat_enum[key]
if default == nil then
local valid_keys = table.concat(vim.tbl_keys(stat_enum), ", ")
-- TODO rebase vim.notify PR upon here and change appropriately
vim.notify(string.format("%s not part of valid stat keys [ %s ]", key, valid_keys), vim.log.levels.WARN)
opts.display_stat[key] = nil -- removing as opts.display_stat is relied upon later on
else
if type(value) == "table" then
opts.display_stat[key] = vim.tbl_deep_extend("keep", value, default)
else
opts.display_stat[key] = default
end
local w = opts.display_stat[key].width or 0
total_file_width = total_file_width - w - #sep
end
end
end
return total_file_width
end
-- General:
-- telescope-file-browser unlike telescope
-- caches "made" entries to retain multi-selections
-- naturally across varying folders
-- entry
-- - value: absolute path of entry
-- - display: made relative to current folder
-- - display: made relative to current folder
-- - Path: cache plenary.Path object of entry
-- - stat: lazily cached vim.loop.fs_stat of entry
local make_entry = function(opts)
local prompt_bufnr = get_fb_prompt()
local status = state.get_status(prompt_bufnr)
local total_file_width = compute_file_width(status, opts)
local autocmd_id
autocmd_id = vim.api.nvim_create_autocmd("VimResized", {
callback = function()
-- Abort if picker was closed
if not vim.api.nvim_win_is_valid(status.results_win) and type(autocmd_id) == "number" then
vim.api.nvim_del_autocmd(autocmd_id)
return
end
total_file_width = compute_file_width(status, opts)
if type(prompt_bufnr) == "number" and vim.api.nvim_buf_is_valid(prompt_bufnr) then
local current_picker = action_state.get_current_picker(prompt_bufnr)
local selection = action_state.get_selected_entry()
fb_utils.selection_callback(current_picker, selection.value)
current_picker:refresh(nil, { reset_prompt = false, multi = current_picker._multi })
end
end,
})
-- needed since Path:make_relative does not resolve parent dirs
local parent_dir = Path:new(opts.cwd):parent():absolute()
local mt = {}
mt.cwd = opts.cwd
-- +1 to start at first file char; cwd may or may not end in os_sep
local cwd_substr = #mt.cwd + 1
cwd_substr = mt.cwd:sub(-1, -1) ~= os_sep and cwd_substr + os_sep_len or cwd_substr
-- TODO(fdschmidt93): handle VimResized with due to variable width
mt.display = function(entry)
-- TODO make more configurable
local widths = {}
local display_array = {}
local icon, icon_hl
local is_dir = entry.Path:is_dir()
-- entry.ordinal is path excl. cwd
local tail = fb_utils.trim_right_os_sep(entry.ordinal)
-- path_display plays better with relative paths
local path_display = utils.transform_path(opts, tail)
if is_dir then
if entry.value == parent_dir then
path_display = string.format("..%s", os_sep)
else
if path_display:sub(-1, -1) ~= os_sep then
path_display = string.format("%s%s", path_display, os_sep)
end
end
end
if not opts.disable_devicons then
if is_dir then
icon = opts.dir_icon or ""
icon_hl = opts.dir_icon_hl or "Default"
else
icon, icon_hl = utils.get_devicons(entry.value, opts.disable_devicons)
icon = icon ~= "" and icon or " "
end
table.insert(widths, { width = strings.strdisplaywidth(icon) })
table.insert(display_array, { icon, icon_hl })
end
if opts.git_status then
if entry.value == parent_dir then
table.insert(widths, { width = 2 })
table.insert(display_array, " ")
else
table.insert(widths, { width = 2 })
table.insert(display_array, entry.git_status)
end
end
if entry.lstat.type == "link" then
path_display = string.format("%s -> %s", path_display, utils.transform_path(opts, entry.realpath))
end
local file_width = vim.F.if_nil(opts.file_width, math.max(15, total_file_width))
-- TODO maybe this can be dealt with more cleanly
if #path_display > file_width then
path_display = strings.truncate(path_display, file_width, nil, -1)
end
path_display = is_dir and { path_display, "TelescopePreviewDirectory" } or path_display
table.insert(display_array, entry.stat and path_display or { path_display, "WarningMsg" })
table.insert(widths, { width = file_width })
-- stat may be false meaning file not found / unavailable, e.g. broken symlink
if entry.stat and opts.display_stat then
for _, stat in ipairs { "mode", "size", "date" } do
local v = opts.display_stat[stat]
if v then
table.insert(widths, { width = v.width, right_justify = v.right_justify })
table.insert(display_array, v.display(entry))
end
end
end
-- original prompt bufnr becomes invalid with `:Telescope resume`
if not vim.api.nvim_buf_is_valid(prompt_bufnr) then
prompt_bufnr = get_fb_prompt()
end
local displayer = entry_display.create {
separator = sep,
items = widths,
prompt_bufnr = prompt_bufnr,
}
return displayer(display_array)
end
mt.__index = function(t, k)
local raw = rawget(mt, k)
if raw then
return raw
end
if k == "git_status" then
local git_status
if t.Path:is_dir() then
if opts.git_file_status and not vim.tbl_isempty(opts.git_file_status) then
for key, value in pairs(opts.git_file_status) do
if key:sub(1, #t.value) == t.value then
git_status = value
break
end
end
end
else
git_status = vim.F.if_nil(opts.git_file_status[t.value], " ")
end
return fb_git.make_display(opts, git_status)
end
if k == "Path" then
t.Path = Path:new(t.value)
return t.Path
end
if k == "path" then
return t.value
end
if k == "stat" then
t.stat = vim.F.if_nil(vim.loop.fs_stat(t.value), false)
if not t.stat then
return t.lstat
end
return t.stat
end
if k == "lstat" then
local lstat = vim.F.if_nil(vim.loop.fs_lstat(t.value), false)
if not lstat then
log.warn("Unable to get stat for " .. t.value)
t.lstat = false
else
t.lstat = lstat
end
return t.lstat
end
if k == "realpath" then
t.realpath = vim.F.if_nil(vim.loop.fs_realpath(t.value))
return t.realpath
end
return rawget(t, rawget({ value = 1 }, k))
end
return function(absolute_path)
local e = setmetatable({
absolute_path,
ordinal = (absolute_path == opts.cwd and ".")
or (absolute_path == parent_dir and ".." or absolute_path:sub(cwd_substr, -1)),
}, mt)
-- telescope-file-browser has to cache the entries to resolve multi-selections
-- across multiple folders
local cached_entry = opts.entry_cache[absolute_path]
if cached_entry ~= nil then
-- update the entry in-place to keep multi selections in tact
cached_entry.ordinal = e.ordinal
cached_entry.display = e.display
cached_entry.cwd = opts.cwd
return cached_entry
end
opts.entry_cache[absolute_path] = e
return e -- entry
end
end
return make_entry