Compare commits

...

3 Commits

Author SHA1 Message Date
44ec0633ce add a ui (wip) 2025-07-01 21:44:43 -04:00
fa3457e463 formatting 2025-07-01 21:38:41 -04:00
f4d1c4cf25 reorganize the main file a little bit 2025-07-01 21:38:10 -04:00
5 changed files with 372 additions and 52 deletions

View File

@ -1,11 +1,11 @@
local logger = require('dep.log')
local git = require('dep.git')
local fs = require('dep.fs')
local packager = require('dep.package')
local h = require('dep.helpers')
local logger = require("dep.log")
local git = require("dep.git")
local fs = require("dep.fs")
local packager = require("dep.package")
local modules = require("dep.modules")
local bench = require("dep.bench")
local lazy = require("dep.lazy")
local ui = require("dep.ui")
-- all functions for convenience
local M = {}
@ -17,7 +17,7 @@ local M = {}
--- sync a tree of plugins
---@param tree package[] tree of plugins
---@param cb function? callback
function M.synctree(tree, cb)
local function synctree(tree, cb)
local progress = 0
local has_errors = false
@ -57,6 +57,29 @@ function M.synctree(tree, cb)
end
end
--- check if a package should be synced
---@param opts table options
---@param package package package table spec
---@return boolean sync
local function shouldsync(opts, package)
if opts.sync == "new" or opts.sync == nil then
return not package.exists
else
return opts.sync == "always"
end
end
--- make comparison for table.sort
---@param a package package spec a
---@param b package package spec b
---@return boolean
local function comp(a, b)
-- NOTE: this doesn't have to be in any real order, it just has to be
-- consistant, thus we can just check if the unicode value of one package
-- id is less than the other
return a.id < b.id
end
-- basically the main function of our program
---@param opts speclist
return function(opts)
@ -65,17 +88,6 @@ return function(opts)
bench.setup()
lazy.setup()
--- make comparison for table.sort
---@param a package package spec a
---@param b package package spec b
---@return boolean
local function comp(a, b)
-- NOTE: this doesn't have to be in any real order, it just has to be
-- consistant, thus we can just check if the unicode value of one package
-- id is less than the other
return a.id < b.id
end
local initialized, err = pcall(function()
packager.set_base_dir(opts.base_dir or vim.fn.stdpath("data").."/site/pack/deps/opt/")
bench.mark("load", function()
@ -115,27 +127,16 @@ return function(opts)
package:reload()
end
--- check if a package should be synced
---@param package table package table spec
---@return boolean sync
local function shouldsync(package)
if opts.sync == "new" or opts.sync == nil then
return not package.exists
else
return opts.sync == "always"
end
end
-- get all package that need syncing
local targets = {}
for _, package in pairs(packager.get_packages()) do
if shouldsync(package) then
if shouldsync(opts, package) then
table.insert(targets, package)
end
end
-- install all targets
M.synctree(targets)
synctree(targets)
end)
if not initialized then
@ -143,27 +144,8 @@ return function(opts)
end
-- add some user commands
vim.api.nvim_create_user_command("DepLog", function()
vim.cmd('vsp '..logger.path)
vim.opt_local.readonly = true
-- make the log auto update while it's open
local w = h.uv.new_fs_event()
local function watch_file(fname)
local fullpath = vim.api.nvim_call_function(
'fnamemodify', { fname, ':p' })
w:start(fullpath, {}, vim.schedule_wrap(function(...)
vim.cmd('checktime')
w:stop()
watch_file(fname)
end))
end
watch_file(logger.path)
end, {})
vim.api.nvim_create_user_command("DepSync", function()
M.synctree(packager.get_packages())
synctree(packager.get_packages())
end, {})
vim.api.nvim_create_user_command("DepReload", function()
@ -177,5 +159,15 @@ return function(opts)
fs:clean(packager)
end, {})
vim.api.nvim_create_user_command("DepUi", function()
ui.open(packager)
ui.set_page("P")
end, {})
vim.api.nvim_create_user_command("DepLog", function()
ui.open(packager)
ui.set_page("L")
end, {})
logger:cleanup()
end

View File

@ -79,8 +79,8 @@ function logger:log(level, message, ...)
-- write to the pipe if it's open
if logger.pipe then
logger.pipe:write(string.format("[%s] %s:%s: %s\n", os.date("%T"),
source.short_src:gsub('.*%/', ''), source.currentline, message))
logger.pipe:write(string.format("[%s] %s:%s:(%s) %s\n", os.date("%T"),
source.short_src:gsub('.*%/', ''), source.currentline, level, message))
end
end)
end

46
lua/dep/ui/format.lua Normal file
View File

@ -0,0 +1,46 @@
local logger = require("dep.log")
local format = {}
--- format a boolean to a chunk with highlights
---@param b boolean
---@return chunk chunk
function format.bool(b)
return { vim.inspect(b), b and "DiffAdd" or "DiffDelete" }
end
--- format a number to a chunk with highlights
---@param n number
---@return chunk chunk
function format.number(n)
return { vim.inspect(n), "Number" }
end
--- format a log line with highlights
---@param log_line string log line
---@return chunk[] chunks
function format.log_line(log_line)
local log_time = string.sub( log_line, string.find(log_line, "%[") + 1,
string.find(log_line, "%]") - 1)
local colon = string.find(log_line, ":", 11)
local log_path = string.sub(log_line, string.find(log_line, "%]") + 2,
colon - 1)
local log_path_ln = string.sub(log_line, colon + 1,
string.find(log_line, ":", colon + 1) - 1)
local level = string.sub(log_line, string.find(log_line, "%(") + 1,
string.find(log_line, "%)") - 1)
local rest = string.sub(log_line, string.find(log_line, "%)") + 2)
return {
{ "[", "" },
{ log_time, "Boolean" },
{ "] ", "" },
{ log_path, "String" },
{ ":", "" },
{ log_path_ln, "Number" },
{ ": ", "" },
{ rest, logger.stage_colors[level] or "" }
}
end
return format

198
lua/dep/ui/init.lua Normal file
View File

@ -0,0 +1,198 @@
local h = require("dep.helpers")
local page = require("dep.ui.page")
local logger = require("dep.log")
local format = require("dep.ui.format")
---@class ui
---@field bufnr number
---@field winnr number
---@field header_bufnr number
---@field header_winnr number
---@field pages page[]
local ui = {}
-- the active page being displayed
local active_page
-- all the pages
local pages = {}
-- the header ext mark
local header_ext_id
local function page_packages(packager)
local p = page:new("Packages", "P")
for _, pkg in pairs(packager.get_packages()) do
p:new_line({
{ pkg.id, "@conditional" },
{ " loaded: ", "" },
format.bool(pkg.loaded),
{ " lazy: ", "" },
format.bool(pkg.lazy)
})
end
return p
end
local function page_log()
local p = page:new("Log", "L")
local f = io.open(logger.path, "r")
if not f then
return
end
-- put the cursor at the bottom of the page after drawing
p.post_draw = function()
vim.api.nvim_win_set_cursor(ui.winnr, { #p.content, 0 })
end
-- read in the contents of the file, and keep watching for updates
local function update_contents()
repeat
local line = f:read("*l")
-- if the line isn't empty we shouldn't draw it
if line then
p:new_line(format.log_line(line))
end
until not line
end
update_contents()
local fullpath = vim.api.nvim_call_function(
"fnamemodify", { logger.path, ":p" })
h.uv.new_fs_event():start(fullpath, {}, vim.schedule_wrap(function()
update_contents()
-- if the log is currently being displayed then make sure to draw it when
-- it updates
if active_page == p then
p:draw(ui.bufnr)
end
end))
return p
end
--- set the current page
---@param p string|page page to set
function ui.set_page(p)
if type(p) == "string" then
for _, v in ipairs(pages) do
if p == v.kb then
v:draw(ui.bufnr)
active_page = v
break
end
end
elseif type(p) == "table" then
p:draw(ui.bufnr)
active_page = p
end
-- color the header text
local txt = vim.api.nvim_buf_get_text(ui.header_bufnr, 0, 0, -1, -1, {})[1]
local start_range = (string.find(txt, active_page.name)) - 2
local end_range = #active_page.name + start_range + 2
if header_ext_id then
vim.api.nvim_buf_del_extmark(ui.header_bufnr, active_page.hlns, header_ext_id)
end
header_ext_id = vim.api.nvim_buf_set_extmark(ui.header_bufnr,
active_page.hlns, 0, start_range, {
hl_mode = "replace",
hl_group = "CurSearch",
end_col = end_range,
})
end
--- setup all the pages
---@param packager package the packager
local function setup_pages(packager)
local header_text = ""
table.insert(pages, page_packages(packager))
table.insert(pages, page_log())
for _, v in ipairs(pages) do
header_text = header_text.." "..v.name.." "
vim.keymap.set("n", v.kb, function()
ui.set_page(v)
end, { buffer = ui.bufnr })
end
-- set the header text
vim.api.nvim_buf_set_lines(ui.header_bufnr, 0, -1, false, { header_text })
-- add keymaps
vim.keymap.set("n", "q", function()
vim.api.nvim_win_close(ui.winnr, false)
ui.winnr = nil
end, { buffer = ui.bufnr })
end
--- setup the ui
---@param packager package
function ui.open(packager)
if not ui.bufnr then
ui.bufnr = vim.api.nvim_create_buf(false, true)
end
if not ui.header_bufnr then
ui.header_bufnr = vim.api.nvim_create_buf(false, true)
end
local header_height = 1
local width = math.floor(vim.o.columns * 0.8)
local height = math.floor(vim.o.lines * 0.8) - header_height
if not ui.winnr then
ui.winnr = vim.api.nvim_open_win(ui.bufnr, true, {
relative = "editor",
row = (vim.o.lines - height) / 2,
col = (vim.o.columns - width) / 2,
width = width,
height = height,
border = "solid",
zindex = 998,
})
end
if not ui.header_winnr then
ui.header_winnr = vim.api.nvim_open_win(ui.header_bufnr, false, {
relative = "editor",
row = ((vim.o.lines - height) / 2) - (header_height * 2),
col = (vim.o.columns - width) / 2,
width = width,
height = header_height,
border = "solid",
zindex = 999,
focusable = false
})
end
vim.api.nvim_win_set_buf(ui.winnr, ui.bufnr)
vim.api.nvim_win_set_buf(ui.header_winnr, ui.header_bufnr)
-- make sure the header closes when the body does and vice versa
local function cb()
vim.api.nvim_win_close(ui.header_winnr, false)
ui.header_winnr = nil
vim.api.nvim_win_close(ui.winnr, false)
ui.winnr = nil
end
vim.api.nvim_create_autocmd("WinClosed", {
pattern = ui.winnr.."",
callback = cb
})
vim.api.nvim_create_autocmd("WinClosed", {
pattern = ui.header_winnr.."",
callback = cb
})
setup_pages(packager)
end
return ui

84
lua/dep/ui/page.lua Normal file
View File

@ -0,0 +1,84 @@
---@class chunk: table
---@field [1] string text to be displayed
---@field [2] string neovim highlight group to use
---@class page
---@field name string name of the ui page
---@field kb string keybind of the page
---@field content chunk[]|chunk[][] all the chunks
---@field hlns number highlight namespace
---@field pre_draw function things to do prior to drawing to the buffer
---@field post_draw function things to do post drawing to the buffer
local page = {}
--- create a new page
---@param name string the name of the page
---@param kb string keybind to change to the page
---@return page page
function page:new(name, kb)
local o = {}
self.__index = self
setmetatable(o, self)
o.hlns = vim.api.nvim_create_namespace("DepUi")
o.name = name
o.kb = kb
o.content = {}
return o
end
--- add a new line to the page
---@param line chunk|chunk[] new line
function page:new_line(line)
table.insert(self.content, line)
end
--- draw the page to the given buffer
---@param bufnr number buffer number
function page:draw(bufnr)
-- try to run pre_draw steps
if self.pre_draw then
self.pre_draw()
end
-- ready all information for rendering
for i, chunk in ipairs(self.content) do
local linenr = i - 1
local text = ""
local hls = {}
if type(chunk[1]) == "table" then
local j = 0
for _, ch in ipairs(chunk) do
text = text..ch[1]
table.insert(hls, { ch[2], j, j + #ch[1] })
j = j + #ch[1]
end
elseif type(chunk[1]) == "string" then
text = chunk[1]
table.insert(hls, { chunk[2], 0, #text })
end
-- draw the text to the buffer
vim.api.nvim_buf_set_lines(bufnr, linenr, -1, false, { text })
-- highlight the buffer
for _, hl in ipairs(hls) do
vim.api.nvim_buf_set_extmark(bufnr, self.hlns, linenr, hl[2], {
hl_mode = "replace",
hl_group = hl[1],
end_col = hl[3],
end_row = linenr
})
end
end
-- try to run post_draw steps
if self.post_draw then
self.post_draw()
end
end
return page