Meh I'll figure out submodules later

This commit is contained in:
mustard 2025-09-16 01:01:02 +02:00
parent 4ca9d44a90
commit 8cb281f436
352 changed files with 66107 additions and 0 deletions

View file

@ -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

View file

@ -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

View file

@ -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