From 353e739fbe1b745f5b65919972ffa31baf63acb4 Mon Sep 17 00:00:00 2001 From: Will Hopkins Date: Sun, 10 Sep 2023 02:33:04 -0700 Subject: [PATCH] feat(path): support file preview for path source fix(sources-path): indexing nil value when previewing dirs/links, etc. feat(sources-path & configs): add `opts.sources.path.preview` --- README.md | 16 ++++++ doc/dropbar.txt | 19 +++++++ lua/dropbar/configs.lua | 5 ++ lua/dropbar/menu.lua | 15 ++++++ lua/dropbar/sources/path.lua | 101 +++++++++++++++++++++++++++++++---- 5 files changed, 147 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 32e4b5ed..fa173999 100644 --- a/README.md +++ b/README.md @@ -789,6 +789,11 @@ vim.ui.select = require('dropbar.utils.menu').select modified = function(sym) return sym end, + ---@type boolean|fun(path: string): boolean?|nil + preview = function(path) + local stat = vim.uv.fs_stat(path) + return stat and stat.type == 'file' and stat.size <= 524288 + end, }, treesitter = { -- Lua pattern used to extract a short name from the node text @@ -1596,6 +1601,16 @@ each sources. }) end ``` +- `opts.sources.path.preview`: `boolean|fun(path: string): boolean?|nil` + - A boolean or a function that takes a file path and returns whether to + preview the file under cursor + - Default: + ```lua + function(path) + local stat = vim.uv.fs_stat(path) + return stat and stat.type == 'file' and stat.size <= 524288 + end + ``` ##### Treesitter @@ -2139,6 +2154,7 @@ Declared and defined in [`lua/dropbar/menu.lua`](https://github.com/Bekaboo/drop | `_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/doc/dropbar.txt b/doc/dropbar.txt index 2cab2768..832d4928 100644 --- a/doc/dropbar.txt +++ b/doc/dropbar.txt @@ -892,6 +892,17 @@ PATH *dropbar-configuration-options-sources-path* -- ... }) end +- `opts.sources.path.preview`: `boolean|fun(path: string): boolean?|nil` + - A boolean or a function that takes a file path and returns whether to + preview the file under cursor + - Default: >lua + + function(path) + local stat = vim.uv.fs_stat(path) + return stat and stat.type == 'file' + and stat.size <= 524288 + end +< TREESITTER *dropbar-configuration-options-sources-treesitter* @@ -1881,6 +1892,14 @@ dropbar_menu_t.win *dropbar_menu_t.win* Type ~ integer +dropbar_menu_t.prev_buf *dropbar_menu_t.prev_buf* + + Previous buffer, assigned when calling `new()` or automatically + determined in `open()` + + Type ~ + integer? + dropbar_menu_t.is_opened *dropbar_menu_t.is_opened* Whether the menu is currently opened diff --git a/lua/dropbar/configs.lua b/lua/dropbar/configs.lua index cc346c0a..c83dd827 100644 --- a/lua/dropbar/configs.lua +++ b/lua/dropbar/configs.lua @@ -536,6 +536,11 @@ M.opts = { modified = function(sym) return sym end, + ---@type boolean|fun(path: string): boolean?|nil + preview = function(path) + local stat = vim.uv.fs_stat(path) + return stat and stat.type == 'file' and stat.size <= 524288 + end, }, 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 43d352a9..aab8e9e1 100644 --- a/lua/dropbar/menu.lua +++ b/lua/dropbar/menu.lua @@ -183,6 +183,7 @@ end ---@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 @@ -303,6 +304,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..a3f0f975 100644 --- a/lua/dropbar/sources/path.lua +++ b/lua/dropbar/sources/path.lua @@ -11,7 +11,7 @@ local function get_icon_and_hl(path) local icon = icon_kind_opts.symbols.File local icon_hl = 'DropBarIconKindFile' local name_hl = 'DropBarKindFile' - local stat = vim.loop.fs_stat(path) + local stat = vim.uv.fs_stat(path) if not stat then return icon, icon_hl elseif stat.type == 'directory' then @@ -34,6 +34,82 @@ 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 stat = vim.uv.fs_stat(path) + if not stat or stat.type ~= 'file' then + self:preview_restore_view() + return + end + local 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(self, path) + if not configs.eval(configs.opts.sources.path.preview, path) then + return + end + local preview_buf = preview_prepare_buf(self, path) + if not preview_buf then + return + end + local buflisted = vim.bo[preview_buf].buflisted + + local preview_win = self.entry.menu:root_win() + if not preview_win then + return + end + self.entry.menu.prev_buf = self.entry.menu.prev_buf + or vim.api.nvim_win_get_buf(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(preview_win, preview_buf) + -- set cursor to the last exited position in buf (:h '"), if available + local last_exit = vim.api.nvim_buf_get_mark(preview_buf, '"') + if last_exit[1] ~= 0 then + vim.api.nvim_win_set_cursor(preview_win, last_exit) + end + + vim.bo[preview_buf].buflisted = buflisted + + -- ensure dropbar still shows then the preview buffer is opened + vim.wo[preview_win].winbar = '%{%v:lua.dropbar.get_dropbar_str()%}' +end + +---@param self dropbar_symbol_t +local function preview_close(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 +118,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 +130,10 @@ local function convert(path, buf, win) jump = function(_) vim.cmd.edit(path) end, + preview = vim.schedule_wrap(function(self) + preview_open(self, path) + end), + preview_restore_view = preview_close, }, { ---@param self dropbar_symbol_t __index = function(self, k) @@ -69,14 +150,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