-
-
Notifications
You must be signed in to change notification settings - Fork 86
/
utils.lua
261 lines (236 loc) · 7.53 KB
/
utils.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
local a = vim.api
local action_state = require "telescope.actions.state"
local utils = require "telescope.utils"
local Job = require "plenary.job"
local Path = require "plenary.path"
local os_sep = Path.path.sep
local truncate = require("plenary.strings").truncate
local fb_utils = {}
fb_utils.iswin = vim.loop.os_uname().sysname == "Windows_NT"
fb_utils.is_dir = function(path)
if Path.is_path(path) then
return path:is_dir()
end
return string.sub(path, -1, -1) == os_sep
end
-- TODO(fdschmidt93): support multi-selections better usptream
fb_utils.get_selected_files = function(prompt_bufnr, smart)
smart = vim.F.if_nil(smart, true)
local selected = {}
local current_picker = action_state.get_current_picker(prompt_bufnr)
local selections = current_picker:get_multi_selection()
if smart and vim.tbl_isempty(selections) then
table.insert(selected, action_state.get_selected_entry())
else
for _, selection in ipairs(selections) do
table.insert(selected, selection.Path)
end
end
selected = vim.tbl_map(function(entry)
return Path:new(entry)
end, selected)
return selected
end
--- Do `opts.cb` if `opts.cond` is met for any valid buf
fb_utils.buf_callback = function(opts)
local bufs = vim.api.nvim_list_bufs()
for _, buf in ipairs(bufs) do
if a.nvim_buf_is_valid(buf) then
if opts.cond(buf) then
opts.cb(buf)
end
end
end
end
fb_utils.rename_buf = function(old_name, new_name)
fb_utils.buf_callback {
cond = function(buf)
return a.nvim_buf_get_name(buf) == old_name
end,
cb = function(buf)
a.nvim_buf_set_name(buf, new_name)
vim.api.nvim_buf_call(buf, function()
vim.cmd "silent! w!"
end)
end,
}
end
fb_utils.rename_dir_buf = function(old_dir, new_dir)
local dir_len = #old_dir
fb_utils.buf_callback {
cond = function(buf)
return a.nvim_buf_get_name(buf):sub(1, dir_len) == old_dir
end,
cb = function(buf)
local buf_name = a.nvim_buf_get_name(buf)
local new_name = new_dir .. buf_name:sub(dir_len + 1)
a.nvim_buf_set_name(buf, new_name)
a.nvim_buf_call(buf, function()
vim.cmd "silent! w!"
end)
end,
}
end
local delete_buf = function(buf)
for _, winid in ipairs(vim.fn.win_findbuf(buf)) do
local new_buf = vim.api.nvim_create_buf(false, false)
vim.api.nvim_win_set_buf(winid, new_buf)
end
utils.buf_delete(buf)
end
fb_utils.delete_buf = function(buf_name)
fb_utils.buf_callback {
cond = function(buf)
return a.nvim_buf_get_name(buf) == buf_name
end,
cb = delete_buf,
}
end
fb_utils.delete_dir_buf = function(dir)
local dir_len = #dir
fb_utils.buf_callback {
cond = function(buf)
return a.nvim_buf_get_name(buf):sub(1, dir_len) == dir
end,
cb = delete_buf,
}
end
-- redraws prompt and results border contingent on picker status
fb_utils.redraw_border_title = function(current_picker)
local finder = current_picker.finder
if current_picker.prompt_border and not finder.prompt_title then
local new_title = finder.files and "File Browser" or "Folder Browser"
current_picker.prompt_border:change_title(new_title)
end
if current_picker.results_border and not finder.results_title then
local new_title
if finder.files or finder.cwd_to_path then
new_title = Path:new(finder.path):make_relative(vim.loop.cwd())
else
new_title = finder.cwd
end
local width = math.floor(a.nvim_win_get_width(current_picker.results_win) * 0.8)
new_title = truncate(new_title ~= os_sep and new_title .. os_sep or new_title, width, nil, -1)
current_picker.results_border:change_title(new_title)
end
end
fb_utils.relative_path_prefix = function(finder)
local prefix
if finder.prompt_path then
local path, _ = Path:new(finder.path):make_relative(finder.cwd):gsub(vim.fn.expand "~", "~")
if path:match "^%w" then
prefix = "./" .. path .. os_sep
else
prefix = path .. os_sep
end
end
return prefix
end
fb_utils.group_by_type = function(tbl)
table.sort(tbl, function(x, y)
local x_stat = vim.loop.fs_stat(x)
local y_stat = vim.loop.fs_stat(y)
-- guard against fs_stat returning nil on invalid files
local x_is_dir = x_stat and x_stat.type == "directory"
local y_is_dir = y_stat and y_stat.type == "directory"
-- if both are dir, "shorter" string of the two
if x_is_dir and y_is_dir then
return x < y
-- prefer directories
elseif x_is_dir and not y_is_dir then
return true
elseif not x_is_dir and y_is_dir then
return false
-- prefer "shorter" filenames
else
return x < y
end
end)
end
--- Telescope Wrapper around vim.notify
---@param funname string: name of the function that will be
---@param opts table: opts.level string, opts.msg string
fb_utils.notify = function(funname, opts)
-- avoid circular require
local fb_config = require "telescope._extensions.file_browser.config"
local quiet = vim.F.if_nil(opts.quiet, fb_config.values.quiet)
if not quiet then
local level = vim.log.levels[opts.level]
if not level then
error("Invalid error level", 2)
end
vim.notify(string.format("[file_browser.%s] %s", funname, opts.msg), level, {
title = "telescope-file-browser.nvim",
})
end
end
--- de-dupe os_sep and right trim os_sep nearly everywhere
--- exception for root dir path (`/` or `C:\`)
---@param path string
---@return string
fb_utils.sanitize_path_str = function(path)
path = path:gsub(os_sep .. os_sep, os_sep)
if iswin then
if path:match "^%w:\\$" then
return path
else
return (path:gsub("(.)\\$", "%1"))
end
end
return (path:gsub("(.)/$", "%1"))
end
local _get_selection_index = function(path, dir, results)
local path_dir = Path:new(path):parent():absolute()
path = fb_utils.sanitize_path_str(path)
if dir == path_dir then
for i, path_entry in ipairs(results) do
if fb_utils.sanitize_path_str(path_entry.value) == path then
return i
end
end
end
end
-- Sets the selection to absolute path if found in the currently opened folder in the file browser
fb_utils.selection_callback = function(current_picker, absolute_path)
current_picker._completion_callbacks = vim.F.if_nil(current_picker._completion_callbacks, {})
table.insert(current_picker._completion_callbacks, function(picker)
local finder = picker.finder
local dir = finder.files and finder.path or finder.cwd
local selection_index = _get_selection_index(absolute_path, dir, finder.results)
if selection_index and selection_index ~= 1 then
picker:set_selection(picker:get_row(selection_index))
end
table.remove(picker._completion_callbacks)
end)
end
fb_utils.to_absolute_path = function(str)
str = vim.fn.expand(str)
local path = Path:new(str)
if not path:exists() then
fb_utils.notify("to_absolute_path", { msg = string.format("Given path '%s' doesn't exist", path), level = "WARN" })
return nil
elseif not path:is_dir() then
fb_utils.notify(
"to_absolute_path",
{ msg = string.format("Given path '%s' is not a directory", path), level = "WARN" }
)
return nil
end
return path:absolute()
end
--- get job results and emit error if applicable
---@param command string
---@param args string[]
---@param cwd string?
---@return string[]
fb_utils.job = function(command, args, cwd)
cwd = cwd or vim.loop.cwd()
local job = Job:new { command = command, args = args, cwd = cwd }
local results = job:sync()
local err = job:stderr_result()
if #err > 0 then
error(table.concat(err, ""))
end
return results
end
return fb_utils