update: remove plenary stuff; try and fix async problem
This commit is contained in:
parent
b88c1efa65
commit
ffcacf5dbb
48
README.md
48
README.md
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
|
358
lua/lf/main.lua
358
lua/lf/main.lua
|
@ -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
|
||||
|
|
147
lua/lf/utils.lua
147
lua/lf/utils.lua
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Reference in New Issue