@@ -2742,19 +2742,125 @@ H.fs_actions_apply = function(fs_actions)
27422742 end
27432743end
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+
27452836H .fs_do = {}
27462837
27472838H .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
27582864end
27592865
27602866H .fs_do .copy = function (from , to )
@@ -2784,19 +2890,42 @@ H.fs_do.copy = function(from, to)
27842890end
27852891
27862892H .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
27942914end
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
28252963end
28262964
0 commit comments