snailed
/
taolf
Archived
2
0
Fork 0
This commit is contained in:
Luca Bilke 2024-01-28 23:11:42 +01:00
parent 5bc3a78955
commit 18c465b922
No known key found for this signature in database
GPG Key ID: AD6630D0A1E650AC
6 changed files with 172 additions and 345 deletions

View File

@ -1,9 +1,5 @@
{
"diagnostics.globals": [
"vim"
],
"diagnostics.disable": [
"duplicate-doc-field",
"duplicate-set-field"
]
}

View File

@ -4,6 +4,22 @@ local Config = require("lf.config")
local api = vim.api
---Make `Lf` become the file manager that opens whenever a directory buffer is loaded
---@param bufnr integer
---@return boolean
local function become_dir_fman(bufnr)
local bufname = api.nvim_buf_get_name(bufnr)
if bufname == "" then
return false
end
local stat = vim.loop.fs_stat(bufname)
if type(stat) ~= "table" or (type(stat) == "table" and stat.type ~= "directory") then
return false
end
return true
end
---Setup the Lf plugin
---@param cfg Lf.Config
function M.setup(cfg)
@ -14,6 +30,52 @@ function M.setup(cfg)
cfg = cfg or {}
M.__conf = cfg
Config.init()
api.nvim_create_user_command("Lf", function(tbl)
require("lf").start(tbl.args)
end, { nargs = "*", complete = "file" })
if Config.replace_netrw then
local group = api.nvim_create_augroup("Lf_ReplaceNetrw", { clear = true })
if vim.g.loaded_netrwPlugin ~= 1 then
api.nvim_create_autocmd("FileType", {
desc = "Display message about Lf not being default file manager",
group = group,
pattern = "netrw",
once = true,
callback = function()
api.nvim_err_writeln([[Lf cannot be the default file manager with netrw enabled.]] ..
[[Put `vim.g.loaded_netrwPlugin` in your configuration.]])
end,
})
end
api.nvim_create_autocmd("VimEnter", {
desc = "Override the default file manager (i.e., netrw)",
group = group,
pattern = "*",
nested = true,
callback = function(a)
if vim.fn.exists("#FileExplorer") then
api.nvim_create_augroup("FileExplorer", { clear = true })
end
end,
})
api.nvim_create_autocmd("BufEnter", {
desc = "After overriding default file manager, open Lf",
group = group,
pattern = "*",
once = true,
callback = function(a)
if become_dir_fman(a.buf) then
vim.defer_fn(function()
require("lf").start(a.file)
end, 1)
end
end,
})
end
end
---Start the file manager
@ -44,7 +106,7 @@ function M.start(path, cfg)
return
end
local opts = vim.tbl_deep_extend("keep", cfg or {}, Config.data)
cfg = vim.tbl_deep_extend("keep", cfg or {}, Config.data)
Lf:new(cfg or M.__conf):start(path)
end
end

View File

@ -14,7 +14,6 @@ local default = {
default_cmd = "lf",
default_action = "drop",
dir = "gwd",
escape_quit = false,
focus_on_open = true,
count = nil,
}
@ -26,7 +25,6 @@ local function validate(cfg)
default_cmd = {cfg.default_cmd, "s", false},
default_action = {cfg.default_action, "s", false},
dir = {cfg.dir, "s", false},
escape_quit = {cfg.escape_quit, "b", false},
focus_on_open = {cfg.focus_on_open, "b", false},
count = {cfg.count, "n", true},
})
@ -76,12 +74,3 @@ return setmetatable(Config, {
api.nvim_err_writeln(("do not set invalid config values: %s => %s"):format(key, val))
end,
})
---@alias Lf.directory "'gwd'"|"''"|nil|string
---@class Lf.Config
---@field default_cmd? string Default `lf` command
---@field default_action? string Default action when `Lf` opens a file
---@field dir? Lf.directory Directory where `lf` starts ('gwd' is git-working-directory, ""/nil is CWD)
---@field escape_quit? boolean Whether escape should be mapped to quit
---@field focus_on_open? boolean Whether Lf should open focused on current file
---@field count? integer A number that triggers that specific terminal

View File

@ -1,6 +1,3 @@
local utils = require("lf.utils")
local fs = utils.fs
local cmd = vim.cmd
local api = vim.api
local fn = vim.fn
@ -14,105 +11,12 @@ if not ok then
end
local Config = require("lf.config")
-- local promise = require("promise")
-- local async = require("async")
---@alias Mode "n" | "i" | "?"
--- @class TerminalState
--- @field mode Mode
--- @class TermCreateArgs
--- @field newline_chr? string user specified newline chararacter
--- @field cmd? string a custom command to run
--- @field direction? string the layout style for the terminal
--- @field id number?
--- @field highlights table<string, table<string, string>>?
--- @field dir string? the directory for the terminal
--- @field count number? the count that triggers that specific terminal
--- @field display_name string?
--- @field hidden boolean? whether or not to include this terminal in the terminals list
--- @field close_on_exit boolean? whether or not to close the terminal window when the process exits
--- @field auto_scroll boolean? whether or not to scroll down on terminal output
--- @field float_opts table<string, any>?
--- @field on_stdout fun(t: Terminal, job: number, data: string[]?, name: string?)?
--- @field on_stderr fun(t: Terminal, job: number, data: string[], name: string)?
--- @field on_exit fun(t: Terminal, job: number, exit_code: number?, name: string?)?
--- @field on_create fun(term:Terminal)?
--- @field on_open fun(term:Terminal)?
--- @field on_close fun(term:Terminal)?
--- @class Terminal
--- @field newline_chr string
--- @field cmd string
--- @field direction string the layout style for the terminal
--- @field id number
--- @field bufnr number
--- @field window number
--- @field job_id number
--- @field highlights table<string, table<string, string>>
--- @field dir string the directory for the terminal
--- @field name string the name of the terminal
--- @field count number the count that triggers that specific terminal
--- @field hidden boolean whether or not to include this terminal in the terminals list
--- @field close_on_exit boolean? whether or not to close the terminal window when the process exits
--- @field auto_scroll boolean? whether or not to scroll down on terminal output
--- @field float_opts table<string, any>?
--- @field display_name string?
--- @field env table<string, string> environmental variables passed to jobstart()
--- @field clear_env boolean use clean job environment, passed to jobstart()
--- @field on_stdout fun(t: Terminal, job: number, data: string[]?, name: string?)?
--- @field on_stderr fun(t: Terminal, job: number, data: string[], name: string)?
--- @field on_exit fun(t: Terminal, job: number, exit_code: number?, name: string?)?
--- @field on_create fun(term:Terminal)?
--- @field on_open fun(term:Terminal)?
--- @field on_close fun(term:Terminal)?
--- @field _display_name fun(term: Terminal): string
--- @field __state TerminalState
--- @field new fun(self, term: TermCreateArgs?): Terminal
--- @field close fun(self)
--- @field open fun(self, size: number?, direction: string?)
local Terminal = require("toggleterm.terminal").Terminal
---@class Lf
---@field cfg Lf.Config Configuration options
---@field term Terminal toggleterm terminal
---@field view_idx number Current index of configuration `views`
---@field winid? number `Terminal` window id
---@field curfile? string File path of the currently opened file
---@field tmp_id? string Path to a file containing `lf`'s id
---@field tmp_sel string Path to a file containing `lf`'s selection(s)
---@field tmp_lastdir string Path to a file containing the last directory `lf` was in
---@field id number? Current `lf` session id
---@field bufnr number The open file's buffer number
---@field arglist string[] The argument list to neovim
---@field action string The current action to open the file
---@field signcolumn string The signcolumn set by the user before the terminal buffer overrides it
local Lf = {}
Lf.__index = Lf
---@private
---Setup `toggleterm`'s `Terminal`
local function setup_term()
terminal.setup({
size = function(term)
if term.direction == "horizontal" then
return o.lines * 0.4
elseif term.direction == "vertical" then
return o.columns * 0.5
end
end,
hide_numbers = true,
shade_filetypes = {},
shade_terminals = false,
start_in_insert = true,
insert_mappings = false,
terminal_mappings = true,
persist_mode = false,
persist_size = false,
})
end
---Setup a new instance of `Lf`
---Configuration has not been fully parsed by the end of this function
---A `Terminal` becomes attached and is able to be toggled
@ -128,10 +32,11 @@ function Lf:new(config)
self:__set_argv()
self.bufnr = 0
self.view_idx = 1
self.action = self.cfg.default_action
-- Needs to be grabbed here before the terminal buffer is created
self.signcolumn = o.signcolumn
setup_term()
terminal.setup()
self:__create_term()
return self
@ -144,8 +49,7 @@ function Lf:__create_term()
cmd = self.cfg.default_cmd,
dir = self.cfg.dir,
close_on_exit = true,
hidden = false,
display_name = "Lf",
hidden = true,
count = self.cfg.count,
})
end
@ -176,7 +80,13 @@ end
---@return Lf?
function Lf:__open_in(path)
if path == "gwd" or path == "git_dir" then
path = utils.git_dir()
local gitdir = fn.system(("git -C %s rev-parse --show-toplevel"):format(fn.expand("%:p:h")))
if gitdir:match("^fatal:.*") then
api.nvim_out_write("Failed to get git directory")
return
end
path = vim.trim(gitdir)
end
if type(path) == "string" then
@ -187,14 +97,14 @@ function Lf:__open_in(path)
local stat = uv.fs_stat(path)
if not type(stat) == "table" then
local cwd = uv.cwd()
local cwd = uv.cwd() --[[@as string]]
stat = uv.fs_stat(cwd)
path = cwd --[[@as string]]
path = cwd
end
-- Should be fine, but just checking
if stat and not stat.type == "directory" then
path = fs.dirname(path)
path = fn.fnamemodify(path, ":h")
end
self.term.dir = path
@ -214,7 +124,7 @@ function Lf:__set_cmd_wrapper()
local open_on = self.term.dir
if
self.cfg.focus_on_open
and fs.dirname(self.curfile) == self.term.dir
and fn.fnamemodify(self.curfile, ":h") == self.term.dir
then
open_on = self.curfile
end
@ -261,6 +171,16 @@ function Lf:__on_open(term)
-- end, 800)
end
local function read_file(path)
-- tonumber(444, 8) == 292
local fd = assert(uv.fs_open(fn.expand(path), "r", 292))
local stat = assert(uv.fs_fstat(fd))
local buffer = assert(uv.fs_read(fd, stat.size, 0))
uv.fs_close(fd)
return buffer, stat
end
---@private
---A callback for the `Terminal`
---
@ -270,7 +190,7 @@ function Lf:__callback(term)
(self.action == "cd" or self.action == "lcd")
and uv.fs_stat(self.tmp_lastdir)
then
local last_dir = utils.read_file(self.tmp_lastdir)
local last_dir = read_file(self.tmp_lastdir)
if type(last_dir) == "string" then
cmd(("%s %s"):format(self.action, fn.fnameescape(last_dir)))
return
@ -284,8 +204,8 @@ function Lf:__callback(term)
cmd(("%s %s"):format(self.action, fesc))
local args = table.concat(self.arglist, " ")
if string.len(args) > 0 then
cmd.argadd(args)
cmd.argdedupe()
cmd.argadd(args)
cmd.argdedupe()
end
self:__set_argv()
end

82
lua/lf/meta.lua Normal file
View File

@ -0,0 +1,82 @@
---@meta
---@alias Mode "n" | "i" | "?"
--- @class TerminalState
--- @field mode Mode
--- @class TermCreateArgs
--- @field newline_chr? string user specified newline chararacter
--- @field cmd? string a custom command to run
--- @field direction? string the layout style for the terminal
--- @field id number?
--- @field highlights table<string, table<string, string>>?
--- @field dir string? the directory for the terminal
--- @field count number? the count that triggers that specific terminal
--- @field display_name string?
--- @field hidden boolean? whether or not to include this terminal in the terminals list
--- @field close_on_exit boolean? whether or not to close the terminal window when the process exits
--- @field auto_scroll boolean? whether or not to scroll down on terminal output
--- @field float_opts table<string, any>?
--- @field on_stdout fun(t: Terminal, job: number, data: string[]?, name: string?)?
--- @field on_stderr fun(t: Terminal, job: number, data: string[], name: string)?
--- @field on_exit fun(t: Terminal, job: number, exit_code: number?, name: string?)?
--- @field on_create fun(term:Terminal)?
--- @field on_open fun(term:Terminal)?
--- @field on_close fun(term:Terminal)?
--- @class Terminal
--- @field newline_chr string
--- @field cmd string
--- @field direction string the layout style for the terminal
--- @field id number
--- @field bufnr number
--- @field window number
--- @field job_id number
--- @field highlights table<string, table<string, string>>
--- @field dir string the directory for the terminal
--- @field name string the name of the terminal
--- @field count number the count that triggers that specific terminal
--- @field hidden boolean whether or not to include this terminal in the terminals list
--- @field close_on_exit boolean? whether or not to close the terminal window when the process exits
--- @field auto_scroll boolean? whether or not to scroll down on terminal output
--- @field float_opts table<string, any>?
--- @field display_name string?
--- @field env table<string, string> environmental variables passed to jobstart()
--- @field clear_env boolean use clean job environment, passed to jobstart()
--- @field on_stdout fun(t: Terminal, job: number, data: string[]?, name: string?)?
--- @field on_stderr fun(t: Terminal, job: number, data: string[], name: string)?
--- @field on_exit fun(t: Terminal, job: number, exit_code: number?, name: string?)?
--- @field on_create fun(term:Terminal)?
--- @field on_open fun(term:Terminal)?
--- @field on_close fun(term:Terminal)?
--- @field _display_name fun(term: Terminal): string
--- @field __state TerminalState
--- @field new fun(self, term: TermCreateArgs?): Terminal
--- @field close fun(self)
--- @field open fun(self, size: number?, direction: string?)
---@alias Lf.directory "'gwd'"|"''"|nil|string
---@class Lf.Config
---@field default_cmd? string Default `lf` command
---@field default_action? string Default action when `Lf` opens a file
---@field dir? Lf.directory Directory where `lf` starts ('gwd' is git-working-directory, ""/nil is CWD)
---@field focus_on_open? boolean Whether Lf should open focused on current file
---@field count? integer A number that triggers that specific terminal
---@class Lf
---@field cfg Lf.Config Configuration options
---@field term Terminal toggleterm terminal
---@field view_idx number Current index of configuration `views`
---@field winid? number `Terminal` window id
---@field curfile? string File path of the currently opened file
---@field tmp_id? string Path to a file containing `lf`'s id
---@field tmp_sel string Path to a file containing `lf`'s selection(s)
---@field tmp_lastdir string Path to a file containing the last directory `lf` was in
---@field id number? Current `lf` session id
---@field bufnr number The open file's buffer number
---@field arglist string[] The argument list to neovim
---@field action string The current action to open the file
---@field signcolumn string The signcolumn set by the user before the terminal buffer overrides it
---@field start fun(self, path: string?)

View File

@ -1,222 +0,0 @@
local M = {}
--
local uv = vim.loop
local fn = vim.fn
-- local api = vim.api
-- local levels = vim.log.levels
-- local o = vim.o
--
-- ---Echo a message with `nvim_echo`
-- ---@param msg string message
-- ---@param hl string highlight group
-- function M.echomsg(msg, hl)
-- hl = hl or "Title"
-- api.nvim_echo({{msg, hl}}, true, {})
-- end
--
-- ---Display notification message
-- ---@param msg string
-- ---@param level number
-- ---@param opts table?
-- function M.notify(msg, level, opts)
-- opts = vim.tbl_extend("force", opts or {}, {title = "lf.nvim"})
-- vim.notify(msg, level, opts)
-- end
--
-- ---INFO message
-- ---@param msg string
-- ---@param print? boolean
-- ---@param opts table?
-- function M.info(msg, print, opts)
-- if print then
-- M.echomsg(("[INFO]: %s"):format(msg), "Directory")
-- else
-- M.notify(msg, levels.INFO, opts)
-- end
-- end
--
-- ---WARN message
-- ---@param msg string
-- ---@param print? boolean
-- ---@param opts table?
-- function M.warn(msg, print, opts)
-- if print then
-- M.echomsg(("[WARN]: %s"):format(msg), "WarningMsg")
-- else
-- M.notify(msg, levels.WARN, opts)
-- end
-- end
--
-- ---ERROR message
-- ---@param msg string
-- ---@param print? boolean
-- ---@param opts table?
-- function M.err(msg, print, opts)
-- if print then
-- M.echomsg(("[ERROR]: %s"):format(msg), "ErrorMsg")
-- else
-- M.notify(msg, levels.ERROR, opts)
-- end
-- end
--
---@param path string
---@diagnostic disable-next-line: undefined-doc-name
---@return uv_fs_t|string
---@diagnostic disable-next-line: undefined-doc-name
---@return uv.aliases.fs_stat_table?
function M.read_file(path)
-- tonumber(444, 8) == 292
local fd = assert(uv.fs_open(fn.expand(path), "r", 292))
local stat = assert(uv.fs_fstat(fd))
local buffer = assert(uv.fs_read(fd, stat.size, 0))
uv.fs_close(fd)
return buffer, stat
end
--
-- ---Create a neovim keybinding
-- ---@param mode string vim mode in a single letter
-- ---@param lhs string keys that are bound
-- ---@param rhs string|function string or lua function that is mapped to the keys
-- ---@param opts table? options set for the mapping
-- function M.map(mode, lhs, rhs, opts)
-- opts = opts or {}
-- opts.noremap = opts.noremap == nil and true or opts.noremap
-- vim.keymap.set(mode, lhs, rhs, opts)
-- end
--
-- ---Get Neovim window height
-- ---@return number
-- function M.height()
-- return o.lines - o.cmdheight
-- end
--
-- ---Get neovim window width (minus signcolumn)
-- ---@param bufnr number: Buffer number from the file that Lf is opened from
-- ---@param signcolumn string: Signcolumn option set by the user, not the terminal buffer
-- ---@return number
-- function M.width(bufnr, signcolumn)
-- if signcolumn:match("no") then
-- return o.columns
-- end
--
-- -- This is a rough estimate of the signcolumn
-- local width = #tostring(api.nvim_buf_line_count(bufnr))
-- local col = vim.split(signcolumn, ":")
-- if #col == 2 then
-- width = width + tonumber(col[2])
-- end
--
-- return o.columns - width
-- end
--
-- ---Get the table that is passed to `api.nvim_win_set_config`
-- ---@param opts table
-- ---@param bufnr number Buffer number from the file that Lf is opened from
-- ---@param signcolumn string Signcolumn option set by the user, not the terminal buffer
-- ---@return table
-- function M.get_view(opts, bufnr, signcolumn)
-- opts = opts or {}
--
-- local width = opts.width
-- if width > o.columns then
-- M.err(("width (%d) cannot be greater than columns (%d)"):format(width, o.columns))
-- width = nil
-- end
-- width = width or M.width(bufnr, signcolumn)
-- if width < 0 then
-- if width > -1 then
-- width = fn.float2nr(o.columns + fn.round(width * o.columns))
-- else
-- width = o.columns + width
-- end
-- else
-- if width < 1 then
-- width = fn.float2nr(fn.round(width * o.columns))
-- end
-- end
--
-- local height = opts.height
-- if height > o.lines then
-- M.err(("height (%d) cannot be greater than lines (%d)"):format(height, o.lines))
-- height = nil
-- end
-- height = height or M.height()
-- if height < 0 then
-- if height > -1 then
-- height = fn.float2nr(o.lines + fn.round(height * o.lines))
-- else
-- height = o.lines + height
-- end
-- else
-- if height < 1 then
-- height = fn.float2nr(fn.round(height * o.lines))
-- end
-- end
--
-- local col = opts.col
-- and fn.float2nr(fn.round(opts.col * o.columns))
-- or math.ceil(o.columns - width) * 0.5 - 1
-- local row = opts.row
-- and fn.float2nr(fn.round(opts.row * o.lines))
-- or math.ceil(o.lines - height) * 0.5 - 1
--
-- return {
-- col = col, -- fractional allowed
-- row = row, -- fractional allowed
-- width = width, -- minimum of 1
-- height = height, -- minimum of 1
-- relative = "editor",
-- style = "minimal",
-- }
-- end
--
-- ---@return string|nil
-- function M.git_dir()
-- ---@diagnostic disable-next-line: missing-parameter
-- local gitdir = fn.system(("git -C %s rev-parse --show-toplevel"):format(fn.expand("%:p:h")))
--
-- if gitdir:match("^fatal:.*") then
-- M.info("Failed to get git directory")
-- return
-- end
-- return vim.trim(gitdir)
-- end
--
-- ---Set the tmux statusline when opening/closing `Lf`
-- ---@param disable boolean: whether the statusline is being enabled or disabled
-- function M.tmux(disable)
-- if not vim.env.TMUX then
-- return
-- end
-- if disable then
-- fn.system([[tmux set status off]])
-- fn.system([[tmux list-panes -F '\#F' | grep -q Z || tmux resize-pane -Z]])
-- else
-- fn.system([[tmux set status on]])
-- fn.system([[tmux list-panes -F '\#F' | grep -q Z && tmux resize-pane -Z]])
-- end
-- end
--
-- -- ╭──────────────────────────────────────────────────────────╮
-- -- │ vim.fs replacement │
-- -- ╰──────────────────────────────────────────────────────────╯
--
-- M.fs = {}
--
-- ---Return basename of the given file entry
-- ---
-- ---@param file string: File or directory
-- ---@return string: Basename of `file`
-- function M.fs.basename(file)
-- return fn.fnamemodify(file, ":t")
-- end
---Return parent directory of the given file entry
---
---@param file string: File or directory
---@return string: Parent directory of file
function M.fs.dirname(file)
return fn.fnamemodify(file, ":h")
end
return M