Skip to content

Commit 134f9dd

Browse files
committed
feat(files): make file system actions LSP aware
1 parent 3923662 commit 134f9dd

1 file changed

Lines changed: 147 additions & 9 deletions

File tree

lua/mini/files.lua

Lines changed: 147 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2742,19 +2742,125 @@ H.fs_actions_apply = function(fs_actions)
27422742
end
27432743
end
27442744

2745+
H.lsp_will_fs_do_methods = {
2746+
create = 'willCreate',
2747+
delete = 'willDelete',
2748+
rename = 'willRename',
2749+
}
2750+
2751+
H.lsp_will_fs_do = function(action, params)
2752+
local method = H.lsp_will_fs_do_methods[action]
2753+
local full_method = 'workspace/' .. method .. 'Files'
2754+
local clients = vim.lsp.get_clients({ method = full_method })
2755+
2756+
-- TODO(TheLeoP): configurable timeout
2757+
local timeout = 1000
2758+
for _, client in ipairs(clients) do
2759+
local filters = client.server_capabilities.workspace.fileOperations[method].filters --[=[@as lsp.FileOperationFilter[]]=]
2760+
local matching_functions = {}
2761+
for _, filter in ipairs(filters) do
2762+
local ignore_case = filter.pattern.options and filter.pattern.options.ignoreCase
2763+
local glob = filter.pattern.glob
2764+
if ignore_case then glob = glob:lower() end
2765+
2766+
table.insert(matching_functions, function(uri)
2767+
local path = vim.uri_to_fname(uri)
2768+
return vim.glob.to_lpeg(glob):match(path)
2769+
end)
2770+
if filter.scheme then
2771+
table.insert(matching_functions, function(uri) return uri:find('^' .. filter.scheme .. ':') ~= nil end)
2772+
end
2773+
end
2774+
2775+
local filtered_params = {
2776+
files = {},
2777+
}
2778+
for _, file in ipairs(params.files) do
2779+
local uri = file.uri or file.oldUri
2780+
local matches = true
2781+
for _, matching_function in ipairs(matching_functions) do
2782+
matches = matches and matching_function(uri)
2783+
end
2784+
if matches then table.insert(filtered_params.files, file) end
2785+
end
2786+
2787+
local response, err = client:request_sync(full_method, filtered_params, timeout)
2788+
if response and response.result then vim.lsp.util.apply_workspace_edit(response.result, client.offset_encoding) end
2789+
end
2790+
end
2791+
2792+
H.lsp_did_fs_do_methods = {
2793+
create = 'didCreate',
2794+
delete = 'didDelete',
2795+
rename = 'didRename',
2796+
}
2797+
2798+
H.lsp_did_fs_do = function(action, params)
2799+
local method = H.lsp_did_fs_do_methods[action]
2800+
local full_method = 'workspace/' .. method .. 'Files'
2801+
2802+
local clients = vim.lsp.get_clients({ method = full_method })
2803+
for _, client in ipairs(clients) do
2804+
local filters = client.server_capabilities.workspace.fileOperations[method].filters --[=[@as lsp.FileOperationFilter[]]=]
2805+
local matching_functions = {}
2806+
for _, filter in ipairs(filters) do
2807+
local ignore_case = filter.pattern.options and filter.pattern.options.ignoreCase
2808+
local glob = filter.pattern.glob
2809+
if ignore_case then glob = glob:lower() end
2810+
2811+
table.insert(matching_functions, function(uri)
2812+
local path = vim.uri_to_fname(uri)
2813+
return vim.glob.to_lpeg(glob):match(path)
2814+
end)
2815+
if filter.scheme then
2816+
table.insert(matching_functions, function(uri) return uri:find('^' .. filter.scheme .. ':') ~= nil end)
2817+
end
2818+
end
2819+
2820+
local filtered_params = {
2821+
files = {},
2822+
}
2823+
for _, file in ipairs(params.files) do
2824+
local uri = file.uri or file.oldUri
2825+
local matches = true
2826+
for _, matching_function in ipairs(matching_functions) do
2827+
matches = matches and matching_function(uri)
2828+
end
2829+
if matches then table.insert(filtered_params.files, file) end
2830+
end
2831+
2832+
client:notify(full_method, filtered_params)
2833+
end
2834+
end
2835+
27452836
H.fs_do = {}
27462837

27472838
H.fs_do.create = function(_, path)
27482839
-- Don't override existing path
27492840
if H.fs_is_present_path(path) then return H.warn_existing_path(path, 'create') end
27502841

2842+
-- TODO(TheLeoP): should we explicitly check permissions before willXxx
2843+
-- requests? It seems like Windows has different permissinon rules for
2844+
-- creating/deleting/moving files and `libuv` doesn't provide an absraction
2845+
-- for them
2846+
local lsp_params = {
2847+
files = { {
2848+
uri = vim.uri_from_fname(path),
2849+
} },
2850+
}
2851+
H.lsp_will_fs_do('create', lsp_params)
2852+
27512853
-- Create parent directory allowing nested names
27522854
vim.fn.mkdir(H.fs_get_parent(path), 'p')
27532855

27542856
-- Create
27552857
local fs_type = path:sub(-1) == '/' and 'directory' or 'file'
27562858
if fs_type == 'directory' then return vim.fn.mkdir(path) == 1 end
2757-
return vim.fn.writefile({}, path) == 0
2859+
local success = vim.fn.writefile({}, path) == 0
2860+
2861+
if success then H.lsp_did_fs_do('create', lsp_params) end
2862+
2863+
return success
27582864
end
27592865

27602866
H.fs_do.copy = function(from, to)
@@ -2784,19 +2890,42 @@ H.fs_do.copy = function(from, to)
27842890
end
27852891

27862892
H.fs_do.delete = function(from, to)
2893+
local lsp_params = { {
2894+
files = {
2895+
uri = vim.uri_from_fname(from),
2896+
},
2897+
} }
2898+
H.lsp_will_fs_do('delete', lsp_params)
2899+
27872900
-- Act based on whether delete is permanent or not
2788-
if to == nil then return vim.fn.delete(from, 'rf') == 0 end
2789-
pcall(vim.fn.delete, to, 'rf')
2790-
-- Move to trash but skip renaming loaded buffer (same as with permanent
2791-
-- delete). This also skips triggering extra autocommands that can have
2792-
-- unwanted side effects (like loading LSP in the new "trash" project).
2793-
return H.fs_do.move(from, to, true)
2901+
local success
2902+
if to == nil then success = vim.fn.delete(from, 'rf') == 0 end
2903+
if to ~= nil then
2904+
pcall(vim.fn.delete, to, 'rf')
2905+
-- Move to trash but skip renaming loaded buffer (same as with permanent
2906+
-- delete). This also skips triggering extra autocommands that can have
2907+
-- unwanted side effects (like loading LSP in the new "trash" project).
2908+
success = H.fs_do.move(from, to, true)
2909+
end
2910+
2911+
if success then H.lsp_did_fs_do('delete', lsp_params) end
2912+
2913+
return success
27942914
end
27952915

2796-
H.fs_do.move = function(from, to, skip_buf_rename)
2916+
H.fs_do.move = function(from, to, is_trash_delete)
27972917
-- Don't override existing path
27982918
if H.fs_is_present_path(to) then return H.warn_existing_path(from, 'move or rename') end
27992919

2920+
if not is_trash_delete then
2921+
H.lsp_will_fs_do('rename', {
2922+
files = { {
2923+
oldUri = vim.uri_from_fname(from),
2924+
newUri = vim.uri_from_fname(to),
2925+
} },
2926+
})
2927+
end
2928+
28002929
-- Move while allowing to create directory
28012930
vim.fn.mkdir(H.fs_get_parent(to), 'p')
28022931
local success, _, err_code = vim.loop.fs_rename(from, to)
@@ -2815,12 +2944,21 @@ H.fs_do.move = function(from, to, skip_buf_rename)
28152944
H.replace_path_in_index(from, to)
28162945

28172946
-- Rename in loaded buffers
2818-
if not skip_buf_rename then
2947+
if not is_trash_delete then
28192948
for _, buf_id in ipairs(vim.api.nvim_list_bufs()) do
28202949
H.rename_loaded_buffer(buf_id, from, to)
28212950
end
28222951
end
28232952

2953+
if not is_trash_delete then
2954+
H.lsp_did_fs_do('rename', {
2955+
files = { {
2956+
oldUri = vim.uri_from_fname(from),
2957+
newUri = vim.uri_from_fname(to),
2958+
} },
2959+
})
2960+
end
2961+
28242962
return success
28252963
end
28262964

0 commit comments

Comments
 (0)