snailed
/
taolf
Archived
2
0
Fork 0

update: remove plenary stuff; try and fix async problem

This commit is contained in:
Lucas Burns 2023-05-09 20:36:50 -05:00
parent b88c1efa65
commit ffcacf5dbb
No known key found for this signature in database
GPG Key ID: C011CBEF6628B679
6 changed files with 400 additions and 456 deletions

View File

@ -8,26 +8,22 @@ It is very similar to [`lf.vim`](https://github.com/ptzz/lf.vim), except for tha
### Installation
```lua
-- Sample configuration is supplied
use(
{
"lmburns/lf.nvim",
config = function()
-- This feature will not work if the plugin is lazy-loaded
vim.g.lf_netrw = 1
use({
"lmburns/lf.nvim",
config = function()
-- This feature will not work if the plugin is lazy-loaded
vim.g.lf_netrw = 1
require("lf").setup(
{
escape_quit = false,
border = "rounded",
highlights = {FloatBorder = {guifg = require("kimbox.palette").colors.magenta}}
}
)
require("lf").setup({
escape_quit = false,
border = "rounded",
-- highlights = {FloatBorder = {guifg = require("kimbox.palette").colors.magenta}}
})
vim.keymap.set("n", "<C-o>", ":Lf<CR>")
end,
requires = {"plenary.nvim", "toggleterm.nvim"}
}
)
vim.keymap.set("n", "<C-o>", ":Lf<CR>")
end,
requires = {"plenary.nvim", "toggleterm.nvim"}
})
```
### Setup/Configuration
@ -45,7 +41,7 @@ require("lf").setup({
},
winblend = 10, -- psuedotransparency level
dir = "", -- directory where `lf` starts ('gwd' is git-working-directory, "" is CWD)
dir = "", -- directory where `lf` starts ('gwd' is git-working-directory, ""/nil is CWD)
direction = "float", -- window type: float horizontal vertical
border = "double", -- border kind: single double shadow curved
height = 0.80, -- height of the *floating* window
@ -77,7 +73,7 @@ require("lf").setup({
}
})
vim.api.nvim_set_keymap("n", "<mapping>", "<cmd>lua require('lf').start()<CR>", { noremap = true })
vim.keymap.set("n", "<mapping>", "<cmd>lua require('lf').start()<CR>", {noremap = true})
```
Another option is to use `vim.keymap.set`, which requires `nvim` 0.7.0 or higher. This doesn't require local
@ -101,7 +97,7 @@ vim.keymap.set(
mappings = true, -- whether terminal buffer mapping is enabled
})
end,
{ noremap = true }
{noremap = true}
)
```
@ -114,8 +110,8 @@ options (`table`). If there is only one argument and it is a `table`, this will
options and `lf` will open in the current directory. The following are all valid:
```lua
require('lf').start({ border = "rounded" }) -- opens in CWD with rounded borders
require('lf').start(nil, { border = "rounded" }) -- opens in CWD with rounded borders
require('lf').start({border = "rounded"}) -- opens in CWD with rounded borders
require('lf').start(nil, {border = "rounded"}) -- opens in CWD with rounded borders
require('lf').start("~/.config") -- opens in `~/.config` with either `.setup()` or default options
require('lf').start("~/.config", nil) -- opens in `~/.config` with either `.setup()` or default options
@ -123,14 +119,14 @@ require('lf').start("~/.config", nil) -- opens in `~/.config` with either `.setu
require('lf').start(nil, nil) -- opens in CWD with either `.setup()` or default options
require('lf').start() -- opens in CWD with either `.setup()` or default options
require('lf').start("~/.config", { border = "rounded" }) -- opens in `~/.config` with rounded borders
require('lf').start("~/.config", {border = "rounded"}) -- opens in `~/.config` with rounded borders
```
### Highlight Groups
The highlight groups that I know for sure work are the ones mentioned above (`Normal`, `NormalFloat`, `FloatBorder`). These are passed to `toggleterm`, and there is a plan in the future to make these `Lf`'s own groups. For now, a one-shot way to change the color of the border of the terminal is the following:
```vim
:lua require("lf").start({ highlights = { FloatBorder = { guifg = "#819C3B" } } })
:lua require("lf").start({highlights = {FloatBorder = {guifg = "#819C3B"}}})
```
### Default Actions
@ -147,7 +143,7 @@ If you do not have the nightly version of `nvim`, then the `mappings` field can
Otherwise, a notification will be displayed saying that you are not allowed to use them.
```lua
require("lf").start({ mappings = false })
require("lf").start({mappings = false})
```
### Replacing Netrw

View File

@ -20,22 +20,22 @@ function M.setup(cfg)
end
has_feature(cfg)
M._cfg = cfg or {}
M.__conf = cfg or {}
loaded = true
end
---Start the file manager
---`nil` can be used as the first parameter to change options and open in CWD
---
---@param path string optional path to start in
---@param cfg LfConfig
function M.start(path, cfg)
local path_t = type(path)
local Lf = require("lf.main")
-- Only one argument was given
-- `path` is given as a table, which is treated as `cfg`
if path ~= nil and cfg == nil and path_t == "table" then
require("lf.main").Lf:new(path or M._cfg):start(nil)
Lf:new(path or M.__conf):start(nil)
else
-- Strict nil checks are needed because `nil` can be given as an argument
if path ~= nil and path_t ~= "string" then
@ -47,7 +47,7 @@ function M.start(path, cfg)
return
end
require("lf.main").Lf:new(cfg or M._cfg):start(path)
Lf:new(cfg or M.__conf):start(path)
end
end

View File

@ -1,152 +1,139 @@
---@alias LfBorder 'none'|'single'|'double'|'rounded'|'solid'|'shadow'|string[8]
local fn = vim.fn
local o = vim.o
---@alias LfGenericBorder {[1]:string,[2]:string,[3]:string,[4]:string,[5]:string,[6]:string,[7]:string,[8]:string}
---@alias LfBorder "'none'"|"'single'"|"'double'"|"'rounded'"|"'solid'"|"'shadow'"|LfGenericBorder
---@class LfViews
---@field relative 'editor'|'win'|'curosr'|'mouse'
---@field win number: For `relative='win'`
---@field anchor 'NW'|'NE'|'SW'|'SE': Which corner of float to place `(row, col)`
---@field relative "'editor'"|"'win'"|"'cursor'"|"'mouse'"
---@field win number For `relative='win'`
---@field anchor "'NW'"|"'NE'"|"'SW'"|"'SE'" Which corner of float to place `(row, col)`
---@field width number
---@field height number
---@field bufpos { row: number, col: number }
---@field bufpos {row: number, col: number}
---@field row number|float
---@field col number|float
---@field focusable boolean
---@field zindex number
---@field style 'minimal'
---@field border LfBorder: Border kind
---@field title string|string[2][]: Can be a string or an array of tuples
---@field title_pos 'left'|'center'|'right'
---@field style "'minimal'"
---@field border LfBorder Border kind
---@field title string|{[1]: string, [2]: string}[] Can be a string or an array of tuples
---@field title_pos "'left'"|"'center'"|"'right'"
---@field noautocmd boolean
---@class LfConfig
---@field default_cmd string: Default `lf` command
---@field default_action string: Default action when `Lf` opens a file
---@field default_actions { [string]: string }: Default action keybindings
---@field winblend number: Psuedotransparency level
---@field dir 'gwd'|''|nil|string: Directory where `lf` starts ('gwd' is git-working-directory, ""/nil is CWD)
---@field direction 'vertical'|'horizontal'|'tab'|'float': Window type
---@field border LfBorder: Border kind
---@field height number: Height of the *floating* window
---@field width number: Width of the *floating* window
---@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 mappings boolean: Whether terminal buffer mappings should be set
---@field tmux boolean: Whether `tmux` statusline should be changed by this plugin
---@field highlights table<string, table<string, string>>: Highlight table passed to `toggleterm`
---@field layout_mapping string: Keybinding to rotate through the window layouts
---@field views LfViews[]: Table of layouts to be applied to `nvim_win_set_config`
---@field default_cmd string Default `lf` command
---@field default_action string Default action when `Lf` opens a file
---@field default_actions { [string]: string } Default action keybindings
---@field winblend number Psuedotransparency level
---@field dir "'gwd'"|"''"|nil|string Directory where `lf` starts ('gwd' is git-working-directory, ""/nil is CWD)
---@field direction "'vertical'"|"'horizontal'"|"'tab'"|"'float'" Window type
---@field border LfBorder Border kind
---@field height number Height of the *floating* window
---@field width number Width of the *floating* window
---@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 mappings boolean Whether terminal buffer mappings should be set
---@field tmux boolean Whether `tmux` statusline should be changed by this plugin
---@field highlights table<string, table<string, string>> Highlight table passed to `toggleterm`
---@field layout_mapping string Keybinding to rotate through the window layouts
---@field views LfViews[] Table of layouts to be applied to `nvim_win_set_config`
local Config = {}
local fn = vim.fn
local o = vim.o
---@type LfConfig
local opts = {
default_cmd = "lf",
default_action = "drop",
default_actions = {
["<C-t>"] = "tabedit",
["<C-x>"] = "split",
["<C-v>"] = "vsplit",
["<C-o>"] = "tab drop",
},
winblend = 10,
dir = "",
direction = "float",
border = "double",
height = 0.80,
width = 0.85,
escape_quit = false,
focus_on_open = true,
mappings = true,
tmux = false,
highlights = {
Normal = {link = "Normal"},
FloatBorder = {link = "FloatBorder"},
},
-- Layout configurations
layout_mapping = "<A-u>",
views = {
{width = 0.600, height = 0.600},
{
width = 1.0 * fn.float2nr(fn.round(0.7 * o.columns)) / o.columns,
height = 1.0 * fn.float2nr(fn.round(0.7 * o.lines)) / o.lines,
},
{width = 0.800, height = 0.800},
{width = 0.950, height = 0.950},
},
}
-- A local function that runs each time allows for a global `.setup()` to work
---Validate configuration values
---@param cfg LfConfig existing configuration options
---@return LfConfig
local function validate(cfg)
vim.validate({
default_cmd = {cfg.default_cmd, "s", false},
default_action = {cfg.default_action, "s", false},
default_actions = {cfg.default_actions, "t", false},
winblend = {cfg.winblend, {"n", "s"}, false},
dir = {cfg.dir, "s", false},
direction = {cfg.direction, "s", false},
border = {cfg.border, {"s", "t"}, false},
height = {cfg.height, {"n", "s"}, false},
width = {cfg.width, {"n", "s"}, false},
escape_quit = {cfg.escape_quit, "b", false},
focus_on_open = {cfg.focus_on_open, "b", false},
mappings = {cfg.mappings, "b", false},
tmux = {cfg.tmux, "b", false},
highlights = {cfg.highlights, "t", false},
-- Layout configurations
layout_mapping = {cfg.layout_mapping, "s", false},
views = {cfg.views, "t", false},
})
cfg.winblend = tonumber(cfg.winblend) --[[@as number]]
cfg.height = tonumber(cfg.height) --[[@as number]]
cfg.width = tonumber(cfg.width) --[[@as number]]
return cfg
end
---@private
---Initialize the default configuration
local function init()
local lf = require("lf")
vim.validate({Config = {lf._cfg, "table", true}})
---@type LfConfig
local opts = {
default_cmd = "lf",
default_action = "edit",
default_actions = {
["<C-t>"] = "tabedit",
["<C-x>"] = "split",
["<C-v>"] = "vsplit",
["<C-o>"] = "tab drop"
},
winblend = 10,
dir = "",
direction = "float",
border = "double",
height = 0.80,
width = 0.85,
escape_quit = false,
focus_on_open = true,
mappings = true,
tmux = false,
highlights = {
Normal = {link = "Normal"},
FloatBorder = {}
},
-- Layout configurations
layout_mapping = "<A-u>",
views = {
{width = 0.600, height = 0.600},
{
width = 1.0 * fn.float2nr(fn.round(0.7 * o.columns)) / o.columns,
height = 1.0 * fn.float2nr(fn.round(0.7 * o.lines)) / o.lines
},
{width = 0.800, height = 0.800},
{width = 0.950, height = 0.950}
}
}
-- Keep options from the `lf.setup()` call
Config = vim.tbl_deep_extend("keep", lf._cfg or {}, opts) --[[@as LfConfig]]
lf._cfg = nil
Config = vim.tbl_deep_extend("keep", lf.__conf or {}, opts) --[[@as LfConfig]]
Config = validate(Config)
lf.__conf = nil
end
init()
-- local notify = require("lf.utils").notify
---Set a configuration passed as a function argument (not through `setup`)
---@param cfg? LfConfig configuration options
---@return LfConfig
function Config:set(cfg)
if cfg and type(cfg) == "table" then
function Config:override(cfg)
if type(cfg) == "table" then
self = vim.tbl_deep_extend("keep", cfg or {}, self) --[[@as LfConfig]]
vim.validate(
{
default_cmd = {self.default_cmd, "s", false},
default_action = {self.default_action, "s", false},
default_actions = {self.default_actions, "t", false},
winblend = {self.winblend, {"n", "s"}, false},
dir = {self.dir, "s", false},
direction = {self.direction, "s", false},
border = {self.border, "s", false},
height = {self.height, {"n", "s"}, false},
width = {self.width, {"n", "s"}, false},
escape_quit = {self.escape_quit, "b", false},
focus_on_open = {self.focus_on_open, "b", false},
mappings = {self.mappings, "b", false},
tmux = {self.tmux, "b", false},
highlights = {self.highlights, "t", false},
-- Layout configurations
layout_mapping = {self.layout_mapping, "s", false},
views = {self.views, "t", false}
}
)
-- Just run `tonumber` on all items that can be strings
-- Checking if each one is a string might take longer
self.winblend = tonumber(self.winblend) --[[@as number]]
self.height = tonumber(self.height) --[[@as number]]
self.width = tonumber(self.width) --[[@as number]]
end
return self
end
---Get the entire configuration if empty, else get the given key
---@param key? string option to get
---@return LfConfig|any
function Config:get(key)
if key then
return self[key]
self = validate(self)
end
return self
end
return setmetatable(
Config, {
__index = function(this, k)
return this[k]
end,
__newindex = function(this, k, v)
this[k] = v
end
}
)
return setmetatable(Config, {
__index = function(self, key)
return rawget(self, key)
end,
__newindex = function(_self, _key, _val)
end,
})

View File

@ -1,21 +1,11 @@
local M = {}
---@diagnostic disable: redefined-local
local utils = require("lf.utils")
local res, terminal = pcall(require, "toggleterm")
if not res then
local ok, terminal = pcall(require, "toggleterm")
if not ok then
utils.err("toggleterm.nvim must be installed to use this program")
return
end
local res, Path = pcall(require, "plenary.path")
if not res then
utils.err("plenary must be installed to use this program")
return
end
local cmd = vim.cmd
local api = vim.api
local fn = vim.fn
@ -24,14 +14,9 @@ local o = vim.o
local fs = utils.fs
local map = utils.map
---Error for this program
M.error = nil
local Config = require("lf.config")
local a = require("plenary.async")
local autil = require("plenary.async.util")
local Job = require("plenary.job")
-- local promise = require("promise")
-- local async = require("async")
---@class Terminal
local Terminal = require("toggleterm.terminal").Terminal
@ -41,10 +26,10 @@ local Terminal = require("toggleterm.terminal").Terminal
---@field term Terminal toggleterm terminal
---@field view_idx number Current index of configuration `views`
---@field winid? number `Terminal` window id
---@field curr_file? string File path of the currently opened file
---@field id_tf? string Path to a file containing `lf`'s id
---@field selection_tf string Path to a file containing `lf`'s selection(s)
---@field lastdir_tf string Path to a file containing the last directory `lf` was in
---@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 action string The current action to open the file
@ -54,25 +39,23 @@ local 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
}
)
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`
@ -85,21 +68,13 @@ function Lf:new(config)
self.__index = self
if config then
self.cfg = Config:set(config):get()
self.cfg = Config:override(config)
else
self.cfg = Config
end
self.id = nil
self.bufnr = 0
self.winid = nil
self.view_idx = 1
self.curr_file = nil
self.id_tf = nil
self.selection_tf = nil
self.lastdir_tf = nil
self.action = self.cfg.default_action
-- Needs to be grabbed here before the terminal buffer is created
self.signcolumn = o.signcolumn
@ -113,45 +88,38 @@ end
---@private
---Create the `Terminal` and set it o `Lf.term`
function Lf:__create_term()
self.term = Terminal:new(
{
cmd = self.cfg.default_cmd,
dir = self.cfg.dir,
direction = self.cfg.direction,
self.term = Terminal:new({
cmd = self.cfg.default_cmd,
dir = self.cfg.dir,
direction = self.cfg.direction,
winblend = self.cfg.winblend,
close_on_exit = true,
hidden = false,
highlights = self.cfg.highlights,
float_opts = {
border = self.cfg.border,
width = math.floor(o.columns * self.cfg.width),
height = math.floor(o.lines * self.cfg.height),
winblend = self.cfg.winblend,
close_on_exit = true,
hidden = false,
highlights = self.cfg.highlights,
float_opts = {
border = self.cfg.border,
width = math.floor(o.columns * self.cfg.width),
height = math.floor(o.lines * self.cfg.height),
winblend = self.cfg.winblend
}
}
)
},
})
end
---Start the underlying terminal
---@param path? string path where Lf starts (reads from Config if none, else CWD)
function Lf:start(path)
self:__open_in(path or self.cfg.dir)
if M.error ~= nil then
utils.err(M.error)
return
end
self:__set_cmd_wrapper()
self.term.on_open = function(term)
a.run(
function()
self:__on_open(term)
end
)
self:__on_open(term)
end
self.term.on_exit = function(term, _, _, _)
self:__callback(term)
uv.fs_unlink(self.tmp_id)
uv.fs_unlink(self.tmp_lastdir)
uv.fs_unlink(self.tmp_sel)
end
self.term:open()
@ -163,29 +131,26 @@ end
---@param path? string
---@return Lf?
function Lf:__open_in(path)
local built = Path:new(
(function(dir)
if dir == "gwd" or dir == "git_dir" then
dir = utils.git_dir()
end
if path == "gwd" or path == "git_dir" then
path = utils.git_dir()
end
path = fn.expand(utils.tern(path == "" or path == nil, "%:p:h", path))
-- Base the CWD on the filename and not `lcd` and such
return fn.expand(utils.tern(dir == "" or dir == nil, "%:p:h", dir))
end)(path)
)
if not built:exists() then
utils.warn("Current directory doesn't exist")
return
local built = path
local stat = uv.fs_stat(path)
if not type(stat) == "table" then
local cwd = uv.cwd()
stat = uv.fs_stat(cwd)
built = cwd
end
-- Should be fine, but just checking
if not built:is_dir() then
built = built:parent()
if stat and not stat.type == "directory" then
built = fs.dirname(built)
end
self.term.dir = built:absolute()
self.curr_file = fn.expand("%:p")
self.term.dir = built
self.curfile = fn.expand("%:p")
return self
end
@ -195,14 +160,14 @@ end
---
---@return Lf
function Lf:__set_cmd_wrapper()
self.selection_tf = os.tmpname()
self.lastdir_tf = os.tmpname()
self.id_tf = os.tmpname()
self.tmp_sel = os.tmpname()
self.tmp_lastdir = os.tmpname()
self.tmp_id = os.tmpname()
-- command lf -command '$printf $id > '"$fid"'' -last-dir-path="$tmp" "$@"
self.term.cmd =
([[%s -command='$printf $id > %s' -last-dir-path='%s' -selection-path='%s' %s]]):format(
self.term.cmd, self.id_tf, self.lastdir_tf, self.selection_tf,
self.term.cmd, self.tmp_id, self.tmp_lastdir, self.tmp_sel,
self.term.dir
)
return self
@ -218,126 +183,87 @@ function Lf:__on_open(term)
-- TODO: The delay here needs to be fixed.
-- I need to find a way to block this outer function's caller
vim.defer_fn(
function()
if self.cfg.focus_on_open then
if self.curr_file == nil then
utils.warn(
"Function has not been deferred long enough, preventing `focus_on_open` from working.\n"
.. "Please report an issue on Github (lmburns/lf.nvim)"
)
vim.defer_fn(function()
if self.cfg.focus_on_open then
if term.dir == fs.dirname(self.curfile) then
if not fn.filereadable(self.tmp_id) then
utils.err(("Lf's id file is not readable: %s"):format(self.tmp_id))
return
end
if term.dir == fs.dirname(self.curr_file) then
if not fn.filereadable(self.id_tf) then
utils.err(
("Lf's id file is not readable: %s"):format(
self.id_tf
)
)
return
end
local res = utils.read_file(self.tmp_id)
self.id = tonumber(res)
local res = utils.read_file(self.id_tf)
self.id = tonumber(res)
if self.id == nil then
utils.warn(
"Lf's ID was not set\n"
.. "Please report an issue on Github (lmburns/lf.nvim)"
)
return
end
Job:new(
{
command = "lf",
args = {
"-remote",
("send %s select %s"):format(
self.id, fs.basename(self.curr_file)
)
},
interactive = false,
detached = true,
enabled_recording = false
}
):sync()
if self.id ~= nil then
fn.system({
"lf",
"-remote",
("send %s select %s"):format(self.id, fs.basename(self.curfile)),
})
end
end
end, 75
)
end
end, 70)
cmd("silent doautocmd User LfTermEnter")
cmd("silent! doautocmd User LfTermEnter")
-- Wrap needs to be set, otherwise the window isn't aligned on resize
api.nvim_buf_call(
self.bufnr, function()
vim.wo[self.winid].showbreak = "NONE"
vim.wo[self.winid].wrap = true
end
)
api.nvim_buf_call(self.bufnr, function()
vim.wo[self.winid].showbreak = "NONE"
vim.wo[self.winid].wrap = true
vim.wo[self.winid].sidescrolloff = 0
end)
if self.cfg.tmux then
utils.tmux(true)
end
-- Not sure if this works
autil.scheduler(
function()
if self.cfg.mappings then
if self.cfg.escape_quit then
map(
"t", "<Esc>", "<Cmd>q<CR>",
{
buffer = self.bufnr,
desc = "Exit Lf"
}
)
end
for key, mapping in pairs(self.cfg.default_actions) do
map(
"t", key, function()
-- Change default_action for easier reading in the callback
self.action = mapping
-- Manually tell `lf` to open the current file
-- since Neovim has hijacked the binding
Job:new(
{
command = "lf",
args = {
"-remote",
("send %d open"):format(self.id)
}
}
):sync()
end, {
noremap = true,
buffer = self.bufnr,
desc = ("Lf %s"):format(mapping)
}
)
end
if self.cfg.layout_mapping then
map(
"t", self.cfg.layout_mapping, function()
api.nvim_win_set_config(
self.winid, utils.get_view(
self.cfg.views[self.view_idx], self.bufnr,
self.signcolumn
)
)
self.view_idx = self.view_idx < #self.cfg.views
and self.view_idx + 1 or 1
end
)
end
end
if self.cfg.mappings then
if self.cfg.escape_quit then
map(
"t",
"<Esc>",
"<Cmd>q<CR>",
{buffer = self.bufnr, desc = "Exit Lf"}
)
end
)
for key, mapping in pairs(self.cfg.default_actions) do
map(
"t",
key,
function()
-- Change default_action for easier reading in the callback
self.action = mapping
-- Manually tell `lf` to open the current file
-- since Neovim has hijacked the binding
fn.system({"lf", "-remote", ("send %d open"):format(self.id)})
end,
{noremap = true, buffer = self.bufnr, desc = ("Lf %s"):format(mapping)}
)
end
if self.cfg.layout_mapping then
map(
"t",
self.cfg.layout_mapping,
function()
api.nvim_win_set_config(
self.winid,
utils.get_view(
self.cfg.views[self.view_idx],
self.bufnr,
self.signcolumn
))
self.view_idx = self.view_idx < #self.cfg.views
and self.view_idx + 1
or 1
end
)
end
end
end
---@private
@ -349,38 +275,36 @@ function Lf:__callback(term)
utils.tmux(false)
end
if (self.action == "cd" or self.action == "lcd")
and uv.fs_stat(self.lastdir_tf) then
local last_dir = utils.read_file(self.lastdir_tf)
if
(self.action == "cd" or self.action == "lcd")
and uv.fs_stat(self.tmp_lastdir)
then
local last_dir = utils.read_file(self.tmp_lastdir)
if last_dir ~= nil and last_dir ~= uv.cwd() then
cmd(("%s %s"):format(self.action, last_dir))
return
end
elseif uv.fs_stat(self.selection_tf) then
elseif uv.fs_stat(self.tmp_sel) then
local contents = {}
for line in io.lines(self.selection_tf) do
for line in io.lines(self.tmp_sel) do
table.insert(contents, line)
end
if not vim.tbl_isempty(contents) then
term:close()
for _, fname in pairs(contents) do
cmd(("%s %s"):format(self.action, Path:new(fname):absolute()))
local stat = uv.fs_stat(fname)
if type(stat) == "table" then
cmd(("%s %s"):format(self.action, fname))
end
end
end
end
-- Reset the action
vim.defer_fn(
function()
self.action = self.cfg.default_action
end, 1
)
vim.defer_fn(function()
self.action = self.cfg.default_action
end, 1)
end
M.Lf = Lf
return M
return Lf

View File

@ -1,5 +1,6 @@
local M = {}
local uv = vim.loop
local fn = vim.fn
local api = vim.api
local levels = vim.log.levels
@ -18,9 +19,7 @@ end
---@param level number
---@param opts table?
function M.notify(msg, level, opts)
opts = vim.tbl_extend(
"force", opts or {}, {title = "lf.nvim"}
)
opts = vim.tbl_extend("force", opts or {}, {title = "lf.nvim"})
vim.notify(msg, level, opts)
end
@ -60,62 +59,104 @@ function M.err(msg, print, opts)
end
end
---Return a value based on two values
---@generic T, V
---@param condition? boolean Statement to be tested
---@param is_if T Return if condition is truthy
---@param is_else V Return if condition is not truthy
---@return T | V
function M.tern(condition, is_if, is_else)
if condition then
return is_if
---Create a shallow copy of a portion of a vector.
---Can index with negative numbers.
---@generic T
---@param vec T[] Vector to select from
---@param first? integer First index, inclusive
---@param last? integer Last index, inclusive
---@return T[] #sliced vector
function M.list_slice(vec, first, last)
local slice = {}
if first and first < 0 then
first = #vec + first + 1
end
return is_else
if last and last < 0 then
last = #vec + last + 1
end
for i = first or 1, last or #vec do
table.insert(slice, vec[i])
end
return slice
end
---Similar to `vim.F.nil` except that an alternate default value can be given
---Return all elements in `t` between `first` and `last` index.
---Can index with negative numbers.
---@generic T
---@param vec T[] Vector to select from
---@param first? integer First index, inclusive
---@param last? integer Last index, inclusive
---@return T ...
function M.list_select(vec, first, last)
return unpack(M.list_slice(vec, first, last))
end
---Similar to C's ternary operator
---@generic T, V
---@param x any: Value to check if `nil`
---@param is_nil `T`: Value to return if `x` is `nil`
---@param is_not_nil `V`: Value to return if `x` is not `nil`
---@return `T` | `V`
function M.ife_nil(x, is_nil, is_not_nil)
return M.tern(x == nil, is_nil, is_not_nil)
end
---Return a default value if `x` is nil
---@generic T, V
---@param x `T`: Value to check if not `nil`
---@param default `V`: Default value to return if `x` is `nil`
---@return `T` | `V`
function M.get_default(x, default)
return M.ife_nil(x, default, x)
end
---Read a file
---@param fname string
---@return string?
function M.read_file(fname)
local fd = assert(io.open(fname, "r"))
local c = coroutine.create(
function()
coroutine.yield(fd)
fd:close()
---@param cond? boolean|fun():boolean Statement to be tested
---@param is_if T Return if cond is truthy
---@param is_else V Return if cond is not truthy
---@param simple? boolean Never treat `is_if` and `is_else` as arg lists
---@return unknown
function M.tern(cond, is_if, is_else, simple)
if cond then
if not simple and type(is_if) == "table" and vim.is_callable(is_if[1]) then
return is_if[1](M.list_select(is_if, 2))
end
)
return is_if
else
if not simple and type(is_else) == "table" and vim.is_callable(is_else[1]) then
return is_else[1](M.list_select(is_else, 2))
end
return is_else
end
end
local ok, context = coroutine.resume(c)
assert(ok, "coroutine didn't yield")
---## if else nil
---Similar to `vim.F.nil` except that:
--- - a default value can be given
--- - `if cond == nil then want else default`
---@generic T, V
---@param cond any Value to check if `nil`
---@param is_nil T Value to return if `cond` is `nil`
---@param is_not_nil V Value to return if `cond` is not `nil`
---@return T | V
function M.ife_nil(cond, is_nil, is_not_nil)
return M.tern(cond == nil, is_nil, is_not_nil)
end
local _, res = pcall(
function()
return fd:read("*a")
end, context
)
---## if nil then
---Return a default value if `val` is nil
--- - `if val == nil then default else val`
--- - `ifn_then`
---@generic T, V
---@param val T value to check if `nil`
---@param default V default value to return if `val` is `nil`
---@return T | V
function M.unwrap_or(val, default)
if type(val) ~= "table" then
return M.ife_nil(val, default, val)
end
val = vim.deepcopy(val or {})
for k, v in pairs(default) do
if val[k] == nil then
val[k] = v
end
end
return val
end
local done, _ = coroutine.resume(c)
assert(done, "coroutine didn't complete")
return res
---@param path string
---@return uv_fs_t|string
---@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
@ -173,7 +214,7 @@ function M.get_view(opts, bufnr, signcolumn)
)
)
local height = opts.height
or math.ceil(
or math.ceil(
math.min(M.height(), math.max(20, M.height() - 10))
)
@ -188,7 +229,7 @@ function M.get_view(opts, bufnr, signcolumn)
relative = "editor",
style = "minimal",
width = width,
height = height
height = height,
}
end

View File

@ -4,8 +4,10 @@ if _G.loaded_lf == 1 then
return
end
local uv = vim.loop
local api = vim.api
local fn = vim.fn
local cmd = vim.cmd
_G.loaded_lf = 1
@ -20,42 +22,36 @@ api.nvim_create_user_command(
if vim.g.lf_netrw == 1 or vim.g.lf_netrw then
local group = api.nvim_create_augroup("ReplaceNetrwWithLf", {clear = true})
api.nvim_create_autocmd(
"VimEnter",
{
pattern = "*",
group = group,
once = true,
callback = function()
if fn.exists("#FileExplorer") then
vim.cmd("silent! autocmd! FileExplorer")
api.nvim_create_autocmd("VimEnter", {
pattern = "*",
group = group,
once = true,
callback = function()
if fn.exists("#FileExplorer") then
cmd("sil! au! FileExplorer")
end
end,
})
api.nvim_create_autocmd("BufEnter", {
pattern = "*",
group = group,
once = true,
callback = function()
local bufnr = api.nvim_get_current_buf()
local fname = fn.expand("%:p")
local stat = uv.fs_stat(fname)
if type(stat) == "table" then
if stat.type == "directory" and fn.argc() ~= 0 then
cmd(("sil! bwipeout! %s"):format(bufnr))
vim.defer_fn(function()
require("lf").start(fname)
end, 1)
end
end
}
)
api.nvim_create_autocmd(
"BufEnter",
{
pattern = "*",
group = group,
once = true,
callback = function()
local bufnr = api.nvim_get_current_buf()
local path = require("plenary.path"):new(fn.expand("%"))
if path:is_dir() and fn.argc() ~= 0 then
vim.cmd(("sil! bwipeout! %s"):format(bufnr))
vim.defer_fn(
function()
require("lf").start(path:absolute())
end,
1
)
end
end
}
)
end,
})
end
return M