Initial commit
This commit is contained in:
commit
f2c0a5f8c6
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2020-2022 TJ DeVries, Michele Guerini Rocco
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
125
README.md
Normal file
125
README.md
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
# Frameline
|
||||||
|
|
||||||
|
### Very minimal framework to write a status/tabline
|
||||||
|
|
||||||
|
Frameline is a Lua library for writing your own statusline (and also a tabline)
|
||||||
|
for Neovim. This is not a ready-made statusline plugin, it's essential just the
|
||||||
|
*frame*, you have to built the rest by yourself.
|
||||||
|
|
||||||
|
Frameline is around 200 lines of Lua with no external dependencies.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
1. A statusline with the usual components: mode, git branch, filename,
|
||||||
|
encoding, etc.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local frameline = require 'frameline'
|
||||||
|
local utils = frameline.utils
|
||||||
|
|
||||||
|
-- Some components
|
||||||
|
function mode(win, buf)
|
||||||
|
if not win.is_active then return end
|
||||||
|
|
||||||
|
local k = vim.api.nvim_get_mode().mode
|
||||||
|
local modes = {
|
||||||
|
n={'Normal', 'Normal'},
|
||||||
|
i={'Insert', 'Insert'},
|
||||||
|
R={'Replas', 'Insert'},
|
||||||
|
v={'Visual', 'Visual'},
|
||||||
|
V={'V⋅Line', 'VLine'},
|
||||||
|
t={'Termin', 'Term'},
|
||||||
|
['']={'V⋅Bloc', 'VBloc'}
|
||||||
|
}
|
||||||
|
return utils.highlight('Mode'..modes[k][2], ' '..modes[k][1]..' ')
|
||||||
|
end
|
||||||
|
|
||||||
|
function branch(_, buf)
|
||||||
|
local head = vim.fn.FugitiveHead()
|
||||||
|
if buf.modifiable and head ~= "" then return '⚑ '..head end
|
||||||
|
end
|
||||||
|
|
||||||
|
function filename(_, buf)
|
||||||
|
local delta = ""
|
||||||
|
if buf.modified then delta = 'Δ' end
|
||||||
|
if not buf.modifiable then delta = '∇' end
|
||||||
|
local fname = buf.name ~= "" and utils.filename or 'new-file'
|
||||||
|
return delta..fname
|
||||||
|
end
|
||||||
|
|
||||||
|
function readonly(_, buf)
|
||||||
|
if buf.modifiable and buf.readonly then return '∅' end
|
||||||
|
end
|
||||||
|
|
||||||
|
function encoding(_, buf)
|
||||||
|
return buf.fileencoding ~= "" and buf.fileencoding or nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function filetype(_, buf)
|
||||||
|
return buf.filetype ~= "" and buf.filetype or 'no ft'
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Statusline
|
||||||
|
frameline.setup_statusline(function()
|
||||||
|
local segments = {}
|
||||||
|
|
||||||
|
-- Left section
|
||||||
|
table.insert(segments, utils.subsection{items={mode}})
|
||||||
|
table.insert(segments, utils.subsection{
|
||||||
|
separator=' → ',
|
||||||
|
items={branch, filename, readonly},
|
||||||
|
})
|
||||||
|
table.insert(segments, utils.split)
|
||||||
|
|
||||||
|
-- Right section
|
||||||
|
table.insert(segments, utils.subsection{
|
||||||
|
user=2,
|
||||||
|
separator=':', stop=' ',
|
||||||
|
items={utils.line_number, utils.column_number},
|
||||||
|
})
|
||||||
|
table.insert(segments, utils.subsection{
|
||||||
|
user=1,
|
||||||
|
separator=' ∘ ',
|
||||||
|
items={utils.percent, encoding, filetype}
|
||||||
|
})
|
||||||
|
|
||||||
|
return segments
|
||||||
|
end)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. A tabline that shows the current tab, open tabs and date.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- Tabline
|
||||||
|
frameline.setup_tabline(function()
|
||||||
|
local segments = {}
|
||||||
|
local api = vim.api
|
||||||
|
local color = '%#StatusLine#'
|
||||||
|
|
||||||
|
-- Tabs
|
||||||
|
local current = api.nvim_get_current_tabpage()
|
||||||
|
local label = ' %d %s '
|
||||||
|
for i, tab in pairs(api.nvim_list_tabpages()) do
|
||||||
|
-- tab -> active win -> active buf -> name
|
||||||
|
local active_buf = api.nvim_win_get_buf(api.nvim_tabpage_get_win(tab))
|
||||||
|
local name = api.nvim_buf_get_name(active_buf)
|
||||||
|
name = vim.fn.fnamemodify(name, ':t') -- filename only
|
||||||
|
|
||||||
|
local group = tab == current and 'TablineTabCur' or 'TablineTab'
|
||||||
|
table.insert(segments, utils.highlight(group, label:format(i, name)))
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(segments, color)
|
||||||
|
table.insert(segments, utils.split)
|
||||||
|
|
||||||
|
-- Current date
|
||||||
|
table.insert(segments, vim.fn.strftime("%a %H:%M"))
|
||||||
|
|
||||||
|
return table.concat(segments)
|
||||||
|
end)
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Frameline is licensed under the MIT license.
|
||||||
|
See the accompanying file LICENSE or https://mit-license.org/.
|
17
default.nix
Normal file
17
default.nix
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{ lib, runCommand }:
|
||||||
|
|
||||||
|
runCommand "nvim-frameline"
|
||||||
|
{ pname = "nvim-frameline";
|
||||||
|
version = "0.1.0";
|
||||||
|
|
||||||
|
meta = with lib; {
|
||||||
|
description = "Very minimal framework to write a status/tabline";
|
||||||
|
homepage = "https://maxwell.ydns.eu/git/rnhmjoj/nvim-frameline";
|
||||||
|
license = licenses.mit;
|
||||||
|
maintainers = [ maintainers.rnhmjoj ];
|
||||||
|
platforms = platforms.all;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
''
|
||||||
|
install -m644 -D ${./frameline.lua} "$out/lua/frameline/init.lua"
|
||||||
|
''
|
224
frameline.lua
Normal file
224
frameline.lua
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
-- Very minimal framework to write a status/tabline
|
||||||
|
-- Based on a (literally) stripped-down express_line.
|
||||||
|
-- License: MIT
|
||||||
|
-- Copyright © 2022 TJ DeVries, Michele Guerini Rocco
|
||||||
|
|
||||||
|
|
||||||
|
local fl = {}
|
||||||
|
|
||||||
|
-- Misc utilities
|
||||||
|
fl.utils = {
|
||||||
|
-- Built-in segments
|
||||||
|
line_number = '%l',
|
||||||
|
column_number = '%c',
|
||||||
|
percent = "%p%%",
|
||||||
|
filename = '%t',
|
||||||
|
split = '%=',
|
||||||
|
|
||||||
|
-- Highlights a string
|
||||||
|
highlight = function(group, content)
|
||||||
|
return ('%%#%s#'):format(group) .. content .. '%*'
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Creates a subsection {highlight, items, separator, stop}
|
||||||
|
subsection = function(cfg)
|
||||||
|
local segments = {}
|
||||||
|
|
||||||
|
if cfg.user ~= nil then
|
||||||
|
table.insert(segments, ('%%%s*'):format(cfg.user))
|
||||||
|
end
|
||||||
|
if cfg.highlight ~= nil then
|
||||||
|
table.insert(segments, ('%%#%s#'):format(cfg.highlight))
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(segments, function(win, buf)
|
||||||
|
local res, last_active = "", false
|
||||||
|
|
||||||
|
for i, item in pairs(cfg.items) do
|
||||||
|
local part = type(item) == 'string' and item or item(win, buf)
|
||||||
|
if part and last_active then
|
||||||
|
res = res .. (cfg.separator or ' ')
|
||||||
|
end
|
||||||
|
if part then
|
||||||
|
res = res .. part
|
||||||
|
last_active = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return res
|
||||||
|
end)
|
||||||
|
table.insert(segments, cfg.stop or ' ')
|
||||||
|
|
||||||
|
if highlight ~= nil or user ~= nil then
|
||||||
|
table.insert(segments, '%*')
|
||||||
|
end
|
||||||
|
|
||||||
|
return segments
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Neovim Buffer class
|
||||||
|
local Buffer = {}
|
||||||
|
local buf_props = {
|
||||||
|
name = function(buf) return vim.api.nvim_buf_get_name(buf.bufnr) end,
|
||||||
|
is_active = function(buf) return buf.bufnr == vim.api.nvim_get_current_buf() end,
|
||||||
|
}
|
||||||
|
function Buffer:new(bufnr)
|
||||||
|
if bufnr == 0 then
|
||||||
|
bufnr = vim.api.nvim_buf_get_number(0)
|
||||||
|
end
|
||||||
|
|
||||||
|
return setmetatable({ bufnr = bufnr, }, { __index=function(t, k)
|
||||||
|
if Buffer[k] ~= nil then
|
||||||
|
t[k] = Buffer[k]
|
||||||
|
elseif buf_props[k] ~= nil then
|
||||||
|
t[k] = buf_props[k](t)
|
||||||
|
else
|
||||||
|
t[k] = vim.api.nvim_buf_get_option(t.bufnr, k)
|
||||||
|
end
|
||||||
|
return t[k]
|
||||||
|
end
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Neovim Window class
|
||||||
|
local Window = {}
|
||||||
|
local win_props = {
|
||||||
|
width = function(win) return vim.api.nvim_win_get_width(win.winid) end,
|
||||||
|
height = function(win) return vim.api.nvim_win_get_height(win.winid) end,
|
||||||
|
is_active = function(win) return win.winid == vim.api.nvim_get_current_win() end
|
||||||
|
}
|
||||||
|
function Window:new(winid)
|
||||||
|
return setmetatable({winid=winid}, { __index=function(t, k)
|
||||||
|
local result
|
||||||
|
if Window[k] ~= nil then
|
||||||
|
result = Window[k]
|
||||||
|
elseif win_props[k] ~= nil then
|
||||||
|
result = win_props[k](t)
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Evaluates the statusline segments
|
||||||
|
local processor = function(items, window, buffer)
|
||||||
|
local winid = window.winid
|
||||||
|
|
||||||
|
return function()
|
||||||
|
if not vim.api.nvim_win_is_valid(winid) then return end
|
||||||
|
|
||||||
|
buffer = Buffer:new(buffer.bufnr)
|
||||||
|
local waiting = {}
|
||||||
|
local statusline = {}
|
||||||
|
local effects = {}
|
||||||
|
|
||||||
|
for k, v in ipairs(items) do
|
||||||
|
local ok, result, effect
|
||||||
|
if type(v) == 'string' then
|
||||||
|
ok, result = true, v
|
||||||
|
elseif type(v) == 'function' then
|
||||||
|
ok, result, effect = pcall(v, window, buffer)
|
||||||
|
else
|
||||||
|
ok = false
|
||||||
|
end
|
||||||
|
|
||||||
|
if not ok then
|
||||||
|
statusline[k] = ''
|
||||||
|
else
|
||||||
|
if type(result) == 'thread' then
|
||||||
|
table.insert(waiting, {index=k, thread=result, effect=effect})
|
||||||
|
else
|
||||||
|
statusline[k], effects[k] = result, effect
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local remaining = table.getn(waiting)
|
||||||
|
local completed = 0
|
||||||
|
|
||||||
|
local start = os.time()
|
||||||
|
while start + 2 > os.time() do
|
||||||
|
if remaining == completed then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, remaining do
|
||||||
|
local wait_val = waiting[i]
|
||||||
|
|
||||||
|
if wait_val ~= nil then
|
||||||
|
local index, thread = wait_val.index, wait_val.thread
|
||||||
|
local _, res = coroutine.resume(thread, window, buffer)
|
||||||
|
|
||||||
|
if coroutine.status(thread) == 'dead' then
|
||||||
|
statusline[index] = res
|
||||||
|
-- Remove
|
||||||
|
completed = completed + 1
|
||||||
|
waiting[i] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Filter nils and concat
|
||||||
|
local final = {}
|
||||||
|
for k, v in ipairs(statusline) do
|
||||||
|
if effects[k] then v = effects[k](v) end
|
||||||
|
if v then table.insert(final, v) end
|
||||||
|
end
|
||||||
|
|
||||||
|
return table.concat(final, "")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Creates a table of all window/buffers
|
||||||
|
local get_new_windows_table = function()
|
||||||
|
return setmetatable({}, {__index = function(self, winid)
|
||||||
|
local val = setmetatable({}, {
|
||||||
|
__index = function(win_table, bufnr)
|
||||||
|
|
||||||
|
if not fl.generator then return function() return '' end end
|
||||||
|
|
||||||
|
local window = Window:new(winid)
|
||||||
|
local buffer = Buffer:new(bufnr)
|
||||||
|
local items = vim.tbl_flatten(fl.generator(window, buffer))
|
||||||
|
local p = processor(items, window, buffer)
|
||||||
|
|
||||||
|
rawset(win_table, bufnr, p)
|
||||||
|
return p
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
rawset(self, winid, val)
|
||||||
|
return val
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Draws the status/tabline
|
||||||
|
function draw_statusline(winid)
|
||||||
|
local bufnr = vim.api.nvim_win_get_buf(winid)
|
||||||
|
return fl._window_statuslines[winid][bufnr]()
|
||||||
|
end
|
||||||
|
function draw_tabline()
|
||||||
|
return fl._tabline()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Set up a statusline per window
|
||||||
|
fl.setup_statusline = function(generator)
|
||||||
|
fl._window_statuslines = get_new_windows_table()
|
||||||
|
fl.generator = generator
|
||||||
|
vim.api.nvim_create_autocmd(
|
||||||
|
{'BufWinEnter','WinEnter'},
|
||||||
|
{pattern='*', callback=function()
|
||||||
|
vim.wo.statusline = "%!luaeval('draw_statusline("..vim.api.nvim_get_current_win()..")')"
|
||||||
|
end
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Set up a global tabline
|
||||||
|
fl.setup_tabline = function(generator)
|
||||||
|
fl._tabline = generator
|
||||||
|
vim.cmd('set tabline=%!v:lua.draw_tabline()')
|
||||||
|
end
|
||||||
|
|
||||||
|
return fl
|
Loading…
Reference in New Issue
Block a user