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,136 @@
local utils = require("neo-tree.utils")
local M = {}
local migrations = {}
M.show_migrations = function()
if #migrations > 0 then
local content = {}
for _, message in ipairs(migrations) do
vim.list_extend(content, vim.split("\n## " .. message, "\n", { trimempty = false }))
end
local header = "# Neo-tree configuration has been updated. Please review the changes below."
table.insert(content, 1, header)
local buf = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_lines(buf, 0, -1, false, content)
vim.bo[buf].buftype = "nofile"
vim.bo[buf].bufhidden = "wipe"
vim.bo[buf].buflisted = false
vim.bo[buf].swapfile = false
vim.bo[buf].modifiable = false
vim.bo[buf].filetype = "markdown"
vim.api.nvim_buf_set_name(buf, "Neo-tree migrations")
vim.defer_fn(function()
vim.cmd(string.format("%ssplit", #content))
vim.api.nvim_win_set_buf(0, buf)
end, 100)
end
end
---@param config neotree.Config.Base
M.migrate = function(config)
migrations = {}
local moved = function(old, new, converter)
local existing = utils.get_value(config, old)
if type(existing) ~= "nil" then
if type(converter) == "function" then
existing = converter(existing)
end
utils.set_value(config, old, nil)
utils.set_value(config, new, existing)
migrations[#migrations + 1] =
string.format("The `%s` option has been deprecated, please use `%s` instead.", old, new)
end
end
local moved_inside = function(old, new_inside, converter)
local existing = utils.get_value(config, old)
if type(existing) ~= "nil" and type(existing) ~= "table" then
if type(converter) == "function" then
existing = converter(existing)
end
utils.set_value(config, old, {})
local new = old .. "." .. new_inside
utils.set_value(config, new, existing)
migrations[#migrations + 1] =
string.format("The `%s` option is replaced with a table, please move to `%s`.", old, new)
end
end
local removed = function(key, desc)
local value = utils.get_value(config, key)
if type(value) ~= "nil" then
utils.set_value(config, key, nil)
migrations[#migrations + 1] =
string.format("The `%s` option has been removed.\n%s", key, desc or "")
end
end
local renamed_value = function(key, old_value, new_value)
local value = utils.get_value(config, key)
if value == old_value then
utils.set_value(config, key, new_value)
migrations[#migrations + 1] =
string.format("The `%s=%s` option has been renamed to `%s`.", key, old_value, new_value)
end
end
local opposite = function(value)
return not value
end
local tab_to_source_migrator = function(labels)
local converted_sources = {}
for entry, label in pairs(labels) do
table.insert(converted_sources, { source = entry, display_name = label })
end
return converted_sources
end
moved("filesystem.filters", "filesystem.filtered_items")
moved("filesystem.filters.show_hidden", "filesystem.filtered_items.hide_dotfiles", opposite)
moved("filesystem.filters.respect_gitignore", "filesystem.filtered_items.hide_gitignored")
moved("open_files_do_not_replace_filetypes", "open_files_do_not_replace_types")
moved("source_selector.tab_labels", "source_selector.sources", tab_to_source_migrator)
removed("filesystem.filters.gitignore_source")
removed("filesystem.filter_items.gitignore_source")
renamed_value("filesystem.hijack_netrw_behavior", "open_split", "open_current")
for _, source in ipairs({ "filesystem", "buffers", "git_status" }) do
renamed_value(source .. "window.position", "split", "current")
end
moved_inside("filesystem.follow_current_file", "enabled")
moved_inside("buffers.follow_current_file", "enabled")
-- v3.x
removed("close_floats_on_escape_key")
-- v4.x
removed(
"enable_normal_mode_for_inputs",
[[
Please use `neo_tree_popup_input_ready` event instead and call `stopinsert` inside the handler.
<https://github.com/nvim-neo-tree/neo-tree.nvim/pull/1372>
See instructions in `:h neo-tree-events` for more details.
```lua
event_handlers = {
{
event = "neo_tree_popup_input_ready",
---@param args { bufnr: integer, winid: integer }
handler = function(args)
vim.cmd("stopinsert")
vim.keymap.set("i", "<esc>", vim.cmd.stopinsert, { noremap = true, buffer = args.bufnr })
end,
}
}
```
]]
)
return migrations
end
return M

View file

@ -0,0 +1,715 @@
local utils = require("neo-tree.utils")
local defaults = require("neo-tree.defaults")
local mapping_helper = require("neo-tree.setup.mapping-helper")
local events = require("neo-tree.events")
local log = require("neo-tree.log")
local file_nesting = require("neo-tree.sources.common.file-nesting")
local highlights = require("neo-tree.ui.highlights")
local manager = require("neo-tree.sources.manager")
local netrw = require("neo-tree.setup.netrw")
local hijack_cursor = require("neo-tree.sources.common.hijack_cursor")
local M = {}
---@param source_config { window: {mappings: neotree.Config.Window.Mappings} }
local normalize_mappings = function(source_config)
if source_config == nil then
return
end
local mappings = vim.tbl_get(source_config, { "window", "mappings" })
if mappings then
local fixed = mapping_helper.normalize_mappings(mappings)
source_config.window.mappings = fixed --[[@as neotree.Config.Window.Mappings]]
end
end
---@param source_config neotree.Config.Filesystem
local normalize_fuzzy_mappings = function(source_config)
if source_config == nil then
return
end
local mappings = source_config.window and source_config.window.fuzzy_finder_mappings
if mappings then
local fixed = mapping_helper.normalize_mappings(mappings)
source_config.window.fuzzy_finder_mappings = fixed --[[@as neotree.Config.FuzzyFinder.Mappings]]
end
end
local events_setup = false
local define_events = function()
if events_setup then
return
end
events.define_event(events.FS_EVENT, {
debounce_frequency = 100,
debounce_strategy = utils.debounce_strategy.CALL_LAST_ONLY,
})
local v = vim.version()
local diag_autocmd = "DiagnosticChanged"
if v.major < 1 and v.minor < 6 then
diag_autocmd = "User LspDiagnosticsChanged"
end
events.define_autocmd_event(events.VIM_DIAGNOSTIC_CHANGED, { diag_autocmd }, 500, function(args)
args.diagnostics_lookup = utils.get_diagnostic_counts()
return args
end)
local update_opened_buffers = function(args)
args.opened_buffers = utils.get_opened_buffers()
return args
end
events.define_autocmd_event(events.VIM_AFTER_SESSION_LOAD, { "SessionLoadPost" }, 200)
events.define_autocmd_event(events.VIM_BUFFER_ADDED, { "BufAdd" }, 200, update_opened_buffers)
events.define_autocmd_event(events.VIM_BUFFER_CHANGED, { "BufWritePost" }, 200)
events.define_autocmd_event(
events.VIM_BUFFER_DELETED,
{ "BufDelete" },
200,
update_opened_buffers
)
events.define_autocmd_event(events.VIM_BUFFER_ENTER, { "BufEnter", "BufWinEnter" }, 0)
events.define_autocmd_event(
events.VIM_BUFFER_MODIFIED_SET,
{ "BufModifiedSet" },
0,
update_opened_buffers
)
events.define_autocmd_event(events.VIM_COLORSCHEME, { "ColorScheme" }, 0)
events.define_autocmd_event(events.VIM_CURSOR_MOVED, { "CursorMoved" }, 100)
events.define_autocmd_event(events.VIM_DIR_CHANGED, { "DirChanged" }, 200, nil, true)
events.define_autocmd_event(events.VIM_INSERT_LEAVE, { "InsertLeave" }, 200)
events.define_autocmd_event(events.VIM_LEAVE, { "VimLeavePre" })
events.define_autocmd_event(events.VIM_RESIZED, { "VimResized" }, 100)
events.define_autocmd_event(events.VIM_TAB_CLOSED, { "TabClosed" })
events.define_autocmd_event(events.VIM_TERMINAL_ENTER, { "TermEnter" }, 0)
events.define_autocmd_event(events.VIM_TEXT_CHANGED_NORMAL, { "TextChanged" }, 200)
events.define_autocmd_event(events.VIM_WIN_CLOSED, { "WinClosed" })
events.define_autocmd_event(events.VIM_WIN_ENTER, { "WinEnter" }, 0, nil, true)
events.define_autocmd_event(events.GIT_EVENT, { "User FugitiveChanged" }, 100)
events.define_event(events.GIT_STATUS_CHANGED, { debounce_frequency = 0 })
events_setup = true
events.subscribe({
event = events.VIM_LEAVE,
handler = function()
events.clear_all_events()
end,
})
events.subscribe({
event = events.VIM_RESIZED,
handler = function()
require("neo-tree.ui.renderer").update_floating_window_layouts()
end,
})
end
local prior_window_options = {}
--- Store the current window options so we can restore them when we close the tree.
--- @param winid number | nil The window id to store the options for, defaults to current window
local store_local_window_settings = function(winid)
winid = winid or vim.api.nvim_get_current_win()
local neo_tree_settings_applied, _ =
pcall(vim.api.nvim_win_get_var, winid, "neo_tree_settings_applied")
if neo_tree_settings_applied then
-- don't store our own window settings
return
end
prior_window_options[tostring(winid)] = {
cursorline = vim.wo.cursorline,
cursorlineopt = vim.wo.cursorlineopt,
foldcolumn = vim.wo.foldcolumn,
wrap = vim.wo.wrap,
list = vim.wo.list,
spell = vim.wo.spell,
number = vim.wo.number,
relativenumber = vim.wo.relativenumber,
winhighlight = vim.wo.winhighlight,
}
end
--- Restore the window options for the current window
--- @param winid number | nil The window id to restore the options for, defaults to current window
local restore_local_window_settings = function(winid)
winid = winid or vim.api.nvim_get_current_win()
-- return local window settings to their prior values
local wo = prior_window_options[tostring(winid)]
if wo then
vim.wo.cursorline = wo.cursorline
vim.wo.cursorlineopt = wo.cursorlineopt
vim.wo.foldcolumn = wo.foldcolumn
vim.wo.wrap = wo.wrap
vim.wo.list = wo.list
vim.wo.spell = wo.spell
vim.wo.number = wo.number
vim.wo.relativenumber = wo.relativenumber
vim.wo.winhighlight = wo.winhighlight
log.debug("Window settings restored")
vim.api.nvim_win_set_var(0, "neo_tree_settings_applied", false)
else
log.debug("No window settings to restore")
end
end
local last_buffer_enter_filetype = nil
M.buffer_enter_event = function()
-- if it is a neo-tree window, just set local options
if vim.bo.filetype == "neo-tree" then
if last_buffer_enter_filetype == "neo-tree" then
-- we've switched to another neo-tree window
events.fire_event(events.NEO_TREE_BUFFER_LEAVE)
else
store_local_window_settings()
end
vim.cmd([[
setlocal cursorline
setlocal cursorlineopt=line
setlocal nowrap
setlocal nolist nospell nonumber norelativenumber
]])
local winhighlight =
"Normal:NeoTreeNormal,NormalNC:NeoTreeNormalNC,SignColumn:NeoTreeSignColumn,CursorLine:NeoTreeCursorLine,FloatBorder:NeoTreeFloatBorder,StatusLine:NeoTreeStatusLine,StatusLineNC:NeoTreeStatusLineNC,VertSplit:NeoTreeVertSplit,EndOfBuffer:NeoTreeEndOfBuffer"
if vim.version().minor >= 7 then
vim.cmd("setlocal winhighlight=" .. winhighlight .. ",WinSeparator:NeoTreeWinSeparator")
else
vim.cmd("setlocal winhighlight=" .. winhighlight)
end
events.fire_event(events.NEO_TREE_BUFFER_ENTER)
last_buffer_enter_filetype = vim.bo.filetype
vim.api.nvim_win_set_var(0, "neo_tree_settings_applied", true)
return
end
if vim.bo.filetype == "neo-tree-popup" then
vim.cmd([[
setlocal winhighlight=Normal:NeoTreeFloatNormal,FloatBorder:NeoTreeFloatBorder
setlocal nolist nospell nonumber norelativenumber
]])
events.fire_event(events.NEO_TREE_POPUP_BUFFER_ENTER)
last_buffer_enter_filetype = vim.bo.filetype
return
end
if last_buffer_enter_filetype == "neo-tree" then
events.fire_event(events.NEO_TREE_BUFFER_LEAVE)
end
if last_buffer_enter_filetype == "neo-tree-popup" then
events.fire_event(events.NEO_TREE_POPUP_BUFFER_LEAVE)
end
last_buffer_enter_filetype = vim.bo.filetype
-- if vim is trying to open a dir, then we hijack it
if netrw.hijack() then
return
end
-- For all others, make sure another buffer is not hijacking our window
-- ..but not if the position is "current"
local prior_buf = vim.fn.bufnr("#")
if prior_buf < 1 then
return
end
local prior_type = vim.bo[prior_buf].filetype
-- there is nothing more we want to do with floating windows
-- but when prior_type is neo-tree we might need to redirect buffer somewhere else.
if utils.is_floating() and prior_type ~= "neo-tree" then
return
end
if prior_type == "neo-tree" then
local success, position = pcall(vim.api.nvim_buf_get_var, prior_buf, "neo_tree_position")
if not success then
-- just bail out now, the rest of these lookups will probably fail too.
return
end
if position == "current" then
-- nothing to do here, files are supposed to open in same window
return
end
local current_tabid = vim.api.nvim_get_current_tabpage()
local neo_tree_tabid = vim.api.nvim_buf_get_var(prior_buf, "neo_tree_tabid")
if neo_tree_tabid ~= current_tabid then
-- This a new tab, so the alternate being neo-tree doesn't matter.
return
end
local neo_tree_winid = vim.api.nvim_buf_get_var(prior_buf, "neo_tree_winid")
local current_winid = vim.api.nvim_get_current_win()
if neo_tree_winid ~= current_winid then
-- This is not the neo-tree window, so the alternate being neo-tree doesn't matter.
return
end
local bufname = vim.api.nvim_buf_get_name(0)
log.debug("redirecting buffer " .. bufname .. " to new split")
vim.cmd("b#")
local win_width = vim.api.nvim_win_get_width(current_winid)
-- Using schedule at this point fixes problem with syntax
-- highlighting in the buffer. I also prevents errors with diagnostics
-- trying to work with the buffer as it's being closed.
vim.schedule(function()
-- try to delete the buffer, only because if it was new it would take
-- on options from the neo-tree window that are undesirable.
---@diagnostic disable-next-line: param-type-mismatch
pcall(vim.cmd, "bdelete " .. bufname)
local fake_state = {
window = {
position = position,
width = win_width or M.config.window.width,
},
}
utils.open_file(fake_state, bufname)
end)
end
end
M.win_enter_event = function()
local win_id = vim.api.nvim_get_current_win()
if utils.is_floating(win_id) then
return
end
-- if the new win is not a floating window, make sure all neo-tree floats are closed
manager.close_all("float")
if vim.o.filetype == "neo-tree" then
local _, position = pcall(vim.api.nvim_buf_get_var, 0, "neo_tree_position")
if position == "current" then
-- make sure the buffer wasn't moved to a new window
local neo_tree_winid = vim.api.nvim_buf_get_var(0, "neo_tree_winid")
local current_winid = vim.api.nvim_get_current_win()
local current_bufnr = vim.api.nvim_get_current_buf()
if neo_tree_winid ~= current_winid then
-- At this point we know that either the neo-tree window was split,
-- or the neo-tree buffer is being shown in another window for some other reason.
-- Sometime the split is just the first step in the process of opening somethig else,
-- so instead of fixing this right away, we add a short delay and check back again to see
-- if the buffer is still in this window.
local old_state = manager.get_state("filesystem", nil, neo_tree_winid)
vim.schedule(function()
local bufnr = vim.api.nvim_get_current_buf()
if bufnr ~= current_bufnr then
-- The neo-tree buffer was replaced with something else, so we don't need to do anything.
log.trace("neo-tree buffer replaced with something else - no further action required")
return
end
-- create a new tree for this window
local state = manager.get_state("filesystem", nil, current_winid) --[[@as neotree.sources.filesystem.State]]
state.path = old_state.path
state.current_position = "current"
local renderer = require("neo-tree.ui.renderer")
state.force_open_folders = renderer.get_expanded_nodes(old_state.tree)
require("neo-tree.sources.filesystem")._navigate_internal(state, nil, nil, nil, false)
end)
return
end
end
-- it's a neo-tree window, ignore
return
end
end
M.set_log_level = function(level)
log.set_level(level)
end
local function merge_global_components_config(components, config)
local indent_exists = false
local merged_components = {}
local do_merge
do_merge = function(component)
local name = component[1]
if type(name) == "string" then
if name == "indent" then
indent_exists = true
end
local merged = { name }
local global_config = config.default_component_configs[name]
if global_config then
for k, v in pairs(global_config) do
merged[k] = v
end
end
for k, v in pairs(component) do
merged[k] = v
end
if name == "container" then
for i, child in ipairs(component.content) do
merged.content[i] = do_merge(child)
end
end
return merged
else
log.error("component name is the wrong type", component)
end
end
for _, component in ipairs(components) do
local merged = do_merge(component)
table.insert(merged_components, merged)
end
-- If the indent component is not specified, then add it.
-- We do this because it used to be implicitly added, so we don't want to
-- break any existing configs.
if not indent_exists then
local indent = { "indent" }
for k, v in pairs(config.default_component_configs.indent or {}) do
indent[k] = v
end
table.insert(merged_components, 1, indent)
end
return merged_components
end
local merge_renderers = function(default_config, source_default_config, user_config)
-- This can't be a deep copy/merge. If a renderer is specified in the target it completely
-- replaces the base renderer.
if source_default_config == nil then
-- first override the default config global renderer with the user's global renderers
for name, renderer in pairs(user_config.renderers or {}) do
log.debug("overriding global renderer for " .. name)
default_config.renderers[name] = renderer
end
else
-- then override the global renderers with the source specific renderers
source_default_config.renderers = source_default_config.renderers or {}
for name, renderer in pairs(default_config.renderers or {}) do
if source_default_config.renderers[name] == nil then
log.debug("overriding source renderer for " .. name)
local r = {}
-- Only copy components that exist in the target source.
-- This alllows us to specify global renderers that include components from all sources,
-- even if some of those components are not universal
for _, value in ipairs(renderer) do
if value[1] and source_default_config.components[value[1]] ~= nil then
table.insert(r, value)
end
end
source_default_config.renderers[name] = r
end
end
-- if user sets renderers, completely wipe the default ones
local source_name = source_default_config.name
for name, _ in pairs(source_default_config.renderers) do
local user = utils.get_value(user_config, source_name .. ".renderers." .. name)
if user then
source_default_config.renderers[name] = nil
end
end
end
end
---@param user_config neotree.Config?
---@return neotree.Config.Base full_config
M.merge_config = function(user_config)
local default_config = vim.deepcopy(defaults)
user_config = vim.deepcopy(user_config or {})
local migrations = require("neo-tree.setup.deprecations").migrate(user_config)
if #migrations > 0 then
-- defer to make sure it is the last message printed
vim.defer_fn(function()
vim.cmd(
"echohl WarningMsg | echo 'Some options have changed, please run `:Neotree migrations` to see the changes' | echohl NONE"
)
end, 50)
end
if user_config.log_level ~= nil then
M.set_log_level(user_config.log_level)
end
log.use_file(user_config.log_to_file, true)
log.debug("setup")
if events_setup then
events.clear_all_events()
end
define_events()
-- Prevent netrw hijacking lazy-loading from conflicting with normal hijacking.
vim.g.neotree_watching_bufenter = 1
-- Prevent accidentally opening another file in the neo-tree window.
events.subscribe({
event = events.VIM_BUFFER_ENTER,
handler = M.buffer_enter_event,
})
events.subscribe({
event = events.NEO_TREE_WINDOW_AFTER_OPEN,
handler = function(args)
if not vim.w[args.winid].neo_tree_settings_applied then
-- TODO: should figure out a less disorganized way to set window options
-- BufEnter doesn't trigger while vim is starting up so this will handle it instead.
M.buffer_enter_event()
end
end,
})
-- Setup autocmd for neo-tree BufLeave, to restore window settings.
-- This is set to happen just before leaving the window.
-- The patterns used should ensure it only runs in neo-tree windows where position = "current"
local augroup = vim.api.nvim_create_augroup("NeoTree_BufLeave", { clear = true })
local bufleave = function(data)
-- Vim patterns in autocmds are not quite precise enough
-- so we are doing a second stage filter in lua
local pattern = "neo%-tree [^ ]+ %[1%d%d%d%]"
if string.match(data.file, pattern) then
restore_local_window_settings()
end
end
vim.api.nvim_create_autocmd({ "BufWinLeave" }, {
group = augroup,
pattern = "neo-tree *",
callback = bufleave,
})
if user_config.event_handlers ~= nil then
for _, handler in ipairs(user_config.event_handlers) do
events.subscribe(handler)
end
end
highlights.setup()
-- used to either limit the sources that or loaded, or add extra external sources
local all_sources = {}
local all_source_names = {}
for _, source in ipairs(user_config.sources or default_config.sources or {}) do
local parts = utils.split(source, ".")
local name = parts[#parts]
local is_internal_ns, is_external_ns = false, false
local module
if #parts == 1 then
-- might be a module name in the internal namespace
is_internal_ns, module = pcall(require, "neo-tree.sources." .. source)
end
if is_internal_ns then
name = module.name or name
all_sources[name] = "neo-tree.sources." .. name
else
-- fully qualified module name
-- or just a root level module name
is_external_ns, module = pcall(require, source)
if is_external_ns then
name = module.name or name
all_sources[name] = source
else
log.error("Source module not found", source)
name = nil
end
end
if name then
default_config[name] = module.default_config or default_config[name]
table.insert(all_source_names, name)
end
end
log.debug("Sources to load: ", vim.inspect(all_sources))
require("neo-tree.command.parser").setup(all_source_names)
normalize_fuzzy_mappings(default_config.filesystem)
normalize_fuzzy_mappings(user_config.filesystem)
if user_config.use_default_mappings == false then
default_config.filesystem.window.fuzzy_finder_mappings = {}
end
-- setup the default values for all sources
normalize_mappings(default_config)
normalize_mappings(user_config)
merge_renderers(default_config, nil, user_config)
for source_name, mod_root in pairs(all_sources) do
local module = require(mod_root)
default_config[source_name] = default_config[source_name]
or {
renderers = {},
components = {},
}
local source_default_config = default_config[source_name]
source_default_config.components = module.components or require(mod_root .. ".components")
source_default_config.commands = module.commands or require(mod_root .. ".commands")
source_default_config.name = source_name
source_default_config.display_name = module.display_name or source_default_config.name
if user_config.use_default_mappings == false then
default_config.window.mappings = {}
source_default_config.window.mappings = {}
end
-- Make sure all the mappings are normalized so they will merge properly.
normalize_mappings(source_default_config)
normalize_mappings(user_config[source_name])
-- merge the global config with the source specific config
source_default_config.window = vim.tbl_deep_extend(
"force",
default_config.window or {},
source_default_config.window or {},
user_config.window or {}
)
merge_renderers(default_config, source_default_config, user_config)
--validate the window.position
local pos_key = source_name .. ".window.position"
local position = utils.get_value(user_config, pos_key, "left", true)
local valid_positions = {
left = true,
right = true,
top = true,
bottom = true,
float = true,
current = true,
}
if not valid_positions[position] then
log.error("Invalid value for ", pos_key, ": ", position)
user_config[source_name].window.position = "left"
end
end
-- local orig_sources = user_config.sources and user_config.sources or {}
-- apply the users config
M.config = vim.tbl_deep_extend("force", default_config, user_config)
-- RE: 873, fixes issue with invalid source checking by overriding
-- source table with name table
-- Setting new "sources" to be the parsed names of the sources
M.config.sources = all_source_names
if
(M.config.source_selector.winbar or M.config.source_selector.statusline)
and M.config.source_selector.sources
and not user_config.default_source
then
-- Set the default source to the head of these
-- This resolves some weirdness with the source selector having
-- a different "head" item than our current default.
-- Removing this line makes Neo-tree show the "filesystem"
-- source instead of whatever the first item in the config is.
-- Probably don't remove this unless you have a better fix for that
M.config.default_source = M.config.source_selector.sources[1].source
end
-- Check if the default source is not included in config.sources
-- log a warning and then "pick" the first in the sources list
local match = false
for _, source in ipairs(M.config.sources) do
if source == M.config.default_source then
match = true
break
end
end
if not match and M.config.default_source ~= "last" then
M.config.default_source = M.config.sources[1]
log.warn(
string.format(
"Invalid default source found in configuration. Using first available source: %s",
M.config.default_source
)
)
end
---@type neotree.Config.HijackNetrwBehavior[]
local disable_netrw_values = { "open_default", "open_current" }
local hijack_behavior = M.config.filesystem.hijack_netrw_behavior
if vim.tbl_contains(disable_netrw_values, hijack_behavior) then
-- Disable netrw autocmds
vim.cmd("silent! autocmd! FileExplorer *")
elseif hijack_behavior ~= "disabled" then
require("neo-tree.log").error(
"Invalid value for filesystem.hijack_netrw_behavior: '"
.. hijack_behavior
.. "', will default to 'disabled'"
)
M.config.filesystem.hijack_netrw_behavior = "disabled"
end
if not M.config.enable_git_status then
M.config.git_status_async = false
end
-- Validate that the source_selector.sources are all available and if any
-- aren't, remove them
local source_selector_sources = {}
for _, ss_source in ipairs(M.config.source_selector.sources or {}) do
if vim.tbl_contains(M.config.sources, ss_source.source) then
table.insert(source_selector_sources, ss_source)
else
log.debug(string.format("Unable to locate Neo-tree extension %s", ss_source.source))
end
end
M.config.source_selector.sources = source_selector_sources
file_nesting.setup(M.config.nesting_rules)
for source_name, mod_root in pairs(all_sources) do
for name, rndr in pairs(M.config[source_name].renderers) do
M.config[source_name].renderers[name] = merge_global_components_config(rndr, M.config)
end
local module = require(mod_root)
if M.config.commands then
M.config[source_name].commands =
vim.tbl_extend("keep", M.config[source_name].commands or {}, M.config.commands)
end
manager.setup(source_name, M.config[source_name] --[[@as table]], M.config, module)
manager.redraw(source_name)
end
events.subscribe({
event = events.VIM_COLORSCHEME,
handler = highlights.setup,
id = "neo-tree-highlight",
})
events.subscribe({
event = events.VIM_WIN_ENTER,
handler = M.win_enter_event,
id = "neo-tree-win-enter",
})
--Dispose ourselves if the tab closes
events.subscribe({
event = events.VIM_TAB_CLOSED,
handler = function(args)
local tabnr = tonumber(args.afile)
log.debug("VIM_TAB_CLOSED: disposing state for tabnr", tabnr)
-- Internally we use tabids to track state but <afile> is tabnr of a tab that has already been
-- closed so there is no way to get its tabid. Instead dispose all tabs that are no longer valid.
-- Must be scheduled because nvim_tabpage_is_valid does not work inside TabClosed event callback.
vim.schedule_wrap(manager.dispose_invalid_tabs)()
end,
})
--Dispose ourselves if the tab closes
events.subscribe({
event = events.VIM_WIN_CLOSED,
handler = function(args)
local winid = tonumber(args.afile)
if not winid then
return
end
log.debug("VIM_WIN_CLOSED: disposing state for window", winid)
manager.dispose_window(winid)
end,
})
local rt = utils.get_value(M.config, "resize_timer_interval", 50, true)
require("neo-tree.ui.renderer").resize_timer_interval = rt
if M.config.enable_cursor_hijack then
hijack_cursor.setup()
end
return M.config
end
return M

View file

@ -0,0 +1,76 @@
local utils = require("neo-tree.utils")
local M = {}
---@param key string
M.normalize_map_key = function(key)
if key == nil then
return nil
end
if key:match("^<[^>]+>$") then
local parts = utils.split(key, "-")
if #parts == 2 then
local mod = parts[1]:lower()
if mod == "<a" then
mod = "<m"
end
local alpha = parts[2]
if #alpha > 2 then
alpha = alpha:lower()
end
key = string.format("%s-%s", mod, alpha)
return key
else
key = key:lower()
if key == "<backspace>" then
return "<bs>"
elseif key == "<enter>" then
return "<cr>"
elseif key == "<return>" then
return "<cr>"
end
end
end
return key
end
---@class neotree.SimpleMappings
---@field [string] string|function?
---@class neotree.SimpleMappingsByMode
---@field [string] neotree.SimpleMappings?
---@class neotree.Mappings : neotree.SimpleMappings
---@field [integer] neotree.SimpleMappingsByMode?
---@param map neotree.Mappings
---@return neotree.Mappings new_map
M.normalize_mappings = function(map)
local new_map = M.normalize_simple_mappings(map)
---@cast new_map neotree.Mappings
for i, mappings_by_mode in ipairs(map) do
new_map[i] = {}
for mode, simple_mappings in pairs(mappings_by_mode) do
---@cast simple_mappings neotree.SimpleMappings
new_map[i][mode] = M.normalize_simple_mappings(simple_mappings)
end
end
return new_map
end
---@param map neotree.SimpleMappings
---@return neotree.SimpleMappings new_map
M.normalize_simple_mappings = function(map)
local new_map = {}
for key, value in pairs(map) do
if type(key) == "string" then
local normalized_key = M.normalize_map_key(key)
if normalized_key ~= nil then
new_map[normalized_key] = value
end
end
end
return new_map
end
return M

View file

@ -0,0 +1,102 @@
local uv = vim.uv or vim.loop
local nt = require("neo-tree")
local utils = require("neo-tree.utils")
local M = {}
local get_position = function(source_name)
local pos = utils.get_value(nt.config, source_name .. ".window.position", "left", true)
return pos
end
---@return neotree.Config.HijackNetrwBehavior
M.get_hijack_behavior = function()
nt.ensure_config()
return nt.config.filesystem.hijack_netrw_behavior
end
---@return boolean hijacked Whether the hijack was successful
M.hijack = function()
local hijack_behavior = M.get_hijack_behavior()
if hijack_behavior == "disabled" then
return false
end
-- ensure this is a directory
local dir_bufnr = vim.api.nvim_get_current_buf()
local path_to_hijack = vim.api.nvim_buf_get_name(dir_bufnr)
local stats = uv.fs_stat(path_to_hijack)
if not stats or stats.type ~= "directory" then
return false
end
-- record where we are now
local pos = get_position("filesystem")
local should_open_current = hijack_behavior == "open_current" or pos == "current"
local dir_window = vim.api.nvim_get_current_win()
-- Now actually open the tree, with a very quick debounce because this may be
-- called multiple times in quick succession.
utils.debounce("hijack_netrw_" .. dir_window, function()
local manager = require("neo-tree.sources.manager")
local log = require("neo-tree.log")
-- We will want to replace the "directory" buffer with either the "alternate"
-- buffer or a new blank one.
local replacement_buffer = vim.fn.bufnr("#")
local is_currently_neo_tree = false
if replacement_buffer > 0 then
if vim.bo[replacement_buffer].filetype == "neo-tree" then
-- don't hijack the current window if it's already a Neo-tree sidebar
local position = vim.b[replacement_buffer].neo_tree_position
if position == "current" then
replacement_buffer = -1
else
is_currently_neo_tree = true
end
end
end
if not should_open_current then
if replacement_buffer == dir_bufnr or replacement_buffer < 1 then
replacement_buffer = vim.api.nvim_create_buf(true, false)
log.trace("Created new buffer for netrw hijack", replacement_buffer)
end
end
if replacement_buffer > 0 then
log.trace("Replacing buffer in netrw hijack", replacement_buffer)
pcall(vim.api.nvim_win_set_buf, dir_window, replacement_buffer)
end
-- If a window takes focus (e.g. lazy.nvim installing plugins on startup) in the time between the method call and
-- this debounced callback, we should focus that window over neo-tree.
local current_window = vim.api.nvim_get_current_win()
local should_restore_cursor = current_window ~= dir_window
local cleanup = vim.schedule_wrap(function()
log.trace("Deleting buffer in netrw hijack", dir_bufnr)
pcall(vim.api.nvim_buf_delete, dir_bufnr, { force = true })
if should_restore_cursor then
vim.api.nvim_set_current_win(current_window)
end
end)
---@type neotree.sources.filesystem.State
local state
if should_open_current and not is_currently_neo_tree then
log.debug("hijack_netrw: opening current")
state = manager.get_state("filesystem", nil, dir_window) --[[@as neotree.sources.filesystem.State]]
state.current_position = "current"
elseif is_currently_neo_tree then
log.debug("hijack_netrw: opening in existing Neo-tree")
state = manager.get_state("filesystem") --[[@as neotree.sources.filesystem.State]]
else
log.debug("hijack_netrw: opening default")
manager.close_all_except("filesystem")
state = manager.get_state("filesystem") --[[@as neotree.sources.filesystem.State]]
end
require("neo-tree.sources.filesystem")._navigate_internal(state, path_to_hijack, nil, cleanup)
end, 10, utils.debounce_strategy.CALL_LAST_ONLY)
return true
end
return M