Refresh generated nvim config
This commit is contained in:
@ -0,0 +1,68 @@
|
||||
local utils = require("winshift.utils")
|
||||
local config = require("winshift.config")
|
||||
local lib = require("winshift.lib")
|
||||
local api = vim.api
|
||||
local M = {}
|
||||
|
||||
-- Lazily ensure that setup has been run before accessing any module exports.
|
||||
local init_done = false
|
||||
local init_safeguard = setmetatable({}, {
|
||||
__index = function(_, k)
|
||||
if not init_done then
|
||||
init_done = true
|
||||
if k == "setup" then
|
||||
return M[k]
|
||||
else
|
||||
config.setup({})
|
||||
return M[k]
|
||||
end
|
||||
else
|
||||
return M[k]
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
local completion_dir = {
|
||||
"left",
|
||||
"right",
|
||||
"up",
|
||||
"down",
|
||||
"far_left",
|
||||
"far_right",
|
||||
"far_up",
|
||||
"far_down",
|
||||
"swap",
|
||||
}
|
||||
|
||||
function M.setup(user_config)
|
||||
config.setup(user_config or {})
|
||||
end
|
||||
|
||||
function M.cmd_winshift(dir)
|
||||
if dir then
|
||||
if not vim.tbl_contains(completion_dir, dir) then
|
||||
utils.err("Action must be one of: " .. table.concat(completion_dir, ", "))
|
||||
return
|
||||
end
|
||||
if dir == "swap" then
|
||||
lib.start_swap_mode()
|
||||
return
|
||||
end
|
||||
lib.move_win(api.nvim_get_current_win(), dir)
|
||||
else
|
||||
lib.start_move_mode()
|
||||
end
|
||||
end
|
||||
|
||||
local function filter_completion(arg_lead, items)
|
||||
return vim.tbl_filter(function(item)
|
||||
return item:match(utils.pattern_esc(arg_lead))
|
||||
end, items)
|
||||
end
|
||||
|
||||
---@diagnostic disable-next-line: unused-local
|
||||
function M.completion(arg_lead, cmd_line, cur_pos)
|
||||
return filter_completion(arg_lead, completion_dir)
|
||||
end
|
||||
|
||||
return init_safeguard
|
||||
@ -0,0 +1,200 @@
|
||||
local config = require("winshift.config")
|
||||
local api = vim.api
|
||||
local M = {}
|
||||
|
||||
--#region TYPES
|
||||
|
||||
---@class HiSpec
|
||||
---@field fg string
|
||||
---@field bg string
|
||||
---@field ctermfg integer
|
||||
---@field ctermbg integer
|
||||
---@field gui string
|
||||
---@field sp string
|
||||
---@field blend integer
|
||||
---@field default boolean
|
||||
|
||||
---@class HiLinkSpec
|
||||
---@field force boolean
|
||||
---@field default boolean
|
||||
|
||||
--#endregion
|
||||
|
||||
---@param name string Syntax group name.
|
||||
---@param attr string Attribute name.
|
||||
---@param trans boolean Translate the syntax group (follows links).
|
||||
function M.get_hl_attr(name, attr, trans)
|
||||
local id = api.nvim_get_hl_id_by_name(name)
|
||||
if id and trans then
|
||||
id = vim.fn.synIDtrans(id)
|
||||
end
|
||||
if not id then
|
||||
return
|
||||
end
|
||||
|
||||
local value = vim.fn.synIDattr(id, attr)
|
||||
if not value or value == "" then
|
||||
return
|
||||
end
|
||||
|
||||
return value
|
||||
end
|
||||
|
||||
---@param groups string|string[] Syntax group name, or an ordered list of
|
||||
---groups where the first found value will be returned.
|
||||
---@param trans boolean Translate the syntax group (follows links). True by default.
|
||||
function M.get_fg(groups, trans)
|
||||
if type(trans) ~= "boolean" then trans = true end
|
||||
|
||||
if type(groups) == "table" then
|
||||
local v
|
||||
for _, group in ipairs(groups) do
|
||||
v = M.get_hl_attr(group, "fg", trans)
|
||||
if v then return v end
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
return M.get_hl_attr(groups, "fg", trans)
|
||||
end
|
||||
|
||||
---@param groups string|string[] Syntax group name, or an ordered list of
|
||||
---groups where the first found value will be returned.
|
||||
---@param trans boolean Translate the syntax group (follows links). True by default.
|
||||
function M.get_bg(groups, trans)
|
||||
if type(trans) ~= "boolean" then trans = true end
|
||||
|
||||
if type(groups) == "table" then
|
||||
local v
|
||||
for _, group in ipairs(groups) do
|
||||
v = M.get_hl_attr(group, "bg", trans)
|
||||
if v then return v end
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
return M.get_hl_attr(groups, "bg", trans)
|
||||
end
|
||||
|
||||
---@param groups string|string[] Syntax group name, or an ordered list of
|
||||
---groups where the first found value will be returned.
|
||||
---@param trans boolean Translate the syntax group (follows links). True by default.
|
||||
function M.get_gui(groups, trans)
|
||||
if type(trans) ~= "boolean" then trans = true end
|
||||
if type(groups) ~= "table" then groups = { groups } end
|
||||
|
||||
local hls
|
||||
for _, group in ipairs(groups) do
|
||||
hls = {}
|
||||
local attributes = {
|
||||
"bold",
|
||||
"italic",
|
||||
"reverse",
|
||||
"standout",
|
||||
"underline",
|
||||
"undercurl",
|
||||
"strikethrough"
|
||||
}
|
||||
|
||||
for _, attr in ipairs(attributes) do
|
||||
if M.get_hl_attr(group, attr, trans) == "1" then
|
||||
table.insert(hls, attr)
|
||||
end
|
||||
end
|
||||
|
||||
if #hls > 0 then
|
||||
return table.concat(hls, ",")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param group string Syntax group name.
|
||||
---@param opt HiSpec
|
||||
function M.hi(group, opt)
|
||||
local use_tc = vim.o.termguicolors
|
||||
local g = use_tc and "gui" or "cterm"
|
||||
|
||||
if not use_tc then
|
||||
if opt.ctermfg then
|
||||
opt.fg = opt.ctermfg
|
||||
end
|
||||
if opt.ctermbg then
|
||||
opt.bg = opt.ctermbg
|
||||
end
|
||||
end
|
||||
|
||||
vim.cmd(string.format(
|
||||
"hi %s %s %s %s %s %s %s",
|
||||
opt.default and "def" or "",
|
||||
group,
|
||||
opt.fg and (g .. "fg=" .. opt.fg) or "",
|
||||
opt.bg and (g .. "bg=" .. opt.bg) or "",
|
||||
opt.gui and ((use_tc and "gui=" or "cterm=") .. opt.gui) or "",
|
||||
opt.sp and ("guisp=" .. opt.sp) or "",
|
||||
opt.blend and ("blend=" .. opt.blend) or ""
|
||||
))
|
||||
end
|
||||
|
||||
---@param from string Syntax group name.
|
||||
---@param to? string Syntax group name. (default: `"NONE"`)
|
||||
---@param opt? HiLinkSpec
|
||||
function M.hi_link(from, to, opt)
|
||||
opt = opt or {}
|
||||
vim.cmd(string.format(
|
||||
"hi%s %s link %s %s",
|
||||
opt.force and "!" or "",
|
||||
opt.default and "default" or "",
|
||||
from,
|
||||
to or "NONE"
|
||||
))
|
||||
end
|
||||
|
||||
function M.get_colors()
|
||||
return {
|
||||
white = M.get_fg("Normal") or "White",
|
||||
red = M.get_fg("Keyword") or "Red",
|
||||
green = M.get_fg("Character") or "Green",
|
||||
yellow = M.get_fg("PreProc") or "Yellow",
|
||||
blue = M.get_fg("Include") or "Blue",
|
||||
purple = M.get_fg("Define") or "Purple",
|
||||
cyan = M.get_fg("Conditional") or "Cyan",
|
||||
dark_red = M.get_fg("Keyword") or "DarkRed",
|
||||
orange = M.get_fg("Number") or "Orange",
|
||||
}
|
||||
end
|
||||
|
||||
function M.get_hl_groups()
|
||||
local hl_focused = config.get_config().focused_hl_group
|
||||
local reverse = M.get_hl_attr(hl_focused, "reverse") == "1"
|
||||
local bg_focused = reverse
|
||||
and (M.get_fg({ hl_focused, "Normal" }) or "white")
|
||||
or (M.get_bg({ hl_focused, "Normal" }) or "white")
|
||||
local fg_focused = reverse and (M.get_bg({ hl_focused, "Normal" }) or "black") or nil
|
||||
|
||||
return {
|
||||
Normal = { fg = fg_focused, bg = bg_focused },
|
||||
EndOfBuffer = { fg = bg_focused, bg = bg_focused },
|
||||
LineNr = { fg = M.get_fg("LineNr"), bg = bg_focused, gui = M.get_gui("LineNr") },
|
||||
CursorLineNr = { fg = M.get_fg("CursorLineNr"), bg = bg_focused, gui = M.get_gui("CursorLineNr") },
|
||||
SignColumn = { fg = M.get_fg("SignColumn"), bg = bg_focused },
|
||||
FoldColumn = { fg = M.get_fg("FoldColumn"), bg = bg_focused },
|
||||
WindowPicker = { fg = "#ededed", bg = "#4493c8", ctermfg = 255, ctermbg = 33, gui = "bold" },
|
||||
}
|
||||
end
|
||||
|
||||
M.hl_links = {
|
||||
LineNrAbove = "WinShiftLineNr",
|
||||
LineNrBelow = "WinShiftLineNr",
|
||||
}
|
||||
|
||||
function M.setup()
|
||||
for name, opt in pairs(M.get_hl_groups()) do
|
||||
M.hi("WinShift" .. name, opt)
|
||||
end
|
||||
|
||||
for from, to in pairs(M.hl_links) do
|
||||
M.hi_link("WinShift" .. from, to, { default = true })
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,111 @@
|
||||
local utils = require("winshift.utils")
|
||||
local M = {}
|
||||
|
||||
-- stylua: ignore start
|
||||
M.defaults = {
|
||||
highlight_moving_win = true,
|
||||
focused_hl_group = "Visual",
|
||||
moving_win_options = {
|
||||
wrap = false,
|
||||
cursorline = false,
|
||||
cursorcolumn = false,
|
||||
colorcolumn = "",
|
||||
},
|
||||
keymaps = {
|
||||
disable_defaults = false,
|
||||
win_move_mode = {
|
||||
["h"] = "left",
|
||||
["j"] = "down",
|
||||
["k"] = "up",
|
||||
["l"] = "right",
|
||||
["H"] = "far_left",
|
||||
["J"] = "far_down",
|
||||
["K"] = "far_up",
|
||||
["L"] = "far_right",
|
||||
["<left>"] = "left",
|
||||
["<down>"] = "down",
|
||||
["<up>"] = "up",
|
||||
["<right>"] = "right",
|
||||
["<S-left>"] = "far_left",
|
||||
["<S-down>"] = "far_down",
|
||||
["<S-up>"] = "far_up",
|
||||
["<S-right>"] = "far_right",
|
||||
},
|
||||
},
|
||||
---A function that should prompt the user to select a window.
|
||||
---
|
||||
---The window picker is used to select a window while swapping windows with
|
||||
---`:WinShift swap`.
|
||||
---@return integer? winid # Either the selected window ID, or `nil` to
|
||||
--- indicate that the user cancelled / gave an invalid selection.
|
||||
window_picker = function()
|
||||
return require("winshift.lib").pick_window({
|
||||
picker_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890",
|
||||
filter_rules = {
|
||||
cur_win = true,
|
||||
floats = true,
|
||||
filetype = {},
|
||||
buftype = {},
|
||||
bufname = {},
|
||||
},
|
||||
---A function used to filter the list of selectable windows.
|
||||
---@param winids integer[] # The list of selectable window IDs.
|
||||
---@return integer[] filtered # The filtered list of window IDs.
|
||||
filter_func = nil,
|
||||
})
|
||||
end,
|
||||
}
|
||||
-- stylua: ignore end
|
||||
|
||||
M._config = M.defaults
|
||||
|
||||
function M.get_key_dir_map()
|
||||
local t = {}
|
||||
|
||||
for lhs, rhs in pairs(M._config.keymaps.win_move_mode) do
|
||||
if lhs:match("%b<>") then
|
||||
-- Get raw key code for special keys
|
||||
lhs = utils.raw_key(lhs)
|
||||
end
|
||||
|
||||
t[lhs] = rhs
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
|
||||
function M.get_config()
|
||||
return M._config
|
||||
end
|
||||
|
||||
function M.setup(user_config)
|
||||
user_config = user_config or {}
|
||||
|
||||
M._config = utils.tbl_deep_clone(M.defaults)
|
||||
M._config = vim.tbl_deep_extend("force", M._config, user_config)
|
||||
|
||||
M._config.moving_win_options = user_config.moving_win_options or M._config.moving_win_options
|
||||
|
||||
--#region DEPRECATION NOTICES
|
||||
|
||||
if M._config.window_picker_chars or M._config.window_picker_ignore then
|
||||
utils.warn(table.concat({
|
||||
"'window_picker_chars' and 'window_picker_ignore' has been deprecated!",
|
||||
" See ':h winshift.changelog-11' for more information.",
|
||||
}, ""))
|
||||
end
|
||||
|
||||
--#endregion
|
||||
|
||||
if M._config.keymaps.disable_defaults then
|
||||
for name, _ in pairs(M._config.keymaps) do
|
||||
if name ~= "disable_defaults" then
|
||||
M._config.keymaps[name] = vim.tbl_get(user_config, "keymaps", name) or {}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require("winshift.colors").setup()
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,659 @@
|
||||
local utils = require("winshift.utils")
|
||||
local config = require("winshift.config")
|
||||
local api = vim.api
|
||||
local M = {}
|
||||
local win_option_store = {}
|
||||
|
||||
---@class Node : { [integer]: Node }
|
||||
---@field type '"leaf"'|'"row"'|'"col"'
|
||||
---@field parent Node
|
||||
---@field index integer
|
||||
---@field winid integer|nil
|
||||
|
||||
---@class VirtualNode : Node
|
||||
---@field target Node
|
||||
|
||||
---@alias HDirection '"left"'|'"right"'
|
||||
---@alias VDirection '"up"'|'"down"'
|
||||
---@alias Direction HDirection|VDirection|'"far_left"'|'"far_right"'|'"far_up"'|'"far_down"'
|
||||
|
||||
M.dir_move_map = {
|
||||
far_left = "H",
|
||||
far_down = "J",
|
||||
far_up = "K",
|
||||
far_right = "L",
|
||||
}
|
||||
|
||||
function M.process_layout(layout)
|
||||
local function recurse(parent)
|
||||
---@type Node
|
||||
local node = { type = parent[1] }
|
||||
|
||||
if node.type == "leaf" then
|
||||
node.winid = parent[2]
|
||||
else
|
||||
for i, child in ipairs(parent[2]) do
|
||||
node[#node + 1] = recurse(child)
|
||||
node[#node].index = i
|
||||
node[#node].parent = node
|
||||
end
|
||||
end
|
||||
|
||||
return node
|
||||
end
|
||||
|
||||
return recurse(layout)
|
||||
end
|
||||
|
||||
function M.get_layout_tree()
|
||||
return M.process_layout(vim.fn.winlayout())
|
||||
end
|
||||
|
||||
---@param node Node
|
||||
---@return Node
|
||||
function M.get_first_leaf(node)
|
||||
local cur = node
|
||||
while cur.type ~= "leaf" do
|
||||
cur = cur[1]
|
||||
end
|
||||
return cur
|
||||
end
|
||||
|
||||
---@param node Node
|
||||
---@return Node
|
||||
function M.get_last_leaf(node)
|
||||
local cur = node
|
||||
while cur.type ~= "leaf" do
|
||||
cur = cur[#cur]
|
||||
end
|
||||
return cur
|
||||
end
|
||||
|
||||
---@param tree Node
|
||||
---@param winid integer
|
||||
function M.find_leaf(tree, winid)
|
||||
---@param node Node
|
||||
---@return Node
|
||||
local function recurse(node)
|
||||
if node.type == "leaf" and node.winid == winid then
|
||||
return node
|
||||
else
|
||||
for _, child in ipairs(node) do
|
||||
local target = recurse(child)
|
||||
if target then
|
||||
return target
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return recurse(tree)
|
||||
end
|
||||
|
||||
---@param a Node
|
||||
---@param b Node
|
||||
function M.swap_leaves(a, b)
|
||||
vim.cmd(
|
||||
string.format(
|
||||
"noautocmd keepjumps %dwindo belowright %s",
|
||||
api.nvim_win_get_number(a.winid),
|
||||
a.parent.type == "col" and "vsp" or "sp"
|
||||
)
|
||||
)
|
||||
local temp_a = api.nvim_get_current_win()
|
||||
local opt_a = { vertical = a.parent.type == "col", rightbelow = false }
|
||||
|
||||
vim.cmd(
|
||||
string.format(
|
||||
"noautocmd keepjumps %dwindo belowright %s",
|
||||
api.nvim_win_get_number(b.winid),
|
||||
b.parent.type == "col" and "vsp" or "sp"
|
||||
)
|
||||
)
|
||||
local temp_b = api.nvim_get_current_win()
|
||||
local opt_b = { vertical = b.parent.type == "col", rightbelow = false }
|
||||
|
||||
vim.fn.win_splitmove(a.winid, temp_b, opt_b)
|
||||
vim.fn.win_splitmove(b.winid, temp_a, opt_a)
|
||||
api.nvim_win_close(temp_a, true)
|
||||
api.nvim_win_close(temp_b, true)
|
||||
end
|
||||
|
||||
---Move a row into a target window, replacing the target.
|
||||
---@param row Node
|
||||
---@param target integer Window id
|
||||
---@param ignore? table<integer, boolean>
|
||||
function M.move_row(row, target, ignore)
|
||||
ignore = ignore or {}
|
||||
local opt = { vertical = true, rightbelow = false }
|
||||
|
||||
---@type Node
|
||||
for _, node in ipairs(row) do
|
||||
if node.type == "col" then
|
||||
local nr = api.nvim_win_get_number(target)
|
||||
vim.cmd("noautocmd keepjumps " .. nr .. "windo aboveleft vsp")
|
||||
M.move_col(node, api.nvim_get_current_win(), ignore)
|
||||
elseif not ignore[node.winid] then
|
||||
vim.fn.win_splitmove(node.winid, target, opt)
|
||||
end
|
||||
end
|
||||
|
||||
api.nvim_win_close(target, true)
|
||||
end
|
||||
|
||||
---Move a column into a target window, replacing the target.
|
||||
---@param col Node
|
||||
---@param target integer Window id
|
||||
---@param ignore? table<integer, boolean>
|
||||
function M.move_col(col, target, ignore)
|
||||
ignore = ignore or {}
|
||||
local opt = { vertical = false, rightbelow = false }
|
||||
|
||||
---@type Node
|
||||
for _, node in ipairs(col) do
|
||||
if node.type == "row" then
|
||||
local nr = api.nvim_win_get_number(target)
|
||||
vim.cmd("noautocmd keepjumps " .. nr .. "windo aboveleft sp")
|
||||
M.move_row(node, api.nvim_get_current_win(), ignore)
|
||||
elseif not ignore[node.winid] then
|
||||
vim.fn.win_splitmove(node.winid, target, opt)
|
||||
end
|
||||
end
|
||||
|
||||
api.nvim_win_close(target, true)
|
||||
end
|
||||
|
||||
---Move a leaf out of a row in a given direction.
|
||||
---@param leaf Node
|
||||
---@param row Node
|
||||
---@param dir VDirection
|
||||
function M.row_move_out(leaf, row, dir)
|
||||
vim.cmd(
|
||||
string.format(
|
||||
"noautocmd keepjumps %dwindo %s sp",
|
||||
api.nvim_win_get_number(leaf.winid),
|
||||
dir == "up" and "belowright" or "aboveleft"
|
||||
)
|
||||
)
|
||||
local tempwin = api.nvim_get_current_win()
|
||||
M.move_row(row, tempwin, { [leaf.winid] = true })
|
||||
end
|
||||
|
||||
---Move a leaf out of a column in a given direction.
|
||||
---@param leaf Node
|
||||
---@param col Node
|
||||
---@param dir HDirection
|
||||
function M.col_move_out(leaf, col, dir)
|
||||
vim.cmd(
|
||||
string.format(
|
||||
"noautocmd keepjumps %dwindo %s vsp",
|
||||
api.nvim_win_get_number(leaf.winid),
|
||||
dir == "left" and "belowright" or "aboveleft"
|
||||
)
|
||||
)
|
||||
local tempwin = api.nvim_get_current_win()
|
||||
M.move_col(col, tempwin, { [leaf.winid] = true })
|
||||
end
|
||||
|
||||
---Move a leaf into a row.
|
||||
---@param leaf Node
|
||||
---@param row Node
|
||||
---@param dir HDirection Determines what side of the row the leaf is moved to.
|
||||
function M.row_move_in(leaf, row, dir)
|
||||
local target_leaf = dir == "right" and M.get_last_leaf(row) or M.get_first_leaf(row)
|
||||
local opt = { vertical = true, rightbelow = dir == "right" }
|
||||
vim.fn.win_splitmove(leaf.winid, target_leaf.winid, opt)
|
||||
|
||||
vim.cmd(
|
||||
string.format(
|
||||
"noautocmd keepjumps %dwindo %s vsp",
|
||||
api.nvim_win_get_number(leaf.winid),
|
||||
dir == "right" and "aboveleft" or "belowright"
|
||||
)
|
||||
)
|
||||
local tempwin = api.nvim_get_current_win()
|
||||
M.move_row(row, tempwin)
|
||||
end
|
||||
|
||||
---Move a leaf into a column.
|
||||
---@param leaf Node
|
||||
---@param col Node
|
||||
---@param dir VDirection Determines what side of the col the leaf is moved to.
|
||||
function M.col_move_in(leaf, col, dir)
|
||||
local target_leaf = dir == "down" and M.get_last_leaf(col) or M.get_first_leaf(col)
|
||||
local opt = { vertical = false, rightbelow = dir == "down" }
|
||||
vim.fn.win_splitmove(leaf.winid, target_leaf.winid, opt)
|
||||
|
||||
vim.cmd(
|
||||
string.format(
|
||||
"noautocmd keepjumps %dwindo %s sp",
|
||||
api.nvim_win_get_number(leaf.winid),
|
||||
dir == "down" and "aboveleft" or "belowright"
|
||||
)
|
||||
)
|
||||
local tempwin = api.nvim_get_current_win()
|
||||
M.move_col(col, tempwin)
|
||||
end
|
||||
|
||||
---Get the next node in a given direction in the given leaf's closest row
|
||||
---parent. Returns `nil` if there's no node in the given direction.
|
||||
---@param leaf Node
|
||||
---@param dir HDirection
|
||||
---@return Node|nil
|
||||
function M.next_node_horizontal(leaf, dir)
|
||||
local outside_parent = (dir == "left" and leaf.index == 1)
|
||||
or (dir == "right" and leaf.index == #leaf.parent)
|
||||
|
||||
if leaf.parent.type == "col" or outside_parent then
|
||||
local outer_parent = leaf.parent.parent
|
||||
if not outer_parent or outer_parent.type == "col" then
|
||||
return
|
||||
end
|
||||
|
||||
return outer_parent[leaf.parent.index + ((dir == "left" and -1) or 1)]
|
||||
else
|
||||
return leaf.parent[leaf.index + ((dir == "left" and -1) or 1)]
|
||||
end
|
||||
end
|
||||
|
||||
---Get the next node in a given direction in the given leaf's closest column
|
||||
---parent. Returns `nil` if there's no node in the given direction.
|
||||
---@param leaf Node
|
||||
---@param dir VDirection
|
||||
---@return Node|nil
|
||||
function M.next_node_vertical(leaf, dir)
|
||||
local outside_parent = (dir == "up" and leaf.index == 1)
|
||||
or (dir == "down" and leaf.index == #leaf.parent)
|
||||
|
||||
if leaf.parent.type == "row" or outside_parent then
|
||||
local outer_parent = leaf.parent.parent
|
||||
if not outer_parent or outer_parent.type == "row" then
|
||||
return
|
||||
end
|
||||
|
||||
return outer_parent[leaf.parent.index + ((dir == "up" and -1) or 1)]
|
||||
else
|
||||
return leaf.parent[leaf.index + ((dir == "up" and -1) or 1)]
|
||||
end
|
||||
end
|
||||
|
||||
---@class WindowPickerFilterRules
|
||||
---@field cur_win boolean
|
||||
---@field floats boolean
|
||||
---@field filetype string[]
|
||||
---@field buftype string[]
|
||||
---@field bufname string[]
|
||||
|
||||
---@class WindowPickerSpec
|
||||
---@field picker_chars string
|
||||
---@field filter_rules WindowPickerFilterRules
|
||||
---@field filter_func fun(winids: integer[]): integer[]
|
||||
|
||||
---Get user to pick a window. Selectable windows are all windows in the current
|
||||
---tabpage.
|
||||
---@param opt? WindowPickerSpec
|
||||
---@return integer|nil -- If a valid window was picked, return its id. If an
|
||||
--- invalid window was picked / user canceled, return nil. If there are
|
||||
--- no selectable windows, return -1.
|
||||
function M.pick_window(opt)
|
||||
opt = opt or {}
|
||||
local tabpage = api.nvim_get_current_tabpage()
|
||||
local win_ids = api.nvim_tabpage_list_wins(tabpage)
|
||||
local curwin = api.nvim_get_current_win()
|
||||
local filter_rules = opt.filter_rules or {}
|
||||
|
||||
local selectable = vim.tbl_filter(function (id)
|
||||
if filter_rules.cur_win and curwin == id then
|
||||
return false
|
||||
elseif filter_rules.floats and api.nvim_win_get_config(id).relative ~= "" then
|
||||
return false
|
||||
end
|
||||
|
||||
local bufid = api.nvim_win_get_buf(id)
|
||||
local bufname = api.nvim_buf_get_name(bufid)
|
||||
|
||||
for _, option in ipairs({ "filetype", "buftype" }) do
|
||||
if vim.tbl_contains(filter_rules[option] or {}, vim.bo[bufid][option]) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
for _, pattern in ipairs(filter_rules.bufname or {}) do
|
||||
local regex = vim.regex(pattern)
|
||||
if regex:match_str(bufname) ~= nil then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end, win_ids)
|
||||
|
||||
if opt.filter_func then
|
||||
selectable = opt.filter_func(selectable)
|
||||
end
|
||||
|
||||
-- If there are no selectable windows: return. If there's only 1, return it without picking.
|
||||
if #selectable == 0 then return -1 end
|
||||
if #selectable == 1 then return selectable[1] end
|
||||
|
||||
local chars = (opt.picker_chars or "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"):upper()
|
||||
local i = 1
|
||||
local win_opts = {}
|
||||
local win_map = {}
|
||||
local laststatus = vim.o.laststatus
|
||||
vim.o.laststatus = 2
|
||||
|
||||
-- Setup UI
|
||||
for _, id in ipairs(selectable) do
|
||||
local char = chars:sub(i, i)
|
||||
|
||||
win_map[char] = id
|
||||
win_opts[id] = {
|
||||
statusline = vim.wo[id].statusline,
|
||||
winhl = vim.wo[id].winhl,
|
||||
}
|
||||
|
||||
utils.set_local(id, {
|
||||
statusline = "%=" .. char .. "%=",
|
||||
winhl = {
|
||||
"StatusLine:WinShiftWindowPicker,StatusLineNC:WinShiftWindowPicker",
|
||||
opt = { method = "append" },
|
||||
},
|
||||
})
|
||||
|
||||
i = i + 1
|
||||
if i > #chars then break end
|
||||
end
|
||||
|
||||
vim.cmd("redraw")
|
||||
local ok, resp = pcall(utils.input_char, "Pick window: ", { prompt_hl = "ModeMsg" })
|
||||
if not ok then
|
||||
utils.clear_prompt()
|
||||
end
|
||||
resp = (resp or ""):upper()
|
||||
|
||||
-- Restore window options
|
||||
for _, id in ipairs(selectable) do
|
||||
utils.set_local(id, win_opts[id])
|
||||
end
|
||||
|
||||
vim.o.laststatus = laststatus
|
||||
|
||||
return win_map[resp]
|
||||
end
|
||||
|
||||
---@param leaf Node
|
||||
---@param flatten? boolean
|
||||
---@return VirtualNode|nil
|
||||
function M.create_virtual_set(leaf, flatten)
|
||||
if not leaf.parent then
|
||||
return
|
||||
end
|
||||
|
||||
local parent = leaf.parent
|
||||
|
||||
-- Handle case where moving the leaf will result in a different virtual set.
|
||||
if flatten
|
||||
and #parent == 2
|
||||
and parent[leaf.index % 2 + 1].type == "leaf"
|
||||
and parent.parent then
|
||||
leaf = utils.tbl_clone(leaf)
|
||||
parent = utils.tbl_clone(parent.parent)
|
||||
parent[leaf.parent.index] = leaf
|
||||
table.insert(parent, leaf.parent.index, utils.tbl_clone(leaf.parent[leaf.index % 2 + 1]))
|
||||
|
||||
for i, l in ipairs(parent) do
|
||||
l.index = i
|
||||
l.parent = parent
|
||||
end
|
||||
end
|
||||
|
||||
local first, last = leaf.index, leaf.index
|
||||
|
||||
for i = leaf.index - 1, 1, -1 do
|
||||
if parent[i].type ~= "leaf" then
|
||||
break
|
||||
end
|
||||
first = i
|
||||
end
|
||||
|
||||
for i = leaf.index + 1, #parent do
|
||||
if parent[i].type ~= "leaf" then
|
||||
break
|
||||
end
|
||||
last = i
|
||||
end
|
||||
|
||||
if (first == leaf.index and last == leaf.index) -- Virtual set is empty
|
||||
or (first == 1 and last == #parent) -- Virtual set is the same as the normal set
|
||||
or last - first + 1 == 2 then -- Virtual sets of 2 leaves are pointless
|
||||
return
|
||||
end
|
||||
|
||||
local target = utils.tbl_clone(leaf)
|
||||
local set = { target = target }
|
||||
for k, v in pairs(parent) do
|
||||
if type(k) ~= "number" then
|
||||
set[k] = v
|
||||
end
|
||||
end
|
||||
for i = first, last do
|
||||
set[#set + 1] = parent[i]
|
||||
end
|
||||
set.parent = parent.parent
|
||||
target.parent = set
|
||||
return set
|
||||
end
|
||||
|
||||
---@param winid integer
|
||||
---@param dir Direction
|
||||
function M.move_win(winid, dir)
|
||||
if M.dir_move_map[dir] then
|
||||
vim.cmd(string.format("%dwincmd %s", api.nvim_win_get_number(winid), M.dir_move_map[dir]))
|
||||
return
|
||||
end
|
||||
|
||||
local tree = M.get_layout_tree()
|
||||
local target_leaf = M.find_leaf(tree, winid)
|
||||
local outer_parent = (target_leaf.parent and target_leaf.parent.parent) or {}
|
||||
|
||||
-- If the target leaf has no parent, there is only one window in the layout.
|
||||
if target_leaf and target_leaf.parent then
|
||||
local ok, err = utils.no_win_event_call(function()
|
||||
if dir == "left" or dir == "right" then
|
||||
-- Horizontal
|
||||
if target_leaf.parent.type == "col" then
|
||||
local set = M.create_virtual_set(target_leaf)
|
||||
target_leaf = (set and set.target) or target_leaf
|
||||
M.col_move_out(target_leaf, target_leaf.parent, dir)
|
||||
else
|
||||
local next_node = M.next_node_horizontal(target_leaf, dir)
|
||||
local set = M.create_virtual_set(target_leaf, true)
|
||||
|
||||
if next_node then
|
||||
if
|
||||
target_leaf.parent.type == "row"
|
||||
and #target_leaf.parent == 2
|
||||
and target_leaf.parent[1].type == "leaf"
|
||||
and target_leaf.parent[2].type == "leaf"
|
||||
then
|
||||
-- Swap the windows
|
||||
M.swap_leaves(target_leaf.parent[1], target_leaf.parent[2])
|
||||
else
|
||||
M.col_move_in(target_leaf, next_node, dir)
|
||||
end
|
||||
elseif set and set.type == "col" then
|
||||
M.col_move_out(set.target, set, dir)
|
||||
elseif outer_parent.type == "col" then
|
||||
M.col_move_out(target_leaf, outer_parent, dir)
|
||||
end
|
||||
end
|
||||
else
|
||||
-- Vertical
|
||||
if target_leaf.parent.type == "row" then
|
||||
local set = M.create_virtual_set(target_leaf)
|
||||
target_leaf = (set and set.target) or target_leaf
|
||||
M.row_move_out(target_leaf, target_leaf.parent, dir)
|
||||
else
|
||||
local next_node = M.next_node_vertical(target_leaf, dir)
|
||||
local set = M.create_virtual_set(target_leaf, true)
|
||||
|
||||
if next_node then
|
||||
if
|
||||
target_leaf.parent.type == "col"
|
||||
and #target_leaf.parent == 2
|
||||
and target_leaf.parent[1].type == "leaf"
|
||||
and target_leaf.parent[2].type == "leaf"
|
||||
then
|
||||
-- Swap the windows
|
||||
M.swap_leaves(target_leaf.parent[1], target_leaf.parent[2])
|
||||
else
|
||||
M.row_move_in(target_leaf, next_node, dir)
|
||||
end
|
||||
elseif set and set.type == "row" then
|
||||
M.row_move_out(set.target, set, dir)
|
||||
elseif outer_parent.type == "row" then
|
||||
M.row_move_out(target_leaf, outer_parent, dir)
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
api.nvim_set_current_win(winid)
|
||||
if not ok then
|
||||
utils.err(err)
|
||||
utils.err(debug.traceback())
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function M.start_move_mode()
|
||||
local char, raw
|
||||
local esc = utils.raw_key("<esc>")
|
||||
local cur_win = api.nvim_get_current_win()
|
||||
local lasthl = vim.wo[cur_win].winhl
|
||||
local conf = config.get_config()
|
||||
local key_dir_map = config.get_key_dir_map()
|
||||
M.save_win_options(cur_win)
|
||||
|
||||
if conf.highlight_moving_win then
|
||||
M.highlight_win(cur_win)
|
||||
end
|
||||
utils.set_local(cur_win, conf.moving_win_options)
|
||||
vim.cmd("redraw")
|
||||
|
||||
local ok, err = pcall(function()
|
||||
while not (char == "q" or raw == esc) do
|
||||
api.nvim_echo({ { "-- WIN MOVE MODE -- press 'q' to exit", "ModeMsg" } }, false, {})
|
||||
char, raw = utils.input_char(nil, { clear_prompt = false, allow_non_ascii = true })
|
||||
local dir = key_dir_map[char or raw]
|
||||
|
||||
if dir then M.move_win(cur_win, dir) end
|
||||
|
||||
vim.cmd("redraw")
|
||||
|
||||
api.nvim_exec_autocmds({ "WinScrolled" }, { modeline = false })
|
||||
vim.schedule(function() vim.cmd("redraw") end)
|
||||
end
|
||||
end)
|
||||
|
||||
utils.clear_prompt()
|
||||
|
||||
if conf.highlight_moving_win then
|
||||
vim.wo[cur_win].winhl = lasthl
|
||||
end
|
||||
|
||||
M.restore_win_options(cur_win)
|
||||
|
||||
if not ok then
|
||||
utils._echo_multiline(err, "ErrorMsg")
|
||||
end
|
||||
end
|
||||
|
||||
function M.start_swap_mode()
|
||||
local cur_win = api.nvim_get_current_win()
|
||||
local lasthl = vim.wo[cur_win].winhl
|
||||
local conf = config.get_config()
|
||||
M.save_win_options(cur_win)
|
||||
|
||||
if conf.highlight_moving_win then
|
||||
M.highlight_win(cur_win)
|
||||
end
|
||||
utils.set_local(cur_win, conf.moving_win_options)
|
||||
vim.cmd("redraw")
|
||||
|
||||
local ok, err = pcall(function()
|
||||
local target = conf.window_picker()
|
||||
|
||||
if target == -1 or target == nil then
|
||||
return
|
||||
end
|
||||
|
||||
local tree = M.get_layout_tree()
|
||||
local cur_leaf = M.find_leaf(tree, cur_win)
|
||||
local target_leaf = M.find_leaf(tree, target)
|
||||
M.swap_leaves(cur_leaf, target_leaf)
|
||||
end)
|
||||
|
||||
if conf.highlight_moving_win then
|
||||
vim.wo[cur_win].winhl = lasthl
|
||||
end
|
||||
|
||||
M.restore_win_options(cur_win)
|
||||
|
||||
if not ok then
|
||||
utils._echo_multiline(err, "ErrorMsg")
|
||||
end
|
||||
end
|
||||
|
||||
function M.save_win_options(winid)
|
||||
win_option_store[winid] = {}
|
||||
local last_winid = api.nvim_get_current_win()
|
||||
utils.no_win_event_call(function()
|
||||
api.nvim_set_current_win(winid)
|
||||
for option, _ in pairs(config.get_config().moving_win_options) do
|
||||
local value = vim.opt_local[option]._value
|
||||
if value ~= "" then
|
||||
win_option_store[winid][option] = value
|
||||
end
|
||||
end
|
||||
end)
|
||||
api.nvim_set_current_win(last_winid)
|
||||
end
|
||||
|
||||
function M.restore_win_options(winid)
|
||||
for option, _ in pairs(config.get_config().moving_win_options) do
|
||||
if win_option_store[winid][option] then
|
||||
utils.set_local(winid, { [option] = win_option_store[winid][option] })
|
||||
else
|
||||
utils.unset_local(winid, option)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function M.highlight_win(winid)
|
||||
local curhl = vim.wo[winid].winhl
|
||||
local hl = {
|
||||
"Normal:WinShiftNormal",
|
||||
"EndOfBuffer:WinShiftEndOfBuffer",
|
||||
"LineNr:WinShiftLineNr",
|
||||
"CursorLineNr:WinShiftCursorLineNr",
|
||||
"SignColumn:WinShiftSignColumn",
|
||||
"FoldColumn:WinShiftFoldColumn",
|
||||
curhl ~= "" and curhl or nil,
|
||||
}
|
||||
|
||||
if vim.fn.has("nvim-0.6") == 1 then
|
||||
hl = utils.vec_join(
|
||||
hl,
|
||||
{
|
||||
"LineNrAbove:WinShiftLineNrAbove",
|
||||
"LineNrBelow:WinShiftLineNrBelow",
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
vim.wo[winid].winhl = table.concat(hl, ",")
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,360 @@
|
||||
local api = vim.api
|
||||
local M = {}
|
||||
|
||||
---@alias vector<T> T[]
|
||||
|
||||
function M._echo_multiline(msg, hl, schedule)
|
||||
if schedule then
|
||||
vim.schedule(function()
|
||||
M._echo_multiline(msg, hl, false)
|
||||
end)
|
||||
return
|
||||
end
|
||||
|
||||
vim.cmd("echohl " .. (hl or "None"))
|
||||
for _, line in ipairs(vim.split(msg, "\n")) do
|
||||
vim.cmd(string.format('echom "%s"', vim.fn.escape(line, [["\]])))
|
||||
end
|
||||
vim.cmd("echohl None")
|
||||
end
|
||||
|
||||
---@param msg string
|
||||
---@param schedule? boolean Schedule the echo call.
|
||||
function M.info(msg, schedule)
|
||||
M._echo_multiline("[WinShift.nvim] " .. msg, "Directory", schedule)
|
||||
end
|
||||
|
||||
---@param msg string
|
||||
---@param schedule? boolean Schedule the echo call.
|
||||
function M.warn(msg, schedule)
|
||||
M._echo_multiline("[WinShift.nvim] " .. msg, "WarningMsg", schedule)
|
||||
end
|
||||
|
||||
---@param msg string
|
||||
---@param schedule? boolean Schedule the echo call.
|
||||
function M.err(msg, schedule)
|
||||
M._echo_multiline("[WinShift.nvim] " .. msg, "ErrorMsg", schedule)
|
||||
end
|
||||
|
||||
---Call the function `f`, ignoring most of the window and buffer related
|
||||
---events. The function is called in protected mode.
|
||||
---@param f function
|
||||
---@return boolean success
|
||||
---@return any result Return value
|
||||
function M.no_win_event_call(f)
|
||||
local last = vim.o.eventignore
|
||||
---@diagnostic disable-next-line: undefined-field
|
||||
vim.opt.eventignore:prepend(
|
||||
"WinEnter,WinLeave,WinNew,WinClosed,BufWinEnter,BufWinLeave,BufEnter,BufLeave"
|
||||
)
|
||||
local ok, err = pcall(f)
|
||||
vim.opt.eventignore = last
|
||||
return ok, err
|
||||
end
|
||||
|
||||
---Escape a string for use as a pattern.
|
||||
---@param s string
|
||||
---@return string
|
||||
function M.pattern_esc(s)
|
||||
local result = string.gsub(s, "[%(|%)|%%|%[|%]|%-|%.|%?|%+|%*|%^|%$]", {
|
||||
["%"] = "%%",
|
||||
["-"] = "%-",
|
||||
["("] = "%(",
|
||||
[")"] = "%)",
|
||||
["."] = "%.",
|
||||
["["] = "%[",
|
||||
["]"] = "%]",
|
||||
["?"] = "%?",
|
||||
["+"] = "%+",
|
||||
["*"] = "%*",
|
||||
["^"] = "%^",
|
||||
["$"] = "%$",
|
||||
})
|
||||
return result
|
||||
end
|
||||
|
||||
function M.tbl_clone(t)
|
||||
if not t then
|
||||
return
|
||||
end
|
||||
local clone = {}
|
||||
|
||||
for k, v in pairs(t) do
|
||||
clone[k] = v
|
||||
end
|
||||
|
||||
return clone
|
||||
end
|
||||
|
||||
function M.tbl_deep_clone(t)
|
||||
if not t then
|
||||
return
|
||||
end
|
||||
local clone = {}
|
||||
|
||||
for k, v in pairs(t) do
|
||||
if type(v) == "table" then
|
||||
clone[k] = M.tbl_deep_clone(v)
|
||||
else
|
||||
clone[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
return clone
|
||||
end
|
||||
|
||||
function M.tbl_pack(...)
|
||||
return { n = select("#", ...), ... }
|
||||
end
|
||||
|
||||
function M.tbl_unpack(t, i, j)
|
||||
return unpack(t, i or 1, j or t.n or #t)
|
||||
end
|
||||
|
||||
function M.tbl_clear(t)
|
||||
for k, _ in pairs(t) do
|
||||
t[k] = nil
|
||||
end
|
||||
end
|
||||
|
||||
---Create a shallow copy of a portion of a vector.
|
||||
---@param t vector
|
||||
---@param first? integer First index, inclusive
|
||||
---@param last? integer Last index, inclusive
|
||||
---@return vector
|
||||
function M.vec_slice(t, first, last)
|
||||
local slice = {}
|
||||
for i = first or 1, last or #t do
|
||||
table.insert(slice, t[i])
|
||||
end
|
||||
|
||||
return slice
|
||||
end
|
||||
|
||||
---Join multiple vectors into one.
|
||||
---@vararg vector
|
||||
---@return vector
|
||||
function M.vec_join(...)
|
||||
local result = {}
|
||||
local args = {...}
|
||||
local n = 0
|
||||
|
||||
for i = 1, select("#", ...) do
|
||||
if type(args[i]) ~= "nil" then
|
||||
if type(args[i]) ~= "table" then
|
||||
result[n + 1] = args[i]
|
||||
n = n + 1
|
||||
else
|
||||
for j, v in ipairs(args[i]) do
|
||||
result[n + j] = v
|
||||
end
|
||||
n = n + #args[i]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
---Return the first index a given object can be found in a vector, or -1 if
|
||||
---it's not present.
|
||||
---@param t vector
|
||||
---@param v any
|
||||
---@return integer
|
||||
function M.vec_indexof(t, v)
|
||||
for i, vt in ipairs(t) do
|
||||
if vt == v then
|
||||
return i
|
||||
end
|
||||
end
|
||||
return -1
|
||||
end
|
||||
|
||||
---Append any number of objects to the end of a vector. Pushing `nil`
|
||||
---effectively does nothing.
|
||||
---@param t vector
|
||||
---@return vector t
|
||||
function M.vec_push(t, ...)
|
||||
for _, v in ipairs({...}) do
|
||||
t[#t + 1] = v
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
---Simple string templating
|
||||
---Example template: "${name} is ${value}"
|
||||
---@param str string Template string
|
||||
---@param table table Key-value pairs to replace in the string
|
||||
function M.str_template(str, table)
|
||||
return (str:gsub("($%b{})", function(w)
|
||||
return table[w:sub(3, -2)] or w
|
||||
end))
|
||||
end
|
||||
|
||||
function M.clear_prompt()
|
||||
vim.api.nvim_echo({ { "" } }, false, {})
|
||||
vim.cmd("redraw")
|
||||
end
|
||||
|
||||
---@class InputCharSpec
|
||||
---@field clear_prompt boolean (default: true)
|
||||
---@field allow_non_ascii boolean (default: true)
|
||||
---@field prompt_hl string (default: nil)
|
||||
|
||||
---@param prompt string
|
||||
---@param opt InputCharSpec
|
||||
---@return string Char
|
||||
---@return string Raw
|
||||
function M.input_char(prompt, opt)
|
||||
opt = vim.tbl_extend("keep", opt or {}, {
|
||||
clear_prompt = true,
|
||||
allow_non_ascii = false,
|
||||
prompt_hl = nil,
|
||||
})
|
||||
|
||||
if prompt then
|
||||
vim.api.nvim_echo({ { prompt, opt.prompt_hl } }, false, {})
|
||||
end
|
||||
|
||||
local c
|
||||
if not opt.allow_non_ascii then
|
||||
while type(c) ~= "number" do
|
||||
c = vim.fn.getchar()
|
||||
end
|
||||
else
|
||||
c = vim.fn.getchar()
|
||||
end
|
||||
|
||||
if opt.clear_prompt then
|
||||
M.clear_prompt()
|
||||
end
|
||||
|
||||
local s = type(c) == "number" and vim.fn.nr2char(c) or nil
|
||||
local raw = type(c) == "number" and s or c
|
||||
|
||||
---@diagnostic disable-next-line: return-type-mismatch
|
||||
return s, raw
|
||||
end
|
||||
|
||||
function M.input(prompt, default, completion)
|
||||
local v = vim.fn.input({
|
||||
prompt = prompt,
|
||||
default = default,
|
||||
completion = completion,
|
||||
cancelreturn = "__INPUT_CANCELLED__",
|
||||
})
|
||||
M.clear_prompt()
|
||||
return v
|
||||
end
|
||||
|
||||
function M.raw_key(vim_key)
|
||||
return api.nvim_eval(string.format([["\%s"]], vim_key))
|
||||
end
|
||||
|
||||
function M.pause(msg)
|
||||
vim.cmd("redraw")
|
||||
M.input_char(
|
||||
"-- PRESS ANY KEY TO CONTINUE -- " .. (msg or ""),
|
||||
{ allow_non_ascii = true, prompt_hl = "Directory" }
|
||||
)
|
||||
end
|
||||
|
||||
---Map of options that accept comma separated, list-like values, but don't work
|
||||
---correctly with Option:set(), Option:append(), Option:prepend(), and
|
||||
---Option:remove() (seemingly for legacy reasons).
|
||||
---WARN: This map is incomplete!
|
||||
local list_like_options = {
|
||||
winhighlight = true,
|
||||
listchars = true,
|
||||
fillchars = true,
|
||||
}
|
||||
|
||||
---@class utils.set_local.Opt
|
||||
---@field method '"set"'|'"remove"'|'"append"'|'"prepend"' Assignment method. (default: "set")
|
||||
|
||||
---@class utils.set_local.ListSpec : string[]
|
||||
---@field opt utils.set_local.Opt
|
||||
|
||||
---@alias WindowOptions table<string, boolean|integer|string|utils.set_local.ListSpec>
|
||||
|
||||
---@param winids number[]|number Either a list of winids, or a single winid (0 for current window).
|
||||
---@param option_map WindowOptions
|
||||
---@param opt? utils.set_local.Opt
|
||||
function M.set_local(winids, option_map, opt)
|
||||
if type(winids) ~= "table" then
|
||||
winids = { winids }
|
||||
end
|
||||
|
||||
opt = vim.tbl_extend("keep", opt or {}, { method = "set" }) --[[@as table ]]
|
||||
|
||||
for _, id in ipairs(winids) do
|
||||
api.nvim_win_call(id, function()
|
||||
for option, value in pairs(option_map) do
|
||||
local o = opt
|
||||
local fullname = api.nvim_get_option_info(option).name
|
||||
local is_list_like = list_like_options[fullname]
|
||||
local cur_value = vim.o[fullname]
|
||||
|
||||
if type(value) == "table" then
|
||||
if value.opt then
|
||||
o = vim.tbl_extend("force", opt, value.opt) --[[@as table ]]
|
||||
end
|
||||
|
||||
if is_list_like then
|
||||
value = table.concat(value, ",")
|
||||
end
|
||||
end
|
||||
|
||||
if o.method == "set" then
|
||||
vim.opt_local[option] = value
|
||||
|
||||
else
|
||||
if o.method == "remove" then
|
||||
if is_list_like then
|
||||
vim.opt_local[fullname] = cur_value:gsub(",?" .. vim.pesc(value), "")
|
||||
else
|
||||
vim.opt_local[fullname]:remove(value)
|
||||
end
|
||||
|
||||
elseif o.method == "append" then
|
||||
if is_list_like then
|
||||
vim.opt_local[fullname] = ("%s%s"):format(
|
||||
cur_value ~= "" and cur_value .. "," or "",
|
||||
value
|
||||
)
|
||||
else
|
||||
vim.opt_local[fullname]:append(value)
|
||||
end
|
||||
|
||||
elseif o.method == "prepend" then
|
||||
if is_list_like then
|
||||
vim.opt_local[fullname] = ("%s%s%s"):format(
|
||||
value,
|
||||
cur_value ~= "" and "," or "",
|
||||
cur_value
|
||||
)
|
||||
else
|
||||
vim.opt_local[fullname]:prepend(value)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
---@param winids number[]|number Either a list of winids, or a single winid (0 for current window).
|
||||
---@param option string
|
||||
function M.unset_local(winids, option)
|
||||
if type(winids) ~= "table" then
|
||||
winids = { winids }
|
||||
end
|
||||
|
||||
for _, id in ipairs(winids) do
|
||||
api.nvim_win_call(id, function()
|
||||
vim.opt_local[option] = nil
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
Reference in New Issue
Block a user