From 5c8cbe96903b8ebc492b34b58b5594ec4dcce74a Mon Sep 17 00:00:00 2001 From: Will Hopkins Date: Sun, 10 Sep 2023 02:33:04 -0700 Subject: [PATCH] feat(path): support floating and embedded preview for path source --- README.md | 6 ++ lua/dropbar/configs.lua | 3 + lua/dropbar/menu.lua | 16 +++ lua/dropbar/sources/path.lua | 182 +++++++++++++++++++++++++++++++++-- 4 files changed, 199 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 73c4b352..e861f3ca 100644 --- a/README.md +++ b/README.md @@ -1409,6 +1409,10 @@ each sources. }) end ``` + - `opts.sources.path.preview`: `"floating"` | `"previous"` + - Default: `"previous"` + - `"previous"` will preview the buffer in the menu's previous split window, like TS/LSP symbols. + - `"floating"` will preview the buffer in a popup winow adjacent to the menu. ##### Treesitter @@ -1942,12 +1946,14 @@ Declared and defined in [`lua/dropbar/menu.lua`](https://github.com/Bekaboo/drop | ------ | ------ | ------ | | `buf` | `integer` | buffer number of the menu | | `win` | `integer` | window id of the menu | +| `preview_win` | `integer` | window id being used for preview, if any | | `is_opened` | `boolean?` | whether the menu is currently opened | | `entries` | [`dropbar_menu_entry_t[]`](#dropbar_menu_entry_t) | entries in the menu | | `win_configs` | `table` | window configuration, value can be a function, see [menu configuration options](#menu) | | `_win_configs` | `table?` | evaluated window configuration | | `cursor` | `integer[]?` | initial cursor position | | `prev_win` | `integer?` | previous window, assigned when calling new() or automatically determined in open() | +| `prev_buf` | `integer?` | previous buffer, assigned when calling new() or automatically determined in open() | | `sub_menu` | `dropbar_menu_t?` | submenu, assigned when calling new() or automatically determined when a new menu opens | | `prev_menu` | `dropbar_menu_t?` | previous menu, assigned when calling new() or automatically determined in open() | | `clicked_at` | `integer[]?` | last position where the menu was clicked, 1,0-indexed | diff --git a/lua/dropbar/configs.lua b/lua/dropbar/configs.lua index 8768a920..45536fdd 100644 --- a/lua/dropbar/configs.lua +++ b/lua/dropbar/configs.lua @@ -446,6 +446,9 @@ M.opts = { modified = function(sym) return sym end, + ---Preview files in the prevous window or a floating window. + ---@type "previous"|"floating" + preview = 'previous', }, treesitter = { -- Lua pattern used to extract a short name from the node text diff --git a/lua/dropbar/menu.lua b/lua/dropbar/menu.lua index 5e552759..f676d367 100644 --- a/lua/dropbar/menu.lua +++ b/lua/dropbar/menu.lua @@ -177,12 +177,14 @@ end ---@class dropbar_menu_t ---@field buf integer? ---@field win integer? +---@field preview_win integer? The open preview window, if any ---@field is_opened boolean? ---@field entries dropbar_menu_entry_t[] ---@field win_configs table window configuration, value can be a function ---@field _win_configs table evaluated window configuration ---@field cursor integer[]? initial cursor position ---@field prev_win integer? previous window, assigned when calling new() or automatically determined in open() +---@field prev_buf integer? previous buffer, assigned when calling new() or automatically determined in open() ---@field sub_menu dropbar_menu_t? submenu, assigned when calling new() or automatically determined when a new menu opens ---@field prev_menu dropbar_menu_t? previous menu, assigned when calling new() or automatically determined in open() ---@field clicked_at integer[]? last position where the menu was clicked, byte-indexed, 1,0-indexed @@ -311,6 +313,20 @@ function dropbar_menu_t:click_at(pos, min_width, n_clicks, button, modifiers) end end +---Retrieves the root window of the menu. +---If `self.prev_menu` is nil then this `self.prev_win`. +---Otherwise, it is the root window of `self.prev_menu`. +---@return integer? +function dropbar_menu_t:root_win() + local current = self + local win = self.prev_win + while current and current.prev_menu do + win = current.prev_menu.prev_win + current = current.prev_menu + end + return win +end + ---"Click" the component in the dropbar menu ---Side effects: update self.clicked_at ---@param symbol dropbar_symbol_t diff --git a/lua/dropbar/sources/path.lua b/lua/dropbar/sources/path.lua index 323d6802..df6c0d01 100644 --- a/lua/dropbar/sources/path.lua +++ b/lua/dropbar/sources/path.lua @@ -34,6 +34,155 @@ local function get_icon_and_hl(path) return icon, icon_hl, name_hl end +---@param self dropbar_symbol_t +local function preview_prepare_buf(self, path) + local buf + if vim.uv.fs_stat(path).type == 'directory' then + -- TODO: preview directory entries + self:preview_restore_view() + return + end + buf = vim.fn.bufnr(path, false) + if buf == nil or buf == -1 then + buf = vim.fn.bufadd(path) + if not buf then + self:preview_restore_view() + return + end + if not vim.api.nvim_buf_is_loaded(buf) then + vim.fn.bufload(buf) + end + end + if buf == nil or self.entry.menu == nil or self.entry.menu.win == nil then + self:preview_restore_view() + return + end + return buf +end + +---@param self dropbar_symbol_t +local function preview_open_float(self, path, icon, icon_hl) + local preview_buf = preview_prepare_buf(self, path) + if not preview_buf then + return + end + + local function make_title() + local pat = vim.fs.normalize( + configs.eval(configs.opts.sources.path.relative_to, preview_buf) + ) + return { + { icon, icon_hl }, + { + vim.api + .nvim_buf_get_name(preview_buf) + :gsub('' .. pat .. '/', '') + :gsub('^' .. pat, ''), -- ':~' + 'NormalFloat', + }, + } + end + if + self.entry.menu.preview_win == nil + or vim.api.nvim_win_is_valid(self.entry.menu.preview_win) == false + then + self.entry.menu.preview_win = vim.api.nvim_open_win(preview_buf, false, { + -- relative = 'editor', + relative = 'win', + style = 'minimal', + -- focusable = false, + width = math.min(80, math.floor(vim.o.columns / 2)), + height = math.min(25, math.floor(vim.o.lines / 2)), + row = 0, + col = vim.api.nvim_win_get_width(self.entry.menu.win) + 1, + -- row = vim.api.nvim_win_get_position(self.entry.menu.win)[1], + -- col = vim.api.nvim_win_get_position(self.entry.menu.win)[2] + -- + vim.api.nvim_win_get_width(self.entry.menu.win) + -- + 1, + border = 'solid', + title = make_title(), + }) + vim.api.nvim_create_autocmd('BufLeave', { + buffer = self.entry.menu.buf, + callback = function() + self:preview_restore_view() + end, + }) + vim.schedule(function() + vim.api.nvim_exec_autocmds( + 'CursorMoved', + { buffer = self.entry.menu.buf } + ) + end) + else + vim.api.nvim_win_set_buf(self.entry.menu.preview_win, preview_buf) + local config = vim.api.nvim_win_get_config(self.entry.menu.preview_win) + config.title = make_title() + vim.api.nvim_win_set_config(self.entry.menu.preview_win, config) + end + vim.api.nvim_win_set_cursor(self.entry.menu.preview_win, { 1, 0 }) + vim.wo[self.entry.menu.preview_win].winbar = '' + vim.wo[self.entry.menu.preview_win].stc = '' + vim.wo[self.entry.menu.preview_win].signcolumn = 'no' + vim.wo[self.entry.menu.preview_win].number = false + vim.wo[self.entry.menu.preview_win].relativenumber = false +end + +---@param self dropbar_symbol_t +local function preview_close_float(self) + if + self.entry.menu.preview_win + and vim.api.nvim_win_is_valid(self.entry.menu.preview_win) + then + vim.api.nvim_win_close(self.entry.menu.preview_win, true) + end + self.entry.menu.preview_win = nil +end + +---@param self dropbar_symbol_t +local function preview_open_previous(self, path) + local preview_buf = preview_prepare_buf(self, path) + if not preview_buf then + return + end + local buflisted = vim.bo[preview_buf].buflisted + + self.entry.menu.preview_win = self.entry.menu:root_win() + self.entry.menu.prev_buf = self.entry.menu.prev_buf + or vim.api.nvim_win_get_buf(self.entry.menu.preview_win) + + vim.api.nvim_create_autocmd('BufLeave', { + buffer = self.entry.menu.buf, + callback = function() + self:preview_restore_view() + end, + }) + vim.api.nvim_win_set_buf(self.entry.menu.preview_win, preview_buf) + local last_exit = vim.api.nvim_buf_get_mark(preview_buf, '"') + if last_exit[1] ~= 0 then + vim.api.nvim_win_set_cursor(self.entry.menu.preview_win, last_exit) + end + + vim.bo[preview_buf].buflisted = buflisted + -- ensure dropbar still shows then the preview buffer is opened + vim.wo[self.entry.menu.preview_win].winbar = + '%{%v:lua.dropbar.get_dropbar_str()%}' +end + +---@param self dropbar_symbol_t +local function preview_close_previous(self) + if self.win then + if self.entry.menu.prev_buf then + vim.api.nvim_win_set_buf(self.win, self.entry.menu.prev_buf) + end + if self.view then + vim.api.nvim_win_call(self.win, function() + vim.fn.winrestview(self.view) + end) + end + end +end + ---Convert a path to a dropbar symbol ---@param path string full path ---@param buf integer buffer handler @@ -42,6 +191,7 @@ end local function convert(path, buf, win) local path_opts = configs.opts.sources.path local icon, icon_hl, name_hl = get_icon_and_hl(path) + return bar.dropbar_symbol_t:new(setmetatable({ buf = buf, win = win, @@ -53,6 +203,20 @@ local function convert(path, buf, win) jump = function(_) vim.cmd.edit(path) end, + preview = vim.schedule_wrap(function(self) + if path_opts.preview == 'previous' then + preview_open_previous(self, path) + else + preview_open_float(self, path, icon, icon_hl) + end + end), + preview_restore_view = function(self) + if path_opts.preview == 'previous' then + preview_close_previous(self) + else + preview_close_float(self) + end + end, }, { ---@param self dropbar_symbol_t __index = function(self, k) @@ -69,14 +233,16 @@ local function convert(path, buf, win) local parent_dir = vim.fs.dirname(path) self.siblings = {} self.sibling_idx = 1 - for idx, name in vim.iter(vim.fs.dir(parent_dir)):enumerate() do - if path_opts.filter(name) then - table.insert( - self.siblings, - convert(parent_dir .. '/' .. name, buf, win) - ) - if name == self.name then - self.sibling_idx = idx + if parent_dir then + for idx, name in vim.iter(vim.fs.dir(parent_dir)):enumerate() do + if path_opts.filter(name) then + table.insert( + self.siblings, + convert(parent_dir .. '/' .. name, buf, win) + ) + if name == self.name then + self.sibling_idx = idx + end end end end