snailed
/
taolf
Archived
2
0
Fork 0

initial commit

This commit is contained in:
Lucas Burns 2022-04-10 00:24:21 -05:00
commit 313c9508b4
No known key found for this signature in database
GPG Key ID: C011CBEF6628B679
6 changed files with 445 additions and 0 deletions

62
README.md Normal file
View File

@ -0,0 +1,62 @@
## Lf.nvim
This is a neovim plugin for the [`lf`](https://github.com/gokcehan/lf) file manager.
It is very similar to [`lf.vim`](https://github.com/ptzz/lf.vim), except for that this is written in Lua.
**NOTE**: This plugin uses [`toggleterm.nvim`](https://github.com/akinsho/toggleterm.nvim) and [`plenary.nvim`](https://github.com/nvim-lua/plenary.nvim)
### Setup/Configuration
```lua
local M = {}
-- Defaults
local lf = require("lf").setup({
default_cmd = "lf", -- default `lf` command
default_action = "edit", -- default action when `Lf` opens a file
default_actions = { -- default action keybindings
["<C-t>"] = "tabedit",
["<C-x>"] = "split",
["<C-v>"] = "vsplit",
["<C-o>"] = "tab drop",
},
winblend = 10, -- psuedotransparency level
dir = "", -- directory where `lf` starts ('gwd' is git-working-directory)
direction = "float", -- window type: float horizontal vertical
border = "double", -- border kind: single double shadow curved
height = 0.80, -- height of the *floating* window
width = 0.85, -- width of the *floating* window
})
function M.start_lf()
lf:start()
end
vim.api.nvim_set_keymap("n", "<mapping>", "<cmd>lua require('file').start_lf()", { noremap = true })
-- or
vim.api.nvim_set_keymap("n", "<mapping>", "<cmd>lua require('lf').setup():start()", { noremap = true })
return M
```
There is a command that does basically the exact same thing `:Lf`. This command takes one optional argument,
which is a directory for `lf` to start in.
### Default Actions
The goal is to allow for these keybindings to be hijacked by `Lf` and make them execute the command
as soon as the keybinding is pressed; however, I am unsure of a way to do this at the moment. If `lf` had a more
programmable API that was similar to `ranger`'s, then something like [`rnvimr`](https://github.com/kevinhwang91/rnvimr)
would be possible, which allows this.
For the moment, these bindings are hijacked on the startup of `Lf`, and when they are pressed, a notification is sent
that your default action has changed. When you go to open a file as you normally would, this command is ran instead
of your `default_action`.
### Replacing Netrw
The only configurable environment variable is `g:lf_replace_netrw`, which can be set to `1` to replace `netrw`
### TODO
- `:LfToggle` command
- Find a way for `lf` to hijack keybindings
- Allow keybindings to cycle through various sizes of the terminal (similar to `rnvimr`)

240
lua/lf/action.lua Normal file
View File

@ -0,0 +1,240 @@
local M = {}
---@diagnostic disable: redefined-local
local utils = require("lf.utils")
local notify = utils.notify
local res, terminal = pcall(require, "toggleterm")
if not res then
notify("toggleterm.nvim must be installed to use this program", "error")
return
end
local res, Path = pcall(require, "plenary.path")
if not res then
notify("plenary must be installed to use this program", "error")
return
end
local api = vim.api
local fn = vim.fn
local uv = vim.loop
local g = vim.g
local map = utils.map
---Error for this program
ERROR = nil
---Global running status
---I'm unsure of a way to keep an `Lf` variable constant through more than 1 `setup` calls
g.__lf_running = false
local Config = require("lf.config")
--- @class Terminal
local Terminal = require("toggleterm.terminal").Terminal
--- @class Lf
--- @field cmd string
--- @field direction string the layout style for the terminal
--- @field id number
--- @field window number
--- @field job_id number
--- @field highlights table<string, table<string, string>>
local Lf = {}
local function setup_term()
terminal.setup(
{
size = function(term)
if term.direction == "horizontal" then
return vim.o.lines * 0.4
elseif term.direction == "vertical" then
return vim.o.columns * 0.5
end
end,
hide_numbers = true,
shade_filetypes = {},
shade_terminals = true,
shading_factor = "1",
start_in_insert = true,
insert_mappings = true,
persist_size = true,
-- open_mapping = [[<c-\>]],
}
)
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
---
---@param config 'table'
---@return Lf
function Lf:new(config)
local cfg = Config:set(config):get()
self.__index = self
self.cfg = cfg
self.cwd = uv.cwd()
setup_term()
self.term = Terminal:new(
{
cmd = cfg.default_cmd,
dir = cfg.dir,
direction = cfg.direction,
winblend = cfg.winblend,
close_on_exit = true,
float_opts = {
border = cfg.border,
width = math.floor(vim.o.columns * cfg.width),
height = math.floor(vim.o.lines * cfg.height),
winblend = cfg.winblend,
highlights = { border = "Normal", background = "Normal" },
},
-- on_open = cfg.on_open,
-- on_close = nil,
}
)
return self
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 ERROR ~= nil then
notify(ERROR, "error")
return
end
self:__wrapper()
self.term.on_open = function(term)
self:__on_open(term)
end
self.term.on_exit = function(term, _, _, _)
self:__callback(term)
end
-- NOTE: Maybe pcall here?
self.term:toggle()
g.__lf_running = true
end
function Lf:toggle(path)
print(g.__lf_running)
if g.__lf_running then
self.term:close()
g.__lf_running = false
else
self:start(path)
end
end
---@private
---Set the directory for `Lf` to open in
---
---@param path string
---@return Lf
function Lf:__open_in(path)
path = Path:new(
(function(dir)
if dir == "gwd" then
dir = require("lf.utils").git_dir()
end
if dir then
return fn.expand(dir)
else
return self.cwd
end
end)(path)
)
if not path:exists() then
ERROR = ("directory doesn't exist: %s"):format(path)
return
end
-- Should be fine, but just checking
if not path:is_dir() then
path = path:parent()
end
self.term.dir = path:absolute()
return self
end
---@private
---Wrap the default command value to write the selected files to a temporary file
---
---@return Lf
function Lf:__wrapper()
self.lf_tmp = os.tmpname()
self.lastdir_tmp = os.tmpname()
self.term.cmd = ([[%s -last-dir-path='%s' -selection-path='%s' %s]]):format(
self.term.cmd, self.lastdir_tmp, self.lf_tmp, self.term.dir
)
return self
end
-- TODO: Figure out a way to open the file with these commands
---On open closure to run in the `Terminal`
---@param term Terminal
function Lf:__on_open(term)
for key, mapping in pairs(self.cfg.default_actions) do
map(
"t", key, function()
self.cfg.default_action = mapping
notify(("Default action changed: %s"):format(mapping))
end, { noremap = true, buffer = term.bufnr }
)
end
end
---@private
---A callback for the `Terminal`
---
---@param term Terminal
function Lf:__callback(term)
if (self.cfg.default_action == "cd" or self.cfg.default_action == "lcd") and
uv.fs_stat(self.lastdir_tmp) then
local f = io.open(self.lastdir_tmp)
local last_dir = f:read()
f:close()
if last_dir ~= uv.cwd() then
api.nvim_exec(("%s %s"):format(self.cfg.default_action, last_dir), true)
return
end
elseif uv.fs_stat(self.lf_tmp) then
local contents = {}
for line in io.lines(self.lf_tmp) do
contents[#contents + 1] = line
end
if not vim.tbl_isempty(contents) then
term:close()
for _, fname in pairs(contents) do
api.nvim_exec(
("%s %s"):format(
self.cfg.default_action, Path:new(fname):absolute()
), true
)
end
end
end
end
M.Lf = Lf
return M

53
lua/lf/config.lua Normal file
View File

@ -0,0 +1,53 @@
--- @class Config
--- @field default_cmd string default `lf` command
--- @field default_action string default action when `Lf` opens a file
--- @field default_actions table default action keybindings
--- @field winblend number psuedotransparency level
--- @field dir string directory where `lf` starts ('gwd' is git-working-directory)
--- @field direction string window type: float horizontal vertical
--- @field border string border kind: single double shadow curved
--- @field height number height of the *floating* window
--- @field width number width of the *floating* window
local Config = {
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,
}
function Config:set(cfg)
if cfg and type(cfg) ~= "table" then
self = vim.tbl_deep_extend("force", self, cfg or {})
end
return self
end
function Config:get(key)
if key then
return self[key]
end
return self
end
return setmetatable(
Config, {
__index = function(this, k)
return this[k]
end,
__newindex = function(this, k, v)
this[k] = v
end,
}
)

7
lua/lf/init.lua Normal file
View File

@ -0,0 +1,7 @@
-- TODO: Cleanup set/new/start functions
return {
setup = function(config)
return require("lf.action").Lf:new(config)
end,
}

67
lua/lf/utils.lua Normal file
View File

@ -0,0 +1,67 @@
local M = {}
-- This was taken from toggleterm.nvim
local fn = vim.fn
local api = vim.api
local fmt = string.format
local levels = vim.log.levels
---Echo a message with `nvim_echo`
---@param msg string message
---@param hl string highlight group
M.echomsg = function(msg, hl)
hl = hl or "Title"
api.nvim_echo({ { msg, hl } }, true, {})
end
---Display an info message on the CLI
---@param msg string
M.info = function(msg)
M.echomsg(("[INFO]: %s"):format(msg), "Directory")
-- M.echomsg(("[INFO]: %s"):format(msg), "Identifier")
end
---Display a warning message on the CLI
---@param msg string
M.warn = function(msg)
M.echomsg(("[WARN]: %s"):format(msg), "WarningMsg")
end
---Display an error message on the CLI
---@param msg string
M.err = function(msg)
M.echomsg(("[ERR]: %s"):format(msg), "ErrorMsg")
end
---Display notification message
---@param msg string
---@param level 'error' | 'info' | 'warn'
M.notify = function(msg, level)
level = level and levels[level:upper()] or levels.INFO
vim.notify(fmt("[lf]: %s", msg), level)
end
---Helper function to derive the current git directory path
---@return string|nil
M.git_dir = function()
local gitdir = fn.system(
fmt(
"git -C %s rev-parse --show-toplevel", fn.expand("%:p:h")
)
)
local isgitdir = fn.matchstr(gitdir, "^fatal:.*") == ""
if not isgitdir then
return
end
return vim.trim(gitdir)
end
M.map = function(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
return M

16
plugin/lf.vim Normal file
View File

@ -0,0 +1,16 @@
command! -nargs=* -complete=file Lf lua require('lf').setup():start(<f-args>)
" TODO: Finish this command
" command! -nargs=* -complete=file LfToggle lua require('lf').setup():toggle(<f-args>)
" TODO: Make sure that this works
if exists('g:lf_replace_netrw') && g:lf_replace_netrw
augroup ReplaceNetrwWithLf
autocmd VimEnter * silent! autocmd! FileExplorer
autocmd BufEnter * let s:buf_path = expand("%")
\ | if isdirectory(s:buf_path)
\ | bdelete!
\ | call timer_start(100, {->v:lua.require'lf'.setup():start(s:buf_path)})
\ | endif
augroup END
endif