feat: add support for LSP willRenameFiles (#184)
This commit is contained in:
parent
956d7fc89b
commit
8f3c1d2d2e
3 changed files with 195 additions and 0 deletions
|
|
@ -26,6 +26,13 @@ M.is_absolute = function(dir)
|
|||
end
|
||||
end
|
||||
|
||||
M.abspath = function(path)
|
||||
if not M.is_absolute(path) then
|
||||
path = vim.fn.fnamemodify(path, ":p")
|
||||
end
|
||||
return path
|
||||
end
|
||||
|
||||
---@param path string
|
||||
---@param cb fun(err: nil|string)
|
||||
M.touch = function(path, cb)
|
||||
|
|
@ -39,6 +46,38 @@ M.touch = function(path, cb)
|
|||
end)
|
||||
end
|
||||
|
||||
--- Returns true if candidate is a subpath of root, or if they are the same path.
|
||||
---@param root string
|
||||
---@param candidate string
|
||||
---@return boolean
|
||||
M.is_subpath = function(root, candidate)
|
||||
if candidate == "" then
|
||||
return false
|
||||
end
|
||||
root = vim.fs.normalize(M.abspath(root))
|
||||
-- Trim trailing "/" from the root
|
||||
if root:find("/", -1) then
|
||||
root = root:sub(1, -2)
|
||||
end
|
||||
candidate = vim.fs.normalize(M.abspath(candidate))
|
||||
if M.is_windows then
|
||||
root = root:lower()
|
||||
candidate = candidate:lower()
|
||||
end
|
||||
if root == candidate then
|
||||
return true
|
||||
end
|
||||
local prefix = candidate:sub(1, root:len())
|
||||
if prefix ~= root then
|
||||
return false
|
||||
end
|
||||
|
||||
local candidate_starts_with_sep = candidate:find("/", root:len() + 1, true) == root:len() + 1
|
||||
local root_ends_with_sep = root:find("/", root:len(), true) == root:len()
|
||||
|
||||
return candidate_starts_with_sep or root_ends_with_sep
|
||||
end
|
||||
|
||||
---@param path string
|
||||
---@return string
|
||||
M.posix_to_os_path = function(path)
|
||||
|
|
|
|||
142
lua/oil/lsp_helpers.lua
Normal file
142
lua/oil/lsp_helpers.lua
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
local fs = require("oil.fs")
|
||||
local util = require("oil.util")
|
||||
|
||||
local M = {}
|
||||
|
||||
---@param filepath string
|
||||
---@param pattern lsp.FileOperationPattern
|
||||
---@return boolean
|
||||
local function file_matches(filepath, pattern)
|
||||
local is_dir = vim.fn.isdirectory(filepath) == 1
|
||||
if pattern.matches then
|
||||
if (pattern.matches == "file" and is_dir) or (pattern.matches == "folder" and not is_dir) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
if vim.tbl_get(pattern, "options", "ignoreCase") then
|
||||
filepath = filepath:lower()
|
||||
pattern.glob = pattern.glob:lower()
|
||||
end
|
||||
|
||||
local pat = vim.fn.glob2regpat(pattern.glob)
|
||||
return vim.fn.match(filepath, pat) >= 0
|
||||
end
|
||||
|
||||
---@param filepath string
|
||||
---@param filters lsp.FileOperationFilter[]
|
||||
---@return boolean
|
||||
local function any_match(filepath, filters)
|
||||
for _, filter in ipairs(filters) do
|
||||
local scheme_match = not filter.scheme or filter.scheme == "file"
|
||||
if scheme_match and file_matches(filepath, filter.pattern) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
---@return nil|{src: string, dest: string}
|
||||
local function get_matching_paths(client, path_pairs)
|
||||
local filters =
|
||||
vim.tbl_get(client.server_capabilities, "workspace", "fileOperations", "willRename", "filters")
|
||||
if not filters then
|
||||
return nil
|
||||
end
|
||||
local ret = {}
|
||||
for _, pair in ipairs(path_pairs) do
|
||||
if fs.is_subpath(client.config.root_dir, pair.src) then
|
||||
local relative_file = pair.src:sub(client.config.root_dir:len() + 2)
|
||||
if any_match(relative_file, filters) then
|
||||
table.insert(ret, pair)
|
||||
end
|
||||
end
|
||||
end
|
||||
if vim.tbl_isempty(ret) then
|
||||
return nil
|
||||
else
|
||||
return ret
|
||||
end
|
||||
end
|
||||
|
||||
---Process LSP rename in the background
|
||||
---@param actions oil.MoveAction[]
|
||||
M.will_rename_files = function(actions)
|
||||
local path_pairs = {}
|
||||
for _, action in ipairs(actions) do
|
||||
local _, src_path = util.parse_url(action.src_url)
|
||||
assert(src_path)
|
||||
local src_file = fs.posix_to_os_path(src_path)
|
||||
local _, dest_path = util.parse_url(action.dest_url)
|
||||
assert(dest_path)
|
||||
local dest_file = fs.posix_to_os_path(dest_path)
|
||||
table.insert(path_pairs, { src = src_file, dest = dest_file })
|
||||
end
|
||||
|
||||
local clients = vim.lsp.get_active_clients()
|
||||
for _, client in ipairs(clients) do
|
||||
local pairs = get_matching_paths(client, path_pairs)
|
||||
if pairs then
|
||||
client.request("workspace/willRenameFiles", {
|
||||
files = vim.tbl_map(function(pair)
|
||||
return {
|
||||
oldUri = vim.uri_from_fname(pair.src),
|
||||
newUri = vim.uri_from_fname(pair.dest),
|
||||
}
|
||||
end, pairs),
|
||||
}, function(_, result)
|
||||
if result then
|
||||
vim.lsp.util.apply_workspace_edit(result, client.offset_encoding)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- LSP types from core Neovim
|
||||
|
||||
---A filter to describe in which file operation requests or notifications
|
||||
---the server is interested in receiving.
|
||||
---
|
||||
---@since 3.16.0
|
||||
---@class lsp.FileOperationFilter
|
||||
---A Uri scheme like `file` or `untitled`.
|
||||
---@field scheme? string
|
||||
---The actual file operation pattern.
|
||||
---@field pattern lsp.FileOperationPattern
|
||||
|
||||
---A pattern to describe in which file operation requests or notifications
|
||||
---the server is interested in receiving.
|
||||
---
|
||||
---@since 3.16.0
|
||||
---@class lsp.FileOperationPattern
|
||||
---The glob pattern to match. Glob patterns can have the following syntax:
|
||||
---- `*` to match one or more characters in a path segment
|
||||
---- `?` to match on one character in a path segment
|
||||
---- `**` to match any number of path segments, including none
|
||||
---- `{}` to group sub patterns into an OR expression. (e.g. `**/*.{ts,js}` matches all TypeScript and JavaScript files)
|
||||
---- `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …)
|
||||
---- `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`)
|
||||
---@field glob string
|
||||
---Whether to match files or folders with this pattern.
|
||||
---
|
||||
---Matches both if undefined.
|
||||
---@field matches? lsp.FileOperationPatternKind
|
||||
---Additional options used during matching.
|
||||
---@field options? lsp.FileOperationPatternOptions
|
||||
|
||||
---A pattern kind describing if a glob pattern matches a file a folder or
|
||||
---both.
|
||||
---
|
||||
---@since 3.16.0
|
||||
---@alias lsp.FileOperationPatternKind
|
||||
---| "file" # file
|
||||
---| "folder" # folder
|
||||
|
||||
---Matching options for the file operation pattern.
|
||||
---
|
||||
---@since 3.16.0
|
||||
---@class lsp.FileOperationPatternOptions
|
||||
---The pattern should be matched ignoring casing.
|
||||
---@field ignoreCase? boolean
|
||||
|
||||
return M
|
||||
|
|
@ -2,6 +2,7 @@ local cache = require("oil.cache")
|
|||
local columns = require("oil.columns")
|
||||
local config = require("oil.config")
|
||||
local constants = require("oil.constants")
|
||||
local lsp_helpers = require("oil.lsp_helpers")
|
||||
local oil = require("oil")
|
||||
local parser = require("oil.mutator.parser")
|
||||
local pathutil = require("oil.pathutil")
|
||||
|
|
@ -376,6 +377,19 @@ M.process_actions = function(actions, cb)
|
|||
end
|
||||
end
|
||||
|
||||
-- send all renames to LSP servers
|
||||
local moves = {}
|
||||
for _, action in ipairs(actions) do
|
||||
if action.type == "move" then
|
||||
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
||||
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
||||
if src_adapter.name == "files" and dest_adapter.name == "files" then
|
||||
table.insert(moves, action)
|
||||
end
|
||||
end
|
||||
end
|
||||
lsp_helpers.will_rename_files(moves)
|
||||
|
||||
-- Convert cross-adapter moves to a copy + delete
|
||||
for _, action in ipairs(actions) do
|
||||
if action.type == "move" then
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue