From 44ec0633cebc79d0dedfeaa3c388742ea98b8b48 Mon Sep 17 00:00:00 2001 From: Squibid Date: Tue, 1 Jul 2025 21:44:43 -0400 Subject: [PATCH] add a ui (wip) --- lua/dep.lua | 30 +++---- lua/dep/log.lua | 4 +- lua/dep/ui/format.lua | 46 ++++++++++ lua/dep/ui/init.lua | 198 ++++++++++++++++++++++++++++++++++++++++++ lua/dep/ui/page.lua | 84 ++++++++++++++++++ 5 files changed, 341 insertions(+), 21 deletions(-) create mode 100644 lua/dep/ui/format.lua create mode 100644 lua/dep/ui/init.lua create mode 100644 lua/dep/ui/page.lua diff --git a/lua/dep.lua b/lua/dep.lua index fcb7155..fe03e5f 100644 --- a/lua/dep.lua +++ b/lua/dep.lua @@ -5,6 +5,7 @@ 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 = {} @@ -143,25 +144,6 @@ 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() synctree(packager.get_packages()) end, {}) @@ -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 diff --git a/lua/dep/log.lua b/lua/dep/log.lua index 36ef6c6..467d941 100644 --- a/lua/dep/log.lua +++ b/lua/dep/log.lua @@ -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 diff --git a/lua/dep/ui/format.lua b/lua/dep/ui/format.lua new file mode 100644 index 0000000..0428ae4 --- /dev/null +++ b/lua/dep/ui/format.lua @@ -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 diff --git a/lua/dep/ui/init.lua b/lua/dep/ui/init.lua new file mode 100644 index 0000000..01d8d34 --- /dev/null +++ b/lua/dep/ui/init.lua @@ -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 diff --git a/lua/dep/ui/page.lua b/lua/dep/ui/page.lua new file mode 100644 index 0000000..85c568d --- /dev/null +++ b/lua/dep/ui/page.lua @@ -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