From a0faad2b9fb7e5f88a59bc22f9be20445ebc13a1 Mon Sep 17 00:00:00 2001 From: bekaboo <18127878294@qq.com> Date: Sun, 2 Jul 2023 00:35:12 -0500 Subject: [PATCH] feat(config)!: open menu relative to clicked symbol by default, fix #37 --- README.md | 44 +++++++++++++++++++++++++++++++---------- doc/dropbar.txt | 43 ++++++++++++++++++++++++++++++++++------ lua/dropbar/bar.lua | 29 +++++++++++++++++++++++++++ lua/dropbar/configs.lua | 22 ++++++++++++++++----- tests/bar_spec.lua | 24 ++++++++++++++++++++++ 5 files changed, 141 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 74a4d28b..89e45502 100644 --- a/README.md +++ b/README.md @@ -444,16 +444,28 @@ https://github.com/Bekaboo/dropbar.nvim/assets/76579810/e8c1ac26-0321-4762-9975- return menu.prev_menu and menu.prev_menu.clicked_at and menu.prev_menu.clicked_at[1] - vim.fn.line('w0') - or 1 + or 0 end, + ---@param menu dropbar_menu_t col = function(menu) - return menu.prev_menu and menu.prev_menu._win_configs.width or 0 - end, - relative = function(menu) - return menu.prev_menu and 'win' or 'mouse' + if menu.prev_menu then + return menu.prev_menu._win_configs.width + end + local mouse = vim.fn.getmousepos() + local bar = require('dropbar.api').get_dropbar( + vim.api.nvim_win_get_buf(menu.prev_win), + menu.prev_win + ) + if not bar then + return mouse.wincol + end + local _, range = bar:get_component_at(math.max(0, mouse.wincol - 1)) + return range and range.start or mouse.wincol end, + relative = 'win', win = function(menu) return menu.prev_menu and menu.prev_menu.win + or vim.fn.getmousepos().winid end, height = function(menu) return math.max( @@ -919,16 +931,28 @@ menu: return menu.prev_menu and menu.prev_menu.clicked_at and menu.prev_menu.clicked_at[1] - vim.fn.line('w0') - or 1 + or 0 end, + ---@param menu dropbar_menu_t col = function(menu) - return menu.prev_menu and menu.prev_menu._win_configs.width or 0 - end, - relative = function(menu) - return menu.prev_menu and 'win' or 'mouse' + if menu.prev_menu then + return menu.prev_menu._win_configs.width + end + local mouse = vim.fn.getmousepos() + local bar = require('dropbar.api').get_dropbar( + vim.api.nvim_win_get_buf(menu.prev_win), + menu.prev_win + ) + if not bar then + return mouse.wincol + end + local _, range = bar:get_component_at(math.max(0, mouse.wincol - 1)) + return range and range.start or mouse.wincol end, + relative = 'win', win = function(menu) return menu.prev_menu and menu.prev_menu.win + or vim.fn.getmousepos().winid end, height = function(menu) return math.max( diff --git a/doc/dropbar.txt b/doc/dropbar.txt index 0b1a266c..fde9ec80 100644 --- a/doc/dropbar.txt +++ b/doc/dropbar.txt @@ -508,16 +508,29 @@ the menu: return menu.prev_menu and menu.prev_menu.clicked_at and menu.prev_menu.clicked_at[1] - vim.fn.line('w') - or 1 + or 0 end, + ---@param menu dropbar_menu_t col = function(menu) - return menu.prev_menu and menu.prev_menu._win_configs.width or 0 - end, - relative = function(menu) - return menu.prev_menu and 'win' or 'mouse' + if menu.prev_menu then + return menu.prev_menu._win_configs.width + end + local mouse = vim.fn.getmousepos() + local bar = require('dropbar.api').get_dropbar( + vim.api.nvim_win_get_buf(menu.prev_win), + menu.prev_win + ) + if not bar then + return mouse.wincol + end + local _, range = + bar:get_component_at(math.max(0, mouse.wincol - 1)) + return range and range.start or mouse.wincol end, + relative = 'win', win = function(menu) - return menu.prev_menu and menu.prev_menu.win + return menu.prev_menu and menu.prev_menu.win + or vim.fn.getmousepos().winid end, height = function(menu) return math.max( @@ -1111,6 +1124,24 @@ dropbar_t:pick([{idx}]) *dropbar_t:pick()* Parameters ~ • {idx} (integer?): The index of the component to pick + *dropbar_t:get_component_at()* +dropbar_t:get_component_at({col}[, {look_ahead}]) + + Get the component at column {col} and the range it occupies in the + menu + + Parameters ~ + • {col} (integer): The column to look at + • {look_ahead} (boolean?): + whether to look ahead to find a component if no component is + found at {col} + + Returns ~ + (`dropbar_symbol_t`): the component at {col} + + A table with fields `start` and `end` representing the range the + component occupies in the menu + dropbar_t:__tostring() *dropbar_t:__tostring()* Meta method to convert dropbar_t to its string representation diff --git a/lua/dropbar/bar.lua b/lua/dropbar/bar.lua index 0da53c87..f3499da9 100644 --- a/lua/dropbar/bar.lua +++ b/lua/dropbar/bar.lua @@ -111,6 +111,7 @@ function dropbar_symbol_t:new(opts) end if this.bar.in_pick_mode then win_configs.relative = 'win' + win_configs.win = vim.api.nvim_get_current_win() win_configs.row = 0 win_configs.col = this.bar.padding.left + _sum(vim.tbl_map( @@ -595,6 +596,34 @@ function dropbar_t:pick(idx) end) end +---Get the component at the given position in the winbar +---@param col integer 0-indexed, byte-indexed +---@param look_ahead boolean? whether to look ahead for the next component if the given position does not contain a component +---@return dropbar_symbol_t? +---@return {start: integer, end: integer}? range of the component in the menu, byte-indexed, 0-indexed, start-inclusive, end-exclusive +function dropbar_t:get_component_at(col, look_ahead) + local col_offset = self.padding.left + for _, component in ipairs(self.components) do + -- Use display width instead of byte width here because + -- vim.fn.getmousepos().wincol is the display width of the mouse position + -- and also the menu window needs to be opened with relative to the + -- display position of the winbar symbol to be aligned with the symbol + -- on the screen + local component_len = component:displaywidth() + if + (look_ahead or col >= col_offset) and col < col_offset + component_len + then + return component, + { + start = col_offset, + ['end'] = col_offset + component_len, + } + end + col_offset = col_offset + component_len + self.separator:displaywidth() + end + return nil, nil +end + ---Get the string representation of the dropbar ---@return string function dropbar_t:__tostring() diff --git a/lua/dropbar/configs.lua b/lua/dropbar/configs.lua index 23384ded..72acbb4a 100644 --- a/lua/dropbar/configs.lua +++ b/lua/dropbar/configs.lua @@ -261,16 +261,28 @@ M.opts = { return menu.prev_menu and menu.prev_menu.clicked_at and menu.prev_menu.clicked_at[1] - vim.fn.line('w0') - or 1 + or 0 end, + ---@param menu dropbar_menu_t col = function(menu) - return menu.prev_menu and menu.prev_menu._win_configs.width or 0 - end, - relative = function(menu) - return menu.prev_menu and 'win' or 'mouse' + if menu.prev_menu then + return menu.prev_menu._win_configs.width + end + local mouse = vim.fn.getmousepos() + local bar = require('dropbar.api').get_dropbar( + vim.api.nvim_win_get_buf(menu.prev_win), + menu.prev_win + ) + if not bar then + return mouse.wincol + end + local _, range = bar:get_component_at(math.max(0, mouse.wincol - 1)) + return range and range.start or mouse.wincol end, + relative = 'win', win = function(menu) return menu.prev_menu and menu.prev_menu.win + or vim.fn.getmousepos().winid end, height = function(menu) return math.max( diff --git a/tests/bar_spec.lua b/tests/bar_spec.lua index c9dd878f..9914f32a 100644 --- a/tests/bar_spec.lua +++ b/tests/bar_spec.lua @@ -190,6 +190,30 @@ describe('[bar]', function() winbar:pick(2) assert.spy(agent).was_called() end) + it('gets the component according to given position', function() + -- sym1 has width 0 and is at the beginning of the bar + -- thus cannot be picked by get_component_at() + local sep_width = winbar.separator:displaywidth() + local current_col = 0 + assert.is_nil(winbar:get_component_at(current_col)) + assert.are.equal(sym2, winbar:get_component_at(current_col, true)) + current_col = current_col + + winbar.padding.left + + sym1:displaywidth() + + sep_width + assert.are.equal(sym2, winbar:get_component_at(current_col)) + current_col = current_col + sym2:displaywidth() + assert.are.equal(sym3, winbar:get_component_at(current_col, true)) + current_col = current_col + sep_width + assert.are.equal(sym3, winbar:get_component_at(current_col)) + current_col = current_col + sym3:displaywidth() + assert.are.equal(sym4, winbar:get_component_at(current_col, true)) + current_col = current_col + sep_width + assert.are.equal(sym4, winbar:get_component_at(current_col)) + current_col = current_col + sym4:displaywidth() + assert.is_nil(winbar:get_component_at(current_col, true)) + assert.is_nil(winbar:get_component_at(current_col)) + end) it('deletes itself', function() local agents = vim.tbl_map(function(component) return spy.on(component, 'del')