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,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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
Loading…
Add table
Add a link
Reference in a new issue