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,324 @@
|
|||
local log = require("neo-tree.log")
|
||||
local utils = require("neo-tree.utils")
|
||||
local M = {}
|
||||
|
||||
---@type integer
|
||||
M.ns_id = vim.api.nvim_create_namespace("neo-tree.nvim")
|
||||
|
||||
M.BUFFER_NUMBER = "NeoTreeBufferNumber"
|
||||
M.CURSOR_LINE = "NeoTreeCursorLine"
|
||||
M.DIM_TEXT = "NeoTreeDimText"
|
||||
M.DIRECTORY_ICON = "NeoTreeDirectoryIcon"
|
||||
M.DIRECTORY_NAME = "NeoTreeDirectoryName"
|
||||
M.DOTFILE = "NeoTreeDotfile"
|
||||
M.FADE_TEXT_1 = "NeoTreeFadeText1"
|
||||
M.FADE_TEXT_2 = "NeoTreeFadeText2"
|
||||
M.FILE_ICON = "NeoTreeFileIcon"
|
||||
M.FILE_NAME = "NeoTreeFileName"
|
||||
M.FILE_NAME_OPENED = "NeoTreeFileNameOpened"
|
||||
M.FILE_STATS = "NeoTreeFileStats"
|
||||
M.FILE_STATS_HEADER = "NeoTreeFileStatsHeader"
|
||||
M.FILTER_TERM = "NeoTreeFilterTerm"
|
||||
M.FLOAT_BORDER = "NeoTreeFloatBorder"
|
||||
M.FLOAT_NORMAL = "NeoTreeFloatNormal"
|
||||
M.FLOAT_TITLE = "NeoTreeFloatTitle"
|
||||
M.GIT_ADDED = "NeoTreeGitAdded"
|
||||
M.GIT_CONFLICT = "NeoTreeGitConflict"
|
||||
M.GIT_DELETED = "NeoTreeGitDeleted"
|
||||
M.GIT_IGNORED = "NeoTreeGitIgnored"
|
||||
M.GIT_MODIFIED = "NeoTreeGitModified"
|
||||
M.GIT_RENAMED = "NeoTreeGitRenamed"
|
||||
M.GIT_STAGED = "NeoTreeGitStaged"
|
||||
M.GIT_UNTRACKED = "NeoTreeGitUntracked"
|
||||
M.GIT_UNSTAGED = "NeoTreeGitUnstaged"
|
||||
M.HIDDEN_BY_NAME = "NeoTreeHiddenByName"
|
||||
M.INDENT_MARKER = "NeoTreeIndentMarker"
|
||||
M.MESSAGE = "NeoTreeMessage"
|
||||
M.MODIFIED = "NeoTreeModified"
|
||||
M.NORMAL = "NeoTreeNormal"
|
||||
M.NORMALNC = "NeoTreeNormalNC"
|
||||
M.SIGNCOLUMN = "NeoTreeSignColumn"
|
||||
M.STATUS_LINE = "NeoTreeStatusLine"
|
||||
M.STATUS_LINE_NC = "NeoTreeStatusLineNC"
|
||||
M.TAB_ACTIVE = "NeoTreeTabActive"
|
||||
M.TAB_INACTIVE = "NeoTreeTabInactive"
|
||||
M.TAB_SEPARATOR_ACTIVE = "NeoTreeTabSeparatorActive"
|
||||
M.TAB_SEPARATOR_INACTIVE = "NeoTreeTabSeparatorInactive"
|
||||
M.VERTSPLIT = "NeoTreeVertSplit"
|
||||
M.WINSEPARATOR = "NeoTreeWinSeparator"
|
||||
M.END_OF_BUFFER = "NeoTreeEndOfBuffer"
|
||||
M.ROOT_NAME = "NeoTreeRootName"
|
||||
M.SYMBOLIC_LINK_TARGET = "NeoTreeSymbolicLinkTarget"
|
||||
M.TITLE_BAR = "NeoTreeTitleBar"
|
||||
M.EXPANDER = "NeoTreeExpander"
|
||||
M.WINDOWS_HIDDEN = "NeoTreeWindowsHidden"
|
||||
M.PREVIEW = "NeoTreePreview"
|
||||
|
||||
---@param n integer
|
||||
---@param chars integer?
|
||||
local function dec_to_hex(n, chars)
|
||||
chars = chars or 6
|
||||
local hex = string.format("%0" .. chars .. "x", n)
|
||||
while #hex < chars do
|
||||
hex = "0" .. hex
|
||||
end
|
||||
return hex
|
||||
end
|
||||
|
||||
---@param name string
|
||||
local get_hl_by_name = function(name)
|
||||
if vim.api.nvim_get_hl then
|
||||
local hl = vim.api.nvim_get_hl(0, { name = name })
|
||||
---@diagnostic disable-next-line: inject-field
|
||||
hl.foreground = hl.fg
|
||||
---@diagnostic disable-next-line: inject-field
|
||||
hl.background = hl.bg
|
||||
return hl
|
||||
end
|
||||
---TODO: remove in 4.0
|
||||
---@diagnostic disable-next-line: deprecated
|
||||
return vim.api.nvim_get_hl_by_name(name, true)
|
||||
end
|
||||
---If the given highlight group is not defined, define it.
|
||||
---@param hl_group_name string The name of the highlight group.
|
||||
---@param link_to_if_exists string[] A list of highlight groups to link to, in order of priority. The first one that exists will be used.
|
||||
---@param background string? The background color to use, in hex, if the highlight group is not defined and it is not linked to another group.
|
||||
---@param foreground string? The foreground color to use, in hex, if the highlight group is not defined and it is not linked to another group.
|
||||
---@param gui string? The gui to use, if the highlight group is not defined and it is not linked to another group.
|
||||
---@return table hlgroups The highlight group values.
|
||||
M.create_highlight_group = function(hl_group_name, link_to_if_exists, background, foreground, gui)
|
||||
local success, hl_group = pcall(get_hl_by_name, hl_group_name, true)
|
||||
if not success or not hl_group.foreground or not hl_group.background then
|
||||
for _, link_to in ipairs(link_to_if_exists) do
|
||||
success, hl_group = pcall(get_hl_by_name, link_to, true)
|
||||
if success then
|
||||
local new_group_has_settings = background or foreground or gui
|
||||
local link_to_has_settings = hl_group.foreground or hl_group.background
|
||||
if link_to_has_settings or not new_group_has_settings then
|
||||
vim.cmd("highlight default link " .. hl_group_name .. " " .. link_to)
|
||||
return hl_group
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if type(background) == "number" then
|
||||
background = dec_to_hex(background)
|
||||
end
|
||||
if type(foreground) == "number" then
|
||||
foreground = dec_to_hex(foreground)
|
||||
end
|
||||
|
||||
local cmd = "highlight default " .. hl_group_name
|
||||
if background then
|
||||
cmd = cmd .. " guibg=#" .. background
|
||||
end
|
||||
if foreground then
|
||||
cmd = cmd .. " guifg=#" .. foreground
|
||||
else
|
||||
cmd = cmd .. " guifg=NONE"
|
||||
end
|
||||
if gui then
|
||||
cmd = cmd .. " gui=" .. gui
|
||||
end
|
||||
vim.cmd(cmd)
|
||||
|
||||
return {
|
||||
background = background and tonumber(background, 16) or nil,
|
||||
foreground = foreground and tonumber(foreground, 16) or nil,
|
||||
}
|
||||
end
|
||||
return hl_group
|
||||
end
|
||||
|
||||
---@param hl_group_name string
|
||||
---@param fade_percentage number
|
||||
local calculate_faded_highlight_group = function(hl_group_name, fade_percentage)
|
||||
local normal = get_hl_by_name("Normal")
|
||||
if type(normal.foreground) ~= "number" then
|
||||
if vim.go.background == "dark" then
|
||||
normal.foreground = 0xffffff
|
||||
else
|
||||
normal.foreground = 0x000000
|
||||
end
|
||||
end
|
||||
if type(normal.background) ~= "number" then
|
||||
if vim.go.background == "dark" then
|
||||
normal.background = 0x000000
|
||||
else
|
||||
normal.background = 0xffffff
|
||||
end
|
||||
end
|
||||
local foreground = dec_to_hex(normal.foreground)
|
||||
local background = dec_to_hex(normal.background)
|
||||
|
||||
local hl_group = get_hl_by_name(hl_group_name)
|
||||
if type(hl_group.foreground) == "number" then
|
||||
foreground = dec_to_hex(hl_group.foreground)
|
||||
end
|
||||
if type(hl_group.background) == "number" then
|
||||
background = dec_to_hex(hl_group.background)
|
||||
end
|
||||
|
||||
local gui = {}
|
||||
if hl_group.bold then
|
||||
table.insert(gui, "bold")
|
||||
end
|
||||
if hl_group.italic then
|
||||
table.insert(gui, "italic")
|
||||
end
|
||||
if hl_group.underline then
|
||||
table.insert(gui, "underline")
|
||||
end
|
||||
if hl_group.undercurl then
|
||||
table.insert(gui, "undercurl")
|
||||
end
|
||||
|
||||
local hl
|
||||
if #gui > 0 then
|
||||
hl = table.concat(gui, ",")
|
||||
end
|
||||
|
||||
local f_red = tonumber(foreground:sub(1, 2), 16)
|
||||
local f_green = tonumber(foreground:sub(3, 4), 16)
|
||||
local f_blue = tonumber(foreground:sub(5, 6), 16)
|
||||
|
||||
local b_red = tonumber(background:sub(1, 2), 16)
|
||||
local b_green = tonumber(background:sub(3, 4), 16)
|
||||
local b_blue = tonumber(background:sub(5, 6), 16)
|
||||
|
||||
local red = (f_red * fade_percentage) + (b_red * (1 - fade_percentage))
|
||||
local green = (f_green * fade_percentage) + (b_green * (1 - fade_percentage))
|
||||
local blue = (f_blue * fade_percentage) + (b_blue * (1 - fade_percentage))
|
||||
|
||||
local new_foreground =
|
||||
string.format("%s%s%s", dec_to_hex(red, 2), dec_to_hex(green, 2), dec_to_hex(blue, 2))
|
||||
|
||||
return {
|
||||
background = hl_group.background,
|
||||
foreground = new_foreground,
|
||||
gui = hl,
|
||||
}
|
||||
end
|
||||
|
||||
local faded_highlight_group_cache = {}
|
||||
---@param hl_group_name string
|
||||
---@param fade_percentage number
|
||||
M.get_faded_highlight_group = function(hl_group_name, fade_percentage)
|
||||
if type(hl_group_name) ~= "string" then
|
||||
error("hl_group_name must be a string")
|
||||
end
|
||||
if type(fade_percentage) ~= "number" then
|
||||
error("hl_group_name must be a number")
|
||||
end
|
||||
if fade_percentage < 0 or fade_percentage > 1 then
|
||||
error("fade_percentage must be between 0 and 1")
|
||||
end
|
||||
|
||||
local key = hl_group_name .. "_" .. tostring(math.floor(fade_percentage * 100))
|
||||
if faded_highlight_group_cache[key] then
|
||||
return faded_highlight_group_cache[key]
|
||||
end
|
||||
|
||||
local faded = calculate_faded_highlight_group(hl_group_name, fade_percentage)
|
||||
|
||||
M.create_highlight_group(key, {}, faded.background, faded.foreground, faded.gui)
|
||||
faded_highlight_group_cache[key] = key
|
||||
return key
|
||||
end
|
||||
local nvim_0_10 = vim.fn.has("nvim-0.10")
|
||||
M.setup = function()
|
||||
local added_hl_name = nvim_0_10 and "Added" or "diffAdded"
|
||||
local changed_hl_name = nvim_0_10 and "Changed" or "diffChanged"
|
||||
local removed_hl_name = nvim_0_10 and "Removed" or "diffRemoved"
|
||||
-- Reset this here in case of color scheme change
|
||||
faded_highlight_group_cache = {}
|
||||
|
||||
local normal_hl = M.create_highlight_group(M.NORMAL, { "Normal" })
|
||||
local normalnc_hl = M.create_highlight_group(M.NORMALNC, { "NormalNC", M.NORMAL })
|
||||
|
||||
M.create_highlight_group(M.SIGNCOLUMN, { "SignColumn", M.NORMAL })
|
||||
|
||||
M.create_highlight_group(M.STATUS_LINE, { "StatusLine" })
|
||||
M.create_highlight_group(M.STATUS_LINE_NC, { "StatusLineNC" })
|
||||
|
||||
M.create_highlight_group(M.VERTSPLIT, { "VertSplit" })
|
||||
M.create_highlight_group(M.WINSEPARATOR, { "WinSeparator" })
|
||||
|
||||
M.create_highlight_group(M.END_OF_BUFFER, { "EndOfBuffer" })
|
||||
|
||||
local float_border_hl =
|
||||
M.create_highlight_group(M.FLOAT_BORDER, { "FloatBorder" }, normalnc_hl.background, "444444")
|
||||
|
||||
M.create_highlight_group(M.FLOAT_NORMAL, { "NormalFloat", M.NORMAL })
|
||||
|
||||
M.create_highlight_group(M.FLOAT_TITLE, {}, float_border_hl.background, normal_hl.foreground)
|
||||
|
||||
local title_fg = normal_hl.background
|
||||
if title_fg == float_border_hl.foreground then
|
||||
title_fg = normal_hl.foreground
|
||||
end
|
||||
M.create_highlight_group(M.TITLE_BAR, {}, float_border_hl.foreground, title_fg)
|
||||
|
||||
local dim_text = calculate_faded_highlight_group("NeoTreeNormal", 0.3)
|
||||
|
||||
M.create_highlight_group(M.BUFFER_NUMBER, { "SpecialChar" })
|
||||
--M.create_highlight_group(M.DIM_TEXT, {}, nil, "505050")
|
||||
M.create_highlight_group(M.MESSAGE, {}, nil, dim_text.foreground, "italic")
|
||||
M.create_highlight_group(M.FADE_TEXT_1, {}, nil, "626262")
|
||||
M.create_highlight_group(M.FADE_TEXT_2, {}, nil, "444444")
|
||||
M.create_highlight_group(M.DOTFILE, {}, nil, "626262")
|
||||
M.create_highlight_group(M.HIDDEN_BY_NAME, { M.DOTFILE }, nil, nil)
|
||||
M.create_highlight_group(M.CURSOR_LINE, { "CursorLine" }, nil, nil, "bold")
|
||||
M.create_highlight_group(M.DIM_TEXT, {}, nil, dim_text.foreground)
|
||||
M.create_highlight_group(M.DIRECTORY_NAME, { "Directory" }, "NONE", "NONE")
|
||||
M.create_highlight_group(M.DIRECTORY_ICON, { "Directory" }, nil, "73cef4")
|
||||
M.create_highlight_group(M.FILE_ICON, { M.DIRECTORY_ICON })
|
||||
M.create_highlight_group(M.FILE_NAME, {}, "NONE", "NONE")
|
||||
M.create_highlight_group(M.FILE_NAME_OPENED, {}, nil, nil, "bold")
|
||||
M.create_highlight_group(M.SYMBOLIC_LINK_TARGET, { M.FILE_NAME })
|
||||
M.create_highlight_group(M.FILTER_TERM, { "SpecialChar", "Normal" })
|
||||
M.create_highlight_group(M.ROOT_NAME, {}, nil, nil, "bold,italic")
|
||||
M.create_highlight_group(M.INDENT_MARKER, { M.DIM_TEXT })
|
||||
M.create_highlight_group(M.EXPANDER, { M.DIM_TEXT })
|
||||
M.create_highlight_group(M.MODIFIED, {}, nil, "d7d787")
|
||||
M.create_highlight_group(M.WINDOWS_HIDDEN, { M.DOTFILE }, nil, nil)
|
||||
M.create_highlight_group(M.PREVIEW, { "Search" }, nil, nil)
|
||||
|
||||
M.create_highlight_group(
|
||||
M.GIT_ADDED,
|
||||
{ "GitGutterAdd", "GitSignsAdd", added_hl_name },
|
||||
nil,
|
||||
"5faf5f"
|
||||
)
|
||||
M.create_highlight_group(
|
||||
M.GIT_DELETED,
|
||||
{ "GitGutterDelete", "GitSignsDelete", removed_hl_name },
|
||||
nil,
|
||||
"ff5900"
|
||||
)
|
||||
M.create_highlight_group(
|
||||
M.GIT_MODIFIED,
|
||||
{ "GitGutterChange", "GitSignsChange", changed_hl_name },
|
||||
nil,
|
||||
"d7af5f"
|
||||
)
|
||||
local conflict = M.create_highlight_group(M.GIT_CONFLICT, {}, nil, "ff8700", "italic,bold")
|
||||
M.create_highlight_group(M.GIT_IGNORED, { M.DOTFILE }, nil, nil)
|
||||
M.create_highlight_group(M.GIT_RENAMED, { M.GIT_MODIFIED }, nil, nil)
|
||||
M.create_highlight_group(M.GIT_STAGED, { M.GIT_ADDED }, nil, nil)
|
||||
M.create_highlight_group(M.GIT_UNSTAGED, { M.GIT_CONFLICT }, nil, nil)
|
||||
M.create_highlight_group(M.GIT_UNTRACKED, {}, nil, conflict.foreground, "italic")
|
||||
|
||||
M.create_highlight_group(M.TAB_ACTIVE, {}, nil, nil, "bold")
|
||||
M.create_highlight_group(M.TAB_INACTIVE, {}, "141414", "777777")
|
||||
M.create_highlight_group(M.TAB_SEPARATOR_ACTIVE, {}, nil, "0a0a0a")
|
||||
M.create_highlight_group(M.TAB_SEPARATOR_INACTIVE, {}, "141414", "101010")
|
||||
|
||||
local faded_normal = calculate_faded_highlight_group("NeoTreeNormal", 0.4)
|
||||
M.create_highlight_group(M.FILE_STATS, {}, nil, faded_normal.foreground)
|
||||
|
||||
local faded_root = calculate_faded_highlight_group("NeoTreeRootName", 0.5)
|
||||
M.create_highlight_group(M.FILE_STATS_HEADER, {}, nil, faded_root.foreground, faded_root.gui)
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
local NuiInput = require("nui.input")
|
||||
local nt = require("neo-tree")
|
||||
local popups = require("neo-tree.ui.popups")
|
||||
local events = require("neo-tree.events")
|
||||
|
||||
local M = {}
|
||||
|
||||
---@param input NuiInput
|
||||
---@param callback function?
|
||||
M.show_input = function(input, callback)
|
||||
input:mount()
|
||||
|
||||
input:map("i", "<esc>", function()
|
||||
vim.cmd("stopinsert")
|
||||
input:unmount()
|
||||
end, { noremap = true })
|
||||
|
||||
input:map("n", "<esc>", function()
|
||||
input:unmount()
|
||||
end, { noremap = true })
|
||||
|
||||
input:map("n", "q", function()
|
||||
input:unmount()
|
||||
end, { noremap = true })
|
||||
|
||||
input:map("i", "<C-w>", "<C-S-w>", { noremap = true })
|
||||
|
||||
local event = require("nui.utils.autocmd").event
|
||||
input:on({ event.BufLeave, event.BufDelete }, function()
|
||||
input:unmount()
|
||||
if callback then
|
||||
callback()
|
||||
end
|
||||
end, { once = true })
|
||||
|
||||
if input.prompt_type ~= "confirm" then
|
||||
vim.schedule(function()
|
||||
events.fire_event(events.NEO_TREE_POPUP_INPUT_READY, {
|
||||
bufnr = input.bufnr,
|
||||
winid = input.winid,
|
||||
})
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
---@param message string
|
||||
---@param default_value string?
|
||||
---@param callback function
|
||||
---@param options nui_popup_options?
|
||||
---@param completion string?
|
||||
M.input = function(message, default_value, callback, options, completion)
|
||||
if nt.config.use_popups_for_input then
|
||||
local popup_options = popups.popup_options(message, 10, options)
|
||||
|
||||
local input = NuiInput(popup_options, {
|
||||
prompt = " ",
|
||||
default_value = default_value,
|
||||
on_submit = callback,
|
||||
})
|
||||
|
||||
M.show_input(input)
|
||||
else
|
||||
local opts = {
|
||||
prompt = message .. "\n",
|
||||
default = default_value,
|
||||
}
|
||||
if vim.opt.cmdheight:get() == 0 then
|
||||
-- NOTE: I really don't know why but letters before the first '\n' is not rendered except in noice.nvim
|
||||
-- when vim.opt.cmdheight = 0 <2023-10-24, pysan3>
|
||||
opts.prompt = "Neo-tree Popup\n" .. opts.prompt
|
||||
end
|
||||
if completion then
|
||||
opts.completion = completion
|
||||
end
|
||||
vim.ui.input(opts, callback)
|
||||
end
|
||||
end
|
||||
|
||||
---Blocks if callback is omitted
|
||||
---@param message string
|
||||
---@param callback? fun(confirmed: boolean)
|
||||
---@return boolean? confirmed_if_no_callback
|
||||
M.confirm = function(message, callback)
|
||||
if callback then
|
||||
if nt.config.use_popups_for_input then
|
||||
local popup_options = popups.popup_options(message, 10)
|
||||
|
||||
---@class NuiInput
|
||||
local input = NuiInput(popup_options, {
|
||||
prompt = " y/n: ",
|
||||
on_close = function()
|
||||
callback(false)
|
||||
end,
|
||||
on_submit = function(value)
|
||||
callback(value == "y" or value == "Y")
|
||||
end,
|
||||
})
|
||||
|
||||
input.prompt_type = "confirm"
|
||||
M.show_input(input)
|
||||
else
|
||||
callback(vim.fn.confirm(message, "&Yes\n&No") == 1)
|
||||
end
|
||||
else
|
||||
return vim.fn.confirm(message, "&Yes\n&No") == 1
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
local NuiText = require("nui.text")
|
||||
local NuiPopup = require("nui.popup")
|
||||
local nt = require("neo-tree")
|
||||
local highlights = require("neo-tree.ui.highlights")
|
||||
local log = require("neo-tree.log")
|
||||
|
||||
local M = {}
|
||||
|
||||
local winborder_option_exists = vim.fn.exists("&winborder") > 0
|
||||
-- These borders will cause errors when trying to display border text with them
|
||||
local invalid_borders = { "", "none", "shadow" }
|
||||
---@param title string
|
||||
---@param min_width integer?
|
||||
---@param override_options table?
|
||||
M.popup_options = function(title, min_width, override_options)
|
||||
if string.len(title) ~= 0 then
|
||||
title = " " .. title .. " "
|
||||
end
|
||||
min_width = min_width or 30
|
||||
local width = string.len(title) + 2
|
||||
|
||||
local popup_border_style = nt.config.popup_border_style
|
||||
if popup_border_style == "" then
|
||||
-- Try to use winborder
|
||||
if not winborder_option_exists or vim.tbl_contains(invalid_borders, vim.o.winborder) then
|
||||
popup_border_style = "single"
|
||||
else
|
||||
---@diagnostic disable-next-line: cast-local-type
|
||||
popup_border_style = vim.o.winborder
|
||||
end
|
||||
end
|
||||
local popup_border_text = NuiText(title, highlights.FLOAT_TITLE)
|
||||
local col = 0
|
||||
-- fix popup position when using multigrid
|
||||
local popup_last_col = vim.api.nvim_win_get_position(0)[2] + width + 2
|
||||
if popup_last_col >= vim.o.columns then
|
||||
col = vim.o.columns - popup_last_col
|
||||
end
|
||||
---@type nui_popup_options
|
||||
local popup_options = {
|
||||
ns_id = highlights.ns_id,
|
||||
relative = "cursor",
|
||||
position = {
|
||||
row = 1,
|
||||
col = col,
|
||||
},
|
||||
size = width,
|
||||
border = {
|
||||
text = {
|
||||
top = popup_border_text,
|
||||
},
|
||||
---@diagnostic disable-next-line: assign-type-mismatch
|
||||
style = popup_border_style,
|
||||
highlight = highlights.FLOAT_BORDER,
|
||||
},
|
||||
win_options = {
|
||||
winhighlight = "Normal:"
|
||||
.. highlights.FLOAT_NORMAL
|
||||
.. ",FloatBorder:"
|
||||
.. highlights.FLOAT_BORDER,
|
||||
},
|
||||
buf_options = {
|
||||
bufhidden = "delete",
|
||||
buflisted = false,
|
||||
filetype = "neo-tree-popup",
|
||||
},
|
||||
}
|
||||
|
||||
if popup_border_style == "NC" then
|
||||
local blank = NuiText(" ", highlights.TITLE_BAR)
|
||||
popup_border_text = NuiText(title, highlights.TITLE_BAR)
|
||||
popup_options.border = {
|
||||
style = { "▕", blank, "▏", "▏", " ", "▔", " ", "▕" },
|
||||
highlight = highlights.FLOAT_BORDER,
|
||||
text = {
|
||||
top = popup_border_text,
|
||||
top_align = "left",
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
if override_options then
|
||||
return vim.tbl_extend("force", popup_options, override_options)
|
||||
else
|
||||
return popup_options
|
||||
end
|
||||
end
|
||||
|
||||
---@param title string
|
||||
---@param message elem_or_list<string|integer>
|
||||
---@param size integer?
|
||||
M.alert = function(title, message, size)
|
||||
local lines = {}
|
||||
local max_line_width = title:len()
|
||||
---@param line any
|
||||
local add_line = function(line)
|
||||
line = tostring(line)
|
||||
if line:len() > max_line_width then
|
||||
max_line_width = line:len()
|
||||
end
|
||||
table.insert(lines, line)
|
||||
end
|
||||
|
||||
if type(message) == "table" then
|
||||
for _, v in ipairs(message) do
|
||||
add_line(v)
|
||||
end
|
||||
else
|
||||
add_line(message)
|
||||
end
|
||||
|
||||
add_line("")
|
||||
add_line(" Press <Escape> or <Enter> to close")
|
||||
|
||||
local win_options = M.popup_options(title, 80)
|
||||
win_options.zindex = 60
|
||||
win_options.size = {
|
||||
width = max_line_width + 4,
|
||||
height = #lines + 1,
|
||||
}
|
||||
local win = NuiPopup(win_options)
|
||||
win:mount()
|
||||
|
||||
local success, msg = pcall(vim.api.nvim_buf_set_lines, win.bufnr, 0, 0, false, lines)
|
||||
if success then
|
||||
win:map("n", "<esc>", function()
|
||||
win:unmount()
|
||||
end, { noremap = true })
|
||||
|
||||
win:map("n", "<enter>", function()
|
||||
win:unmount()
|
||||
end, { noremap = true })
|
||||
|
||||
local event = require("nui.utils.autocmd").event
|
||||
win:on({ event.BufLeave, event.BufDelete }, function()
|
||||
win:unmount()
|
||||
end, { once = true })
|
||||
|
||||
-- why is this necessary?
|
||||
vim.api.nvim_set_current_win(win.winid)
|
||||
else
|
||||
log.error(msg)
|
||||
win:unmount()
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,405 @@
|
|||
local utils = require("neo-tree.utils")
|
||||
local log = require("neo-tree.log")
|
||||
local manager = require("neo-tree.sources.manager")
|
||||
|
||||
local M = {}
|
||||
|
||||
---calc_click_id_from_source:
|
||||
-- Calculates click_id that stores information of the source and window id
|
||||
-- DANGER: Do not change this function unless you know what you are doing
|
||||
---@param winid integer: window id of the window source_selector is placed
|
||||
---@param source_index integer: index of the source
|
||||
---@return integer
|
||||
local calc_click_id_from_source = function(winid, source_index)
|
||||
local base_number = #require("neo-tree").config.source_selector.sources + 1
|
||||
return base_number * winid + source_index
|
||||
end
|
||||
|
||||
---calc_source_from_click_id:
|
||||
-- Calculates source index and window id from click_id. Paired with `M.calc_click_id_from_source`
|
||||
-- DANGER: Do not change this function unless you know what you are doing
|
||||
---@param click_id integer: click_id
|
||||
---@return integer, integer
|
||||
local calc_source_from_click_id = function(click_id)
|
||||
local base_number = #require("neo-tree").config.source_selector.sources + 1
|
||||
return math.floor(click_id / base_number), click_id % base_number
|
||||
end
|
||||
---sep_tbl:
|
||||
-- Returns table expression of separator.
|
||||
-- Converts to table expression if sep is string.
|
||||
---@param sep string | table:
|
||||
---@return table: `{ left = .., right = .., override = .. }`
|
||||
local sep_tbl = function(sep)
|
||||
if type(sep) == "nil" then
|
||||
return {}
|
||||
elseif type(sep) ~= "table" then
|
||||
return { left = sep, right = sep, override = "active" }
|
||||
end
|
||||
return sep
|
||||
end
|
||||
|
||||
---get_separators
|
||||
-- Returns information about separator on each tab.
|
||||
---@param source_index integer: index of source
|
||||
---@param active_index integer: index of active source. used to check if source is active and when `override = "active"`
|
||||
---@param force_ignore_left boolean: overwrites calculated results with "" if set to true
|
||||
---@param force_ignore_right boolean: overwrites calculated results with "" if set to true
|
||||
---@return table: something like `{ left = "|", right = "|" }`
|
||||
local get_separators = function(source_index, active_index, force_ignore_left, force_ignore_right)
|
||||
local config = require("neo-tree").config
|
||||
local is_active = source_index == active_index
|
||||
local sep = sep_tbl(config.source_selector.separator)
|
||||
if is_active then
|
||||
sep = vim.tbl_deep_extend("force", sep, sep_tbl(config.source_selector.separator_active))
|
||||
end
|
||||
local show_left = sep.override == "left"
|
||||
or (sep.override == "active" and source_index <= active_index)
|
||||
or sep.override == nil
|
||||
local show_right = sep.override == "right"
|
||||
or (sep.override == "active" and source_index >= active_index)
|
||||
or sep.override == nil
|
||||
return {
|
||||
left = (show_left and not force_ignore_left) and sep.left or "",
|
||||
right = (show_right and not force_ignore_right) and sep.right or "",
|
||||
}
|
||||
end
|
||||
|
||||
---get_selector_tab_info:
|
||||
-- Returns information to create a tab
|
||||
---@param source_name string: name of source. should be same as names in `config.sources`
|
||||
---@param source_index integer: index of source_name
|
||||
---@param is_active boolean: whether this source is currently focused
|
||||
---@param separator table: `{ left = .., right = .. }`: output from `get_separators()`
|
||||
---@return table (see code): Note: `length`: length of whole tab (including seps), `text_length`: length of tab excluding seps
|
||||
local get_selector_tab_info = function(source_name, source_index, is_active, separator)
|
||||
local config = require("neo-tree").config
|
||||
local separator_config = utils.resolve_config_option(config, "source_selector", nil)
|
||||
if separator_config == nil then
|
||||
log.warn("Cannot find source_selector config. `get_selector` abort.")
|
||||
return {}
|
||||
end
|
||||
local source_config = config[source_name] or {}
|
||||
local get_strlen = vim.api.nvim_strwidth
|
||||
local text = separator_config.sources[source_index].display_name
|
||||
or source_config.display_name
|
||||
or source_name
|
||||
local text_length = get_strlen(text)
|
||||
if separator_config.tabs_min_width ~= nil and text_length < separator_config.tabs_min_width then
|
||||
text = M.text_layout(text, separator_config.content_layout, separator_config.tabs_min_width)
|
||||
text_length = separator_config.tabs_min_width
|
||||
end
|
||||
if separator_config.tabs_max_width ~= nil and text_length > separator_config.tabs_max_width then
|
||||
text = M.text_layout(text, separator_config.content_layout, separator_config.tabs_max_width)
|
||||
text_length = separator_config.tabs_max_width
|
||||
end
|
||||
local tab_hl = is_active and separator_config.highlight_tab_active
|
||||
or separator_config.highlight_tab
|
||||
local sep_hl = is_active and separator_config.highlight_separator_active
|
||||
or separator_config.highlight_separator
|
||||
return {
|
||||
index = source_index,
|
||||
is_active = is_active,
|
||||
left = separator.left,
|
||||
right = separator.right,
|
||||
text = text,
|
||||
tab_hl = tab_hl,
|
||||
sep_hl = sep_hl,
|
||||
length = text_length + get_strlen(separator.left) + get_strlen(separator.right),
|
||||
text_length = text_length,
|
||||
}
|
||||
end
|
||||
|
||||
---text_with_hl:
|
||||
-- Returns text with highlight syntax for winbar / statusline
|
||||
---@param text string: text to highlight
|
||||
---@param tab_hl string | nil: if nil, does nothing
|
||||
---@return string: e.g. "%#HiName#text"
|
||||
local text_with_hl = function(text, tab_hl)
|
||||
if tab_hl == nil then
|
||||
return text
|
||||
end
|
||||
return string.format("%%#%s#%s", tab_hl, text)
|
||||
end
|
||||
|
||||
---add_padding:
|
||||
-- Use for creating padding with highlight
|
||||
---@param padding_legth number: number of padding. if float, value is rounded with `math.floor`
|
||||
---@param padchar string | nil: if nil, " " (space) is used
|
||||
---@return string
|
||||
local add_padding = function(padding_legth, padchar)
|
||||
if padchar == nil then
|
||||
padchar = " "
|
||||
end
|
||||
return string.rep(padchar, math.floor(padding_legth))
|
||||
end
|
||||
|
||||
---text_layout:
|
||||
-- Add padding to fill `output_width`.
|
||||
-- If `output_width` is less than `text_length`, text is truncated to fit `output_width`.
|
||||
---@param text string:
|
||||
---@param content_layout string: `"start", "center", "end"`: see `config.source_selector.tabs_layout` for more details
|
||||
---@param output_width integer: exact `strdisplaywidth` of the output string
|
||||
---@param trunc_char string | nil: Character used to indicate truncation. If nil, "…" (ellipsis) is used.
|
||||
---@return string
|
||||
local text_layout = function(text, content_layout, output_width, trunc_char)
|
||||
if output_width < 1 then
|
||||
return ""
|
||||
end
|
||||
local text_length = vim.fn.strdisplaywidth(text)
|
||||
local pad_length = output_width - text_length
|
||||
local left_pad, right_pad = 0, 0
|
||||
if pad_length < 0 then
|
||||
if output_width < 4 then
|
||||
return (utils.truncate_by_cell(text, output_width))
|
||||
else
|
||||
return (utils.truncate_by_cell(text, output_width - 1) .. trunc_char)
|
||||
end
|
||||
elseif content_layout == "start" then
|
||||
left_pad, right_pad = 0, pad_length
|
||||
elseif content_layout == "end" then
|
||||
left_pad, right_pad = pad_length, 0
|
||||
elseif content_layout == "center" then
|
||||
left_pad, right_pad = pad_length / 2, math.ceil(pad_length / 2)
|
||||
end
|
||||
return add_padding(left_pad) .. text .. add_padding(right_pad)
|
||||
end
|
||||
|
||||
---render_tab:
|
||||
-- Renders string to express one tab for winbar / statusline.
|
||||
---@param left_sep string: left separator
|
||||
---@param right_sep string: right separator
|
||||
---@param sep_hl string: highlight of separators
|
||||
---@param text string: text, mostly name of source in this case
|
||||
---@param tab_hl string: highlight of text
|
||||
---@param click_id integer: id passed to `___neotree_selector_click`, should be calculated with `M.calc_click_id_from_source`
|
||||
---@return string: complete string to render one tab
|
||||
local render_tab = function(left_sep, right_sep, sep_hl, text, tab_hl, click_id)
|
||||
local res = "%" .. click_id .. "@v:lua.___neotree_selector_click@"
|
||||
if left_sep ~= nil then
|
||||
res = res .. text_with_hl(left_sep, sep_hl)
|
||||
end
|
||||
res = res .. text_with_hl(text, tab_hl)
|
||||
if right_sep ~= nil then
|
||||
res = res .. text_with_hl(right_sep, sep_hl)
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
M.get_scrolled_off_node_text = function(state)
|
||||
if state == nil then
|
||||
state = require("neo-tree.sources.manager").get_state_for_window()
|
||||
if state == nil then
|
||||
return
|
||||
end
|
||||
end
|
||||
local win_top_line = vim.fn.line("w0")
|
||||
if win_top_line == nil or win_top_line == 1 then
|
||||
return
|
||||
end
|
||||
local node = assert(state.tree:get_node(win_top_line))
|
||||
return " " .. vim.fn.fnamemodify(node.path, ":~:h")
|
||||
end
|
||||
|
||||
M.get = function()
|
||||
local state = require("neo-tree.sources.manager").get_state_for_window()
|
||||
if state == nil then
|
||||
return
|
||||
else
|
||||
local config = require("neo-tree").config
|
||||
local scrolled_off =
|
||||
utils.resolve_config_option(config, "source_selector.show_scrolled_off_parent_node", false)
|
||||
if scrolled_off then
|
||||
local node_text = M.get_scrolled_off_node_text(state)
|
||||
if node_text ~= nil then
|
||||
return node_text
|
||||
end
|
||||
end
|
||||
return M.get_selector(state, vim.api.nvim_win_get_width(0))
|
||||
end
|
||||
end
|
||||
|
||||
---get_selector:
|
||||
-- Does everything to generate the string for source_selector in winbar / statusline.
|
||||
---@param state neotree.State:
|
||||
---@param width integer: width of the entire window where the source_selector is displayed
|
||||
---@return string | nil
|
||||
M.get_selector = function(state, width)
|
||||
local config = require("neo-tree").config
|
||||
if config == nil then
|
||||
log.warn("Cannot find config. `get_selector` abort.")
|
||||
return nil
|
||||
end
|
||||
local winid = state.winid or vim.api.nvim_get_current_win()
|
||||
|
||||
-- load padding from config
|
||||
local padding_config = config.source_selector.padding
|
||||
local padding
|
||||
if type(padding_config) == "number" then
|
||||
padding = { left = padding_config, right = padding_config }
|
||||
else
|
||||
padding = padding_config or { left = 0, right = 0 }
|
||||
end
|
||||
width = math.floor(width - padding.left - padding.right)
|
||||
|
||||
-- generate information of each tab (look `get_selector_tab_info` for type hint)
|
||||
local tabs = {}
|
||||
local sources = config.source_selector.sources or {}
|
||||
local active_index = #sources
|
||||
local length_sum, length_active, length_separators = 0, 0, 0
|
||||
for i, source_info in ipairs(sources) do
|
||||
local is_active = source_info.source == state.name
|
||||
if is_active then
|
||||
active_index = i
|
||||
end
|
||||
local separator = get_separators(
|
||||
i,
|
||||
active_index,
|
||||
config.source_selector.show_separator_on_edge == false and i == 1,
|
||||
config.source_selector.show_separator_on_edge == false and i == #sources
|
||||
)
|
||||
local element = get_selector_tab_info(source_info.source, i, is_active, separator)
|
||||
length_sum = length_sum + element.length
|
||||
length_separators = length_separators + element.length - element.text_length
|
||||
if is_active then
|
||||
length_active = element.length
|
||||
end
|
||||
table.insert(tabs, element)
|
||||
end
|
||||
|
||||
-- start creating string to display
|
||||
local tabs_layout = config.source_selector.tabs_layout or "equal"
|
||||
local content_layout = config.source_selector.content_layout or "center"
|
||||
local hl_background = config.source_selector.highlight_background
|
||||
local trunc_char = config.source_selector.truncation_character or "…"
|
||||
local remaining_width = width - length_separators
|
||||
local return_string = text_with_hl(add_padding(padding.left), hl_background)
|
||||
if width < length_sum then -- not enough width
|
||||
tabs_layout = "equal" -- other methods cannot handle this
|
||||
end
|
||||
if tabs_layout == "active" then
|
||||
local active_tab_length = width - length_sum + length_active - 1
|
||||
for _, tab in ipairs(tabs) do
|
||||
return_string = return_string
|
||||
.. render_tab(
|
||||
tab.left,
|
||||
tab.right,
|
||||
tab.sep_hl,
|
||||
text_layout(
|
||||
tab.text,
|
||||
tab.is_active and content_layout or nil,
|
||||
active_tab_length,
|
||||
trunc_char
|
||||
),
|
||||
tab.tab_hl,
|
||||
calc_click_id_from_source(winid, tab.index)
|
||||
)
|
||||
.. text_with_hl("", hl_background)
|
||||
end
|
||||
elseif tabs_layout == "equal" then
|
||||
for _, tab in ipairs(tabs) do
|
||||
return_string = return_string
|
||||
.. render_tab(
|
||||
tab.left,
|
||||
tab.right,
|
||||
tab.sep_hl,
|
||||
text_layout(tab.text, content_layout, math.floor(remaining_width / #tabs), trunc_char),
|
||||
tab.tab_hl,
|
||||
calc_click_id_from_source(winid, tab.index)
|
||||
)
|
||||
.. text_with_hl("", hl_background)
|
||||
end
|
||||
else -- config.source_selector.tab_labels == "start", "end", "center"
|
||||
-- calculate padding based on tabs_layout
|
||||
local pad_length = width - length_sum
|
||||
local left_pad, right_pad = 0, 0
|
||||
if pad_length > 0 then
|
||||
if tabs_layout == "start" then
|
||||
left_pad, right_pad = 0, pad_length
|
||||
elseif tabs_layout == "end" then
|
||||
left_pad, right_pad = pad_length, 0
|
||||
elseif tabs_layout == "center" then
|
||||
left_pad, right_pad = pad_length / 2, math.ceil(pad_length / 2)
|
||||
end
|
||||
end
|
||||
|
||||
for i, tab in ipairs(tabs) do
|
||||
if width == 0 then
|
||||
break
|
||||
end
|
||||
|
||||
-- only render trunc_char if there is no space for the tab
|
||||
local sep_length = tab.length - tab.text_length
|
||||
if width <= sep_length + 1 then
|
||||
return_string = return_string
|
||||
.. text_with_hl(trunc_char .. add_padding(width - 1), hl_background)
|
||||
width = 0
|
||||
break
|
||||
end
|
||||
|
||||
-- tab_length should not exceed width
|
||||
local tab_length = width < tab.length and width or tab.length
|
||||
width = width - tab_length
|
||||
|
||||
-- add padding for first and last tab
|
||||
local tab_text = tab.text
|
||||
if i == 1 then
|
||||
tab_text = add_padding(left_pad) .. tab_text
|
||||
tab_length = tab_length + left_pad
|
||||
end
|
||||
if i == #tabs then
|
||||
tab_text = tab_text .. add_padding(right_pad)
|
||||
tab_length = tab_length + right_pad
|
||||
end
|
||||
|
||||
return_string = return_string
|
||||
.. render_tab(
|
||||
tab.left,
|
||||
tab.right,
|
||||
tab.sep_hl,
|
||||
text_layout(tab_text, tabs_layout, tab_length - sep_length, trunc_char),
|
||||
tab.tab_hl,
|
||||
calc_click_id_from_source(winid, tab.index)
|
||||
)
|
||||
end
|
||||
end
|
||||
return return_string .. "%<%0@v:lua.___neotree_selector_click@"
|
||||
end
|
||||
|
||||
---set_source_selector:
|
||||
-- (public): Directly set source_selector to current window's winbar / statusline
|
||||
---@param state neotree.State: state
|
||||
---@return nil
|
||||
M.set_source_selector = function(state)
|
||||
if state.enable_source_selector == false then
|
||||
return
|
||||
end
|
||||
local sel_config = utils.resolve_config_option(require("neo-tree").config, "source_selector", {})
|
||||
if sel_config and sel_config.winbar then
|
||||
vim.wo[state.winid].winbar = "%{%v:lua.require'neo-tree.ui.selector'.get()%}"
|
||||
end
|
||||
if sel_config and sel_config.statusline then
|
||||
vim.wo[state.winid].statusline = "%{%v:lua.require'neo-tree.ui.selector'.get()%}"
|
||||
end
|
||||
end
|
||||
|
||||
-- @v:lua@ in the tabline only supports global functions, so this is
|
||||
-- the only way to add click handlers without autoloaded vimscript functions
|
||||
_G.___neotree_selector_click = function(id, _, _, _)
|
||||
if id < 1 then
|
||||
return
|
||||
end
|
||||
local sources = require("neo-tree").config.source_selector.sources or {}
|
||||
local winid, source_index = calc_source_from_click_id(id)
|
||||
local state = manager.get_state_for_window(winid)
|
||||
if state == nil then
|
||||
log.warn("state not found for window ", winid, "; ignoring click")
|
||||
return
|
||||
end
|
||||
require("neo-tree.command").execute({
|
||||
source = sources[source_index].source,
|
||||
position = state.current_position,
|
||||
action = "focus",
|
||||
})
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
local locations = {}
|
||||
|
||||
locations.get_location = function(location)
|
||||
local tab = vim.api.nvim_get_current_tabpage()
|
||||
if not locations[tab] then
|
||||
locations[tab] = {}
|
||||
end
|
||||
local loc = locations[tab][location]
|
||||
if loc then
|
||||
if loc.winid ~= 0 then
|
||||
-- verify the window before we return it
|
||||
if not vim.api.nvim_win_is_valid(loc.winid) then
|
||||
loc.winid = 0
|
||||
end
|
||||
end
|
||||
return loc
|
||||
end
|
||||
loc = {
|
||||
source = nil,
|
||||
name = location,
|
||||
winid = 0,
|
||||
}
|
||||
locations[tab][location] = loc
|
||||
return loc
|
||||
end
|
||||
|
||||
return locations
|
||||
Loading…
Add table
Add a link
Reference in a new issue