mirror of
https://github.com/PriceHiller/ppd.nvim.git
synced 2024-12-30 14:09:15 -06:00
185 lines
4.9 KiB
Lua
185 lines
4.9 KiB
Lua
local config = require("ppd.config")
|
|
|
|
---@alias ppd.Dirstack.scope "global" | "tab" | "window"
|
|
|
|
---@class ppd.DirStack
|
|
---@field scope ppd.Dirstack.scope
|
|
---@field autocmd integer?
|
|
---@field watch_cd_enabled boolean
|
|
---@field augroup integer
|
|
---@field stack string[]
|
|
local DirStack = {}
|
|
|
|
--- Create a new Dirstack
|
|
---@param scope ppd.Dirstack.scope
|
|
---@return ppd.DirStack
|
|
function DirStack.new(scope)
|
|
local valid_scopes = {
|
|
"global",
|
|
"tab",
|
|
"window",
|
|
}
|
|
vim.validate {
|
|
scope = {
|
|
scope,
|
|
function(sc)
|
|
return vim.tbl_contains(valid_scopes, sc)
|
|
end,
|
|
"a valid DirStack scope",
|
|
},
|
|
}
|
|
|
|
local self = setmetatable({
|
|
scope = scope,
|
|
stack = {},
|
|
augroup = vim.api.nvim_create_augroup("Ppd" .. scope:upper(), { clear = false }),
|
|
}, { __index = DirStack })
|
|
|
|
-- Put at minimum, a single directory on the stack by default so it's possible to unwind to the
|
|
if scope == "global" then
|
|
self:push(vim.fn.getcwd(-1, -1))
|
|
elseif scope == "tab" then
|
|
self:push(vim.fn.getcwd(-1, 0))
|
|
else
|
|
self:push(vim.fn.getcwd(0))
|
|
end
|
|
|
|
-- Start the watch *after* we have a single base directory on the path
|
|
self:watch_cd()
|
|
|
|
return self
|
|
end
|
|
|
|
--- Creates an autocmd if not already called to watch cd events for the
|
|
--- relevant scope and push the directories onto the stack if `auto_cd` is
|
|
--- enabled
|
|
function DirStack:watch_cd()
|
|
self.watch_cd_enabled = true
|
|
if self.autocmd then
|
|
return
|
|
end
|
|
|
|
self.autocmd = vim.api.nvim_create_autocmd("DirChanged", {
|
|
group = self.augroup,
|
|
desc = "Ppd " .. self.scope .. " Listener for Auto CD",
|
|
pattern = self.scope,
|
|
callback = function(args)
|
|
if config.auto_cd and self.watch_cd_enabled then
|
|
local new_dir = args.file
|
|
self:push(new_dir)
|
|
end
|
|
end,
|
|
})
|
|
end
|
|
|
|
function DirStack:unwatch_cd()
|
|
self.watch_cd_enabled = false
|
|
end
|
|
|
|
--- Get the string representation of the stack
|
|
---@return string
|
|
function DirStack:repr()
|
|
return table.concat(self.stack, " ")
|
|
end
|
|
|
|
--- Removes one or more directories off the stack and returns them
|
|
---@private
|
|
---@param count integer? The number of directories to pop or nil for one
|
|
---@return string[] removed_paths The paths removed in order from the stack
|
|
function DirStack:pop(count)
|
|
count = count or 1
|
|
local removed_paths = {}
|
|
while not self:empty() and count > 0 do
|
|
count = count - 1
|
|
table.insert(removed_paths, table.remove(self.stack))
|
|
end
|
|
|
|
return removed_paths
|
|
end
|
|
|
|
--- Get whether the stack is empty, empty being defined as only one element
|
|
---@return boolean is_empty true if the stack is empty
|
|
function DirStack:empty()
|
|
return #self.stack == 1
|
|
end
|
|
|
|
--- Push a path onto the stack
|
|
---@private
|
|
---@param path string
|
|
function DirStack:push(path)
|
|
path = vim.fn.fnamemodify(vim.fn.fnamemodify(path, ":p:h"), ":~")
|
|
if not ((config.dedup.all and self:check_dup_all(path)) or (config.dedup.top and self:check_dup_top(path))) then
|
|
table.insert(self.stack, path)
|
|
end
|
|
end
|
|
|
|
--- Peek the top of the stack
|
|
---@return string? top_path The path at the top of the stack if it exists
|
|
function DirStack:peek()
|
|
return self.stack[#self.stack]
|
|
end
|
|
|
|
--- Changes directory accounting for the current scope
|
|
---@private
|
|
---@param path string A path to change the directory to
|
|
function DirStack:cd(path)
|
|
local cmd = ({
|
|
["global"] = vim.cmd.cd,
|
|
["tab"] = vim.cmd.tcd,
|
|
["window"] = vim.cmd.lcd,
|
|
})[self.scope]
|
|
self:unwatch_cd()
|
|
cmd(path)
|
|
self:watch_cd()
|
|
end
|
|
|
|
--- Checks if the topmost element in the stack is the same as the given path
|
|
---@param path string
|
|
---@return boolean dup_found true if the topmost element is the same as the given path
|
|
function DirStack:check_dup_top(path)
|
|
return self:peek() == path
|
|
end
|
|
|
|
--- Checks if any element within the current stack is the same as the given path
|
|
---@param path string
|
|
---@return boolean dup_found true if a duplicate value is located in the stack
|
|
function DirStack:check_dup_all(path)
|
|
return vim.iter(self.stack):any(function(val)
|
|
return val == path
|
|
end)
|
|
end
|
|
|
|
--- Change the current directory for the relevant scope to the given path and push it onto the stack
|
|
---@param path string
|
|
function DirStack:pushd(path)
|
|
if not path or path:len() == 0 then
|
|
vim.notify(self:repr())
|
|
return
|
|
end
|
|
|
|
self:cd(path)
|
|
self:push(path)
|
|
|
|
if config.notify.on_pushd then
|
|
vim.notify(self:repr())
|
|
end
|
|
end
|
|
|
|
--- Removes one or more directories off the stack and changes the path to it
|
|
---@param count integer? The number of directories to pop or nil for one
|
|
function DirStack:popd(count)
|
|
count = count or 1
|
|
self:pop(count)
|
|
|
|
local top_path = self:peek()
|
|
if top_path then
|
|
self:cd(top_path)
|
|
end
|
|
|
|
if config.notify.on_popd then
|
|
vim.notify(self:repr())
|
|
end
|
|
end
|
|
|
|
return DirStack
|