Meh I'll figure out submodules later
This commit is contained in:
parent
4ca9d44a90
commit
8cb281f436
352 changed files with 66107 additions and 0 deletions
|
|
@ -0,0 +1,130 @@
|
|||
local parser = require("neo-tree.command.parser")
|
||||
local utils = require("neo-tree.utils")
|
||||
|
||||
local M = {
|
||||
show_key_value_completions = true,
|
||||
}
|
||||
|
||||
---@param key_prefix string?
|
||||
---@param base_path string
|
||||
---@return string paths_string
|
||||
local get_path_completions = function(key_prefix, base_path)
|
||||
key_prefix = key_prefix or ""
|
||||
local completions = {}
|
||||
local expanded = parser.resolve_path(base_path)
|
||||
local path_completions = vim.fn.glob(expanded .. "*", false, true)
|
||||
for _, completion in ipairs(path_completions) do
|
||||
if expanded ~= base_path then
|
||||
-- we need to recreate the relative path from the aboluste path
|
||||
-- first strip trailing slashes to normalize
|
||||
if expanded:sub(-1) == utils.path_separator then
|
||||
expanded = expanded:sub(1, -2)
|
||||
end
|
||||
if base_path:sub(-1) == utils.path_separator then
|
||||
base_path = base_path:sub(1, -2)
|
||||
end
|
||||
-- now put just the current completion onto the base_path being used
|
||||
completion = base_path .. string.sub(completion, #expanded + 1)
|
||||
end
|
||||
table.insert(completions, key_prefix .. completion)
|
||||
end
|
||||
|
||||
return table.concat(completions, "\n")
|
||||
end
|
||||
|
||||
---@param key_prefix string?
|
||||
---@return string references_string
|
||||
local get_ref_completions = function(key_prefix)
|
||||
key_prefix = key_prefix or ""
|
||||
local completions = { key_prefix .. "HEAD" }
|
||||
local ok, refs = utils.execute_command("git show-ref")
|
||||
if not ok then
|
||||
return ""
|
||||
end
|
||||
for _, ref in ipairs(refs) do
|
||||
local _, i = ref:find("refs%/%a+%/")
|
||||
if i then
|
||||
table.insert(completions, key_prefix .. ref:sub(i + 1))
|
||||
end
|
||||
end
|
||||
|
||||
return table.concat(completions, "\n")
|
||||
end
|
||||
|
||||
---@param argLead string
|
||||
---@param cmdLine string
|
||||
---@return string candidates_string
|
||||
M.complete_args = function(argLead, cmdLine)
|
||||
local candidates = {}
|
||||
local existing = utils.split(cmdLine, " ")
|
||||
local parsed = parser.parse(existing, false)
|
||||
|
||||
local eq = string.find(argLead, "=")
|
||||
if eq == nil then
|
||||
if M.show_key_value_completions then
|
||||
-- may be the start of a new key=value pair
|
||||
for _, key in ipairs(parser.list_args) do
|
||||
key = tostring(key)
|
||||
if key:find(argLead, 1, true) and not parsed[key] then
|
||||
table.insert(candidates, key .. "=")
|
||||
end
|
||||
end
|
||||
|
||||
for _, key in ipairs(parser.path_args) do
|
||||
key = tostring(key)
|
||||
if key:find(argLead, 1, true) and not parsed[key] then
|
||||
table.insert(candidates, key .. "=./")
|
||||
end
|
||||
end
|
||||
|
||||
for _, key in ipairs(parser.ref_args) do
|
||||
key = tostring(key)
|
||||
if key:find(argLead, 1, true) and not parsed[key] then
|
||||
table.insert(candidates, key .. "=")
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
-- continuation of a key=value pair
|
||||
local key = string.sub(argLead, 1, eq - 1)
|
||||
local value = string.sub(argLead, eq + 1)
|
||||
local arg_type = parser.argtype_lookup[key]
|
||||
if arg_type == parser.argtypes.PATH then
|
||||
return get_path_completions(key .. "=", value)
|
||||
elseif arg_type == parser.argtypes.REF then
|
||||
return get_ref_completions(key .. "=")
|
||||
elseif arg_type == parser.argtypes.LIST then
|
||||
local valid_values = parser.arguments[key].values
|
||||
if valid_values and not (parsed[key] and #parsed[key] > 0) then
|
||||
for _, vv in ipairs(valid_values) do
|
||||
if vv:find(value, 1, true) then
|
||||
table.insert(candidates, key .. "=" .. vv)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- may be a value without a key
|
||||
for value, key in pairs(parser.reverse_lookup) do
|
||||
value = tostring(value)
|
||||
local key_already_used = false
|
||||
if parser.argtype_lookup[key] == parser.argtypes.LIST then
|
||||
key_already_used = type(parsed[key]) ~= "nil"
|
||||
else
|
||||
key_already_used = type(parsed[value]) ~= "nil"
|
||||
end
|
||||
|
||||
if not key_already_used and value:find(argLead, 1, true) then
|
||||
table.insert(candidates, value)
|
||||
end
|
||||
end
|
||||
|
||||
if #candidates == 0 then
|
||||
-- default to path completion
|
||||
return get_path_completions(nil, argLead) .. "\n" .. get_ref_completions(nil)
|
||||
end
|
||||
return table.concat(candidates, "\n")
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
@ -0,0 +1,245 @@
|
|||
local parser = require("neo-tree.command.parser")
|
||||
local log = require("neo-tree.log")
|
||||
local manager = require("neo-tree.sources.manager")
|
||||
local utils = require("neo-tree.utils")
|
||||
local renderer = require("neo-tree.ui.renderer")
|
||||
local inputs = require("neo-tree.ui.inputs")
|
||||
local completion = require("neo-tree.command.completion")
|
||||
local do_show_or_focus, handle_reveal
|
||||
|
||||
local M = {
|
||||
complete_args = completion.complete_args,
|
||||
}
|
||||
|
||||
-- Store the last source used for `M.execute`
|
||||
M._last = {
|
||||
source = nil,
|
||||
position = nil,
|
||||
}
|
||||
|
||||
---Executes a Neo-tree action from outside of a Neo-tree window,
|
||||
---such as show, hide, navigate, etc.
|
||||
---@param args table The action to execute. The table can have the following keys:
|
||||
--- action = string The action to execute, can be one of:
|
||||
--- "close",
|
||||
--- "focus", <-- default value
|
||||
--- "show",
|
||||
--- source = string The source to use for this action. This will default
|
||||
--- to the default_source specified in the user's config.
|
||||
--- Can be one of:
|
||||
--- "filesystem",
|
||||
--- "buffers",
|
||||
--- "git_status",
|
||||
-- "migrations"
|
||||
--- position = string The position this action will affect. This will default
|
||||
--- to the the last used position or the position specified
|
||||
--- in the user's config for the given source. Can be one of:
|
||||
--- "left",
|
||||
--- "right",
|
||||
--- "float",
|
||||
--- "current"
|
||||
--- toggle = boolean Whether to toggle the visibility of the Neo-tree window.
|
||||
--- reveal = boolean Whether to reveal the current file in the Neo-tree window.
|
||||
--- reveal_file = string The specific file to reveal.
|
||||
--- dir = string The root directory to set.
|
||||
--- git_base = string The git base used for diff
|
||||
M.execute = function(args)
|
||||
local nt = require("neo-tree")
|
||||
nt.ensure_config()
|
||||
|
||||
if args.source == "migrations" then
|
||||
require("neo-tree.setup.deprecations").show_migrations()
|
||||
return
|
||||
end
|
||||
|
||||
args.action = args.action or "focus"
|
||||
|
||||
-- handle close action, which can specify a source and/or position
|
||||
if args.action == "close" then
|
||||
if args.source then
|
||||
manager.close(args.source, args.position)
|
||||
else
|
||||
manager.close_all(args.position)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
-- The rest of the actions require a source
|
||||
args.source = args.source or nt.config.default_source
|
||||
|
||||
-- Handle source=last
|
||||
if args.source == "last" then
|
||||
args.source = M._last.source or nt.config.default_source
|
||||
|
||||
-- Restore last position if it was not specified
|
||||
if args.position == nil then
|
||||
args.position = M._last.position
|
||||
end
|
||||
|
||||
-- Prevent the default source from being set to "last"
|
||||
if args.source == "last" then
|
||||
args.source = nt.config.sources[1]
|
||||
end
|
||||
end
|
||||
M._last.source = args.source
|
||||
M._last.position = args.position
|
||||
|
||||
-- If position=current was requested, but we are currently in a neo-tree window,
|
||||
-- then we need to override that.
|
||||
if args.position == "current" and vim.bo.filetype == "neo-tree" then
|
||||
local position = vim.api.nvim_buf_get_var(0, "neo_tree_position")
|
||||
if position then
|
||||
args.position = position
|
||||
end
|
||||
end
|
||||
|
||||
-- Now get the correct state
|
||||
---@type neotree.State
|
||||
local state
|
||||
local requested_position = args.position or nt.config[args.source].window.position
|
||||
if requested_position == "current" then
|
||||
local winid = vim.api.nvim_get_current_win()
|
||||
state = manager.get_state(args.source, nil, winid)
|
||||
else
|
||||
state = manager.get_state(args.source, nil, nil)
|
||||
end
|
||||
|
||||
-- Next handle toggle, the rest is irrelevant if there is a window to toggle
|
||||
if args.toggle then
|
||||
if renderer.close(state) then
|
||||
-- It was open, and now it's not.
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- Handle position override
|
||||
local default_position = nt.config[args.source].window.position
|
||||
local current_position = state.current_position or default_position
|
||||
local position_changed = false
|
||||
if args.position then
|
||||
state.current_position = args.position
|
||||
position_changed = args.position ~= current_position
|
||||
end
|
||||
|
||||
-- Handle setting directory if requested
|
||||
local path_changed = false
|
||||
if utils.truthy(args.dir) then
|
||||
-- Root paths on Windows have 3 characters ("C:\")
|
||||
local root_len = vim.fn.has("win32") == 1 and 3 or 1
|
||||
if #args.dir > root_len and args.dir:sub(-1) == utils.path_separator then
|
||||
args.dir = args.dir:sub(1, -2)
|
||||
end
|
||||
path_changed = state.path ~= args.dir
|
||||
end
|
||||
|
||||
-- Handle setting git ref
|
||||
local git_base_changed = state.git_base ~= args.git_base
|
||||
if utils.truthy(args.git_base) then
|
||||
state.git_base = args.git_base
|
||||
end
|
||||
|
||||
-- Handle source selector option
|
||||
state.enable_source_selector = args.selector
|
||||
|
||||
-- Handle reveal logic
|
||||
args.reveal = args.reveal or args.reveal_force_cwd
|
||||
local do_reveal = utils.truthy(args.reveal_file)
|
||||
if args.reveal and not do_reveal then
|
||||
args.reveal_file = manager.get_path_to_reveal()
|
||||
do_reveal = utils.truthy(args.reveal_file)
|
||||
end
|
||||
|
||||
-- All set, now show or focus the window
|
||||
local force_navigate = path_changed or do_reveal or git_base_changed or state.dirty
|
||||
--if position_changed and args.position ~= "current" and current_position ~= "current" then
|
||||
-- manager.close(args.source)
|
||||
--end
|
||||
if do_reveal then
|
||||
handle_reveal(args, state)
|
||||
return
|
||||
end
|
||||
if not args.dir then
|
||||
args.dir = state.path
|
||||
end
|
||||
do_show_or_focus(args, state, force_navigate)
|
||||
end
|
||||
|
||||
---Parses and executes the command line. Use execute(args) instead.
|
||||
---@param ... string Argument as strings.
|
||||
M._command = function(...)
|
||||
local args = parser.parse({ ... }, true)
|
||||
M.execute(args)
|
||||
end
|
||||
|
||||
do_show_or_focus = function(args, state, force_navigate)
|
||||
local window_exists = renderer.window_exists(state)
|
||||
local function close_other_sources()
|
||||
if not window_exists then
|
||||
-- Clear the space in case another source is already open
|
||||
local target_position = args.position or state.current_position or state.window.position
|
||||
if target_position ~= "current" then
|
||||
manager.close_all(target_position)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if args.action == "show" then
|
||||
-- "show" means show the window without focusing it
|
||||
if window_exists and not force_navigate then
|
||||
-- There's nothing to do here, we are already at the target state
|
||||
return
|
||||
end
|
||||
-- close_other_sources()
|
||||
local current_win = vim.api.nvim_get_current_win()
|
||||
manager.navigate(state, args.dir, args.reveal_file, function()
|
||||
-- navigate changes the window to neo-tree, so just quickly hop back to the original window
|
||||
vim.api.nvim_set_current_win(current_win)
|
||||
end, false)
|
||||
elseif args.action == "focus" then
|
||||
-- "focus" mean open and jump to the window if closed, and just focus it if already opened
|
||||
if window_exists then
|
||||
vim.api.nvim_set_current_win(state.winid)
|
||||
end
|
||||
if force_navigate or not window_exists then
|
||||
-- close_other_sources()
|
||||
manager.navigate(state, args.dir, args.reveal_file, nil, false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
handle_reveal = function(args, state)
|
||||
args.reveal_file = utils.normalize_path(args.reveal_file)
|
||||
-- Deal with cwd if we need to
|
||||
local cwd = args.dir or state.path or manager.get_cwd(state)
|
||||
if utils.is_subpath(cwd, args.reveal_file) then
|
||||
args.dir = cwd
|
||||
do_show_or_focus(args, state, true)
|
||||
return
|
||||
end
|
||||
|
||||
local reveal_file_parent, _ = utils.split_path(args.reveal_file) --[[@as string]]
|
||||
if args.reveal_force_cwd then
|
||||
args.dir = reveal_file_parent
|
||||
do_show_or_focus(args, state, true)
|
||||
return
|
||||
end
|
||||
|
||||
-- if dir doesn't have the reveal_file, ignore the reveal_file
|
||||
if args.dir then
|
||||
args.reveal_file = nil
|
||||
do_show_or_focus(args, state, true)
|
||||
return
|
||||
end
|
||||
|
||||
-- force was not specified and the file does not belong to cwd, so we need to ask the user
|
||||
inputs.confirm("File not in cwd. Change cwd to " .. reveal_file_parent .. "?", function(response)
|
||||
if response == true then
|
||||
args.dir = reveal_file_parent
|
||||
else
|
||||
args.reveal_file = nil
|
||||
end
|
||||
do_show_or_focus(args, state, true)
|
||||
end)
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
@ -0,0 +1,208 @@
|
|||
local uv = vim.uv or vim.loop
|
||||
local utils = require("neo-tree.utils")
|
||||
local _compat = require("neo-tree.utils._compat")
|
||||
|
||||
---@enum neotree.command.ParserArgument.Type
|
||||
local argtype = {
|
||||
FLAG = "<FLAG>",
|
||||
LIST = "<LIST>",
|
||||
PATH = "<PATH>",
|
||||
REF = "<REF>",
|
||||
}
|
||||
|
||||
---@class neotree.command.Parser
|
||||
---@field argtypes table<string, neotree.command.ParserArgument.Type>
|
||||
local M = {
|
||||
argtypes = argtype,
|
||||
}
|
||||
|
||||
---@param all_source_names string[]
|
||||
M.setup = function(all_source_names)
|
||||
local source_names = vim.deepcopy(all_source_names, _compat.noref())
|
||||
table.insert(source_names, "migrations")
|
||||
|
||||
-- A special source referring to the last used source.
|
||||
table.insert(source_names, "last")
|
||||
|
||||
---@class neotree.command.ParserArgument
|
||||
---@field type neotree.command.ParserArgument.Type
|
||||
|
||||
-- For lists, the first value is the default value.
|
||||
---@class neotree.command.ParserArguments
|
||||
---@field [string] neotree.command.ParserArgument
|
||||
---@field values string[]
|
||||
local arguments = {
|
||||
action = {
|
||||
type = M.argtypes.LIST,
|
||||
values = {
|
||||
"close",
|
||||
"focus",
|
||||
"show",
|
||||
},
|
||||
},
|
||||
position = {
|
||||
type = M.argtypes.LIST,
|
||||
values = {
|
||||
"left",
|
||||
"right",
|
||||
"top",
|
||||
"bottom",
|
||||
"float",
|
||||
"current",
|
||||
},
|
||||
},
|
||||
source = {
|
||||
type = M.argtypes.LIST,
|
||||
values = source_names,
|
||||
},
|
||||
dir = { type = M.argtypes.PATH, stat_type = "directory" },
|
||||
reveal_file = { type = M.argtypes.PATH, stat_type = "file" },
|
||||
git_base = { type = M.argtypes.REF },
|
||||
toggle = { type = M.argtypes.FLAG },
|
||||
reveal = { type = M.argtypes.FLAG },
|
||||
reveal_force_cwd = { type = M.argtypes.FLAG },
|
||||
selector = { type = M.argtypes.FLAG },
|
||||
}
|
||||
|
||||
local arg_type_lookup = {}
|
||||
local list_args = {}
|
||||
local path_args = {}
|
||||
local ref_args = {}
|
||||
local flag_args = {}
|
||||
local reverse_lookup = {}
|
||||
for name, def in pairs(arguments) do
|
||||
arg_type_lookup[name] = def.type
|
||||
if def.type == M.argtypes.LIST then
|
||||
table.insert(list_args, name)
|
||||
for _, vv in ipairs(def.values) do
|
||||
reverse_lookup[tostring(vv)] = name
|
||||
end
|
||||
elseif def.type == M.argtypes.PATH then
|
||||
table.insert(path_args, name)
|
||||
elseif def.type == M.argtypes.FLAG then
|
||||
table.insert(flag_args, name)
|
||||
reverse_lookup[name] = M.argtypes.FLAG
|
||||
elseif def.type == M.argtypes.REF then
|
||||
table.insert(ref_args, name)
|
||||
else
|
||||
error("Unknown type: " .. def.type)
|
||||
end
|
||||
end
|
||||
|
||||
M.arguments = arguments
|
||||
M.list_args = list_args
|
||||
M.path_args = path_args
|
||||
M.ref_args = ref_args
|
||||
M.flag_args = flag_args
|
||||
M.argtype_lookup = arg_type_lookup
|
||||
M.reverse_lookup = reverse_lookup
|
||||
end
|
||||
|
||||
---@param path string
|
||||
---@param validate_type string?
|
||||
M.resolve_path = function(path, validate_type)
|
||||
path = vim.fs.normalize(path)
|
||||
local expanded = vim.fn.expand(path)
|
||||
local abs_path = vim.fn.fnamemodify(expanded, ":p")
|
||||
if validate_type then
|
||||
local stat = uv.fs_stat(abs_path)
|
||||
if not stat or stat.type ~= validate_type then
|
||||
error("Invalid path: " .. path .. " is not a " .. validate_type)
|
||||
end
|
||||
end
|
||||
return abs_path
|
||||
end
|
||||
|
||||
---@param ref string
|
||||
M.verify_git_ref = function(ref)
|
||||
local ok, _ = utils.execute_command("git rev-parse --verify " .. ref)
|
||||
return ok
|
||||
end
|
||||
|
||||
---@class neotree.command.Parser.Parsed
|
||||
---@field [string] string|boolean
|
||||
|
||||
---@param result neotree.command.Parser.Parsed
|
||||
---@param arg string
|
||||
local parse_arg = function(result, arg)
|
||||
if type(arg) ~= "string" then
|
||||
return
|
||||
end
|
||||
local eq = arg:find("=")
|
||||
if eq then
|
||||
local key = arg:sub(1, eq - 1)
|
||||
local value = arg:sub(eq + 1)
|
||||
local def = M.arguments[key]
|
||||
if not def.type then
|
||||
error("Invalid argument: " .. arg)
|
||||
end
|
||||
|
||||
if def.type == M.argtypes.PATH then
|
||||
result[key] = M.resolve_path(value, def.stat_type)
|
||||
elseif def.type == M.argtypes.FLAG then
|
||||
if value == "true" then
|
||||
result[key] = true
|
||||
elseif value == "false" then
|
||||
result[key] = false
|
||||
else
|
||||
error("Invalid value for " .. key .. ": " .. value)
|
||||
end
|
||||
elseif def.type == M.argtypes.REF then
|
||||
if not M.verify_git_ref(value) then
|
||||
error("Invalid value for " .. key .. ": " .. value)
|
||||
end
|
||||
result[key] = value
|
||||
else
|
||||
result[key] = value
|
||||
end
|
||||
else
|
||||
local value = arg
|
||||
local key = M.reverse_lookup[value]
|
||||
if key == nil then
|
||||
-- maybe it's a git ref
|
||||
if M.verify_git_ref(value) then
|
||||
result["git_base"] = value
|
||||
return
|
||||
end
|
||||
-- maybe it's a path
|
||||
local path = M.resolve_path(value)
|
||||
local stat = uv.fs_stat(path)
|
||||
if stat then
|
||||
if stat.type == "directory" then
|
||||
result["dir"] = path
|
||||
elseif stat.type == "file" then
|
||||
result["reveal_file"] = path
|
||||
end
|
||||
else
|
||||
error("Invalid argument: " .. arg)
|
||||
end
|
||||
elseif key == M.argtypes.FLAG then
|
||||
result[value] = true
|
||||
else
|
||||
result[key] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param args string|string[]
|
||||
---@param strict_checking boolean
|
||||
---@return neotree.command.Parser.Parsed parsed_args
|
||||
M.parse = function(args, strict_checking)
|
||||
require("neo-tree").ensure_config()
|
||||
local result = {}
|
||||
|
||||
if type(args) == "string" then
|
||||
args = utils.split(args, " ")
|
||||
end
|
||||
-- read args from user
|
||||
for _, arg in ipairs(args) do
|
||||
local success, err = pcall(parse_arg, result, arg)
|
||||
if strict_checking and not success then
|
||||
error(err)
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
return M
|
||||
Loading…
Add table
Add a link
Reference in a new issue