You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

223 lines
5.5 KiB
Lua

local utils = require "fzf-lua.utils"
local string_byte = string.byte
local M = {}
M.separator = function()
return '/'
end
M.starts_with_separator = function(path)
return path:find("^"..M.separator()) == 1
end
M.starts_with_cwd = function(path)
return path:match("^."..M.separator()) ~= nil
end
M.strip_cwd_prefix = function(path)
return #path>1 and path[1] == '.' and path[2] == M.separator()
end
function M.tail(path)
local os_sep = string_byte(M.separator())
for i=#path,1,-1 do
if string_byte(path, i) == os_sep then
return path:sub(i+1)
end
end
return path
end
function M.extension(path)
for i=#path,1,-1 do
if string_byte(path, i) == 46 then
return path:sub(i+1)
end
end
return path
end
function M.to_matching_str(path)
return path:gsub('(%-)', '(%%-)'):gsub('(%.)', '(%%.)'):gsub('(%_)', '(%%_)')
end
function M.join(paths)
-- gsub to remove double separator
return table.concat(paths, M.separator()):gsub(
M.separator()..M.separator(), M.separator())
end
function M.split(path)
return path:gmatch('[^'..M.separator()..']+'..M.separator()..'?')
end
---Get the basename of the given path.
---@param path string
---@return string
function M.basename(path)
path = M.remove_trailing(path)
local i = path:match("^.*()" .. M.separator())
if not i then return path end
return path:sub(i + 1, #path)
end
---Get the path to the parent directory of the given path. Returns `nil` if the
---path has no parent.
---@param path string
---@param remove_trailing boolean
---@return string|nil
function M.parent(path, remove_trailing)
path = " " .. M.remove_trailing(path)
local i = path:match("^.+()" .. M.separator())
if not i then return nil end
path = path:sub(2, i)
if remove_trailing then
path = M.remove_trailing(path)
end
return path
end
---Get a path relative to another path.
---@param path string
---@param relative_to string
---@return string
function M.relative(path, relative_to)
local p, _ = path:gsub("^" .. M.to_matching_str(M.add_trailing(relative_to)), "")
return p
end
function M.is_relative(path, relative_to)
local p = path:match("^" .. M.to_matching_str(M.add_trailing(relative_to)))
return p ~= nil
end
function M.add_trailing(path)
if path:sub(-1) == M.separator() then
return path
end
return path..M.separator()
end
function M.remove_trailing(path)
local p, _ = path:gsub(M.separator()..'$', '')
return p
end
function M.shorten(path, max_length)
if string.len(path) > max_length - 1 then
path = path:sub(string.len(path) - max_length + 1, string.len(path))
local i = path:match("()" .. M.separator())
if not i then
return "" .. path
end
return "" .. path:sub(i, -1)
else
return path
end
end
local function lastIndexOf(haystack, needle)
local i=haystack:match(".*"..needle.."()")
if i==nil then return nil else return i-1 end
end
local function stripBeforeLastOccurrenceOf(str, sep)
local idx = lastIndexOf(str, sep) or 0
return str:sub(idx+1), idx
end
function M.entry_to_location(entry)
local uri, line, col = entry:match("^(.*://.*):(%d+):(%d+):")
line = line and tonumber(line-1) or 0
col = col and tonumber(col) or 1
return {
stripped = entry,
line = line+1,
col = col,
uri = uri,
range = {
start = {
line = line,
character = col,
}
}
}
end
function M.entry_to_file(entry, cwd)
-- Remove ansi coloring and prefixed icons
entry = utils.strip_ansi_coloring(entry)
local stripped, idx = stripBeforeLastOccurrenceOf(entry, utils.nbsp)
-- entries from 'buffers' contain '[<bufnr>]'
-- buffer placeholder always comes before the nbsp
local bufnr = idx>1 and entry:sub(1, idx):match("%[(%d+)") or nil
if not bufnr and stripped:match("^%a+://") then
-- Issue #195, when using nvim-jdtls
-- https://github.com/mfussenegger/nvim-jdtls
-- LSP entries inside .jar files appear as URIs
-- 'jdt://' which can then be opened with
-- 'vim.lsp.util.jump_to_location' or
-- 'lua require('jdtls').open_jdt_link(vim.fn.expand('jdt://...'))'
-- Convert to location item so we can use 'jump_to_location'
-- This can also work with any 'file://' prefixes
return M.entry_to_location(stripped)
end
local s = utils.strsplit(stripped, ":")
if not s[1] then return {} end
local file = s[1]
local line = tonumber(s[2])
local col = tonumber(s[3])
if cwd and #cwd>0 and not M.starts_with_separator(file) then
file = M.join({cwd, file})
stripped = M.join({cwd, stripped})
end
local terminal
if bufnr then
terminal = utils.is_term_buffer(bufnr)
if terminal then
file, line = stripped:match("([^:]+):(%d+)")
end
end
return {
stripped = stripped,
bufnr = tonumber(bufnr),
terminal = terminal,
path = file,
line = tonumber(line) or 1,
col = tonumber(col) or 1,
}
end
function M.git_cwd(cmd, cwd)
if not cwd then return cmd end
cwd = vim.fn.expand(cwd)
if type(cmd) == 'string' then
local arg_cwd = ("-C %s "):format(vim.fn.shellescape(cwd))
cmd = cmd:gsub("^git ", "git " .. arg_cwd)
else
cmd = utils.tbl_deep_clone(cmd)
table.insert(cmd, 2, "-C")
table.insert(cmd, 3, cwd)
end
return cmd
end
function M.is_git_repo(cwd, noerr)
return not not M.git_root(cwd, noerr)
end
function M.git_root(cwd, noerr)
local cmd = M.git_cwd({"git", "rev-parse", "--show-toplevel"}, cwd)
local output, err = utils.io_systemlist(cmd)
if err ~= 0 then
if not noerr then utils.info(unpack(output)) end
return nil
end
return output[1]
end
return M