From 9bec056bb40e062c0b171ec43982ae6b1d29ce44 Mon Sep 17 00:00:00 2001 From: "Kim A. Brandt" Date: Thu, 11 Apr 2024 17:28:08 +0200 Subject: [PATCH 1/4] feat(index-tracking): keep track of the `list._index` while navigating This change tries to keep track of the `list._index`, by using an `autocmd` for `BufEnter` - which updates the index when entering a harpooned file - and setting the index for different functions inside the list. --- lua/harpoon/buffer.lua | 12 ------- lua/harpoon/list.lua | 73 ++++++++++++++++++++++++++++++++++++++++-- lua/harpoon/ui.lua | 10 ++++-- 3 files changed, 78 insertions(+), 17 deletions(-) diff --git a/lua/harpoon/buffer.lua b/lua/harpoon/buffer.lua index a6a9fd30..af1814d7 100644 --- a/lua/harpoon/buffer.lua +++ b/lua/harpoon/buffer.lua @@ -29,18 +29,6 @@ end ---@param bufnr number function M.setup_autocmds_and_keymaps(bufnr) - local curr_file = vim.api.nvim_buf_get_name(0) - local cmd = string.format( - "autocmd Filetype harpoon " - .. "let path = '%s' | call clearmatches() | " - -- move the cursor to the line containing the current filename - .. "call search('\\V'.path.'\\$') | " - -- add a hl group to that line - .. "call matchadd('HarpoonCurrentFile', '\\V'.path.'\\$')", - curr_file:gsub("\\", "\\\\") - ) - vim.cmd(cmd) - if vim.api.nvim_buf_get_name(bufnr) == "" then vim.api.nvim_buf_set_name(bufnr, get_harpoon_menu_name()) end diff --git a/lua/harpoon/list.lua b/lua/harpoon/list.lua index 897122fa..9a663da7 100644 --- a/lua/harpoon/list.lua +++ b/lua/harpoon/list.lua @@ -1,4 +1,5 @@ local Logger = require("harpoon.logger") +local Path = require("plenary.path") local utils = require("harpoon.utils") local Extensions = require("harpoon.extensions") @@ -77,16 +78,51 @@ end --- @field items HarpoonItem[] local HarpoonList = {} +---@param list HarpoonList +---@param options any +local sync_index = function(list, options) + local bufnr = options.bufnr + local filename = options.filename + local index = options.index + if bufnr ~= nil and filename ~= nil then + local config = list.config + local relname = Path:new(filename):make_relative(config.get_root_dir()) + if bufnr == vim.fn.bufnr(relname, false) then + local element = config.create_list_item(config, relname) + local index_found = index_of(list.items, list._length, element, config) + if index_found > -1 then + list._index = index_found + -- elseif index then + -- list._index = index + end + elseif index ~= nil then + list._index = index + end + elseif (index ~= nil) then + list._index = index + end +end + HarpoonList.__index = HarpoonList function HarpoonList:new(config, name, items) items = items or {} - return setmetatable({ + local list = setmetatable({ items = items, config = config, name = name, _length = guess_length(items), - _index = 1, + _index = 0, }, self) + vim.api.nvim_create_autocmd({ "BufEnter" }, { + pattern = { "*" }, + callback = function (args) + sync_index(list, { + bufnr = args.buf, + filename = args.file + }) + end, + }) + return list end ---@return number @@ -151,6 +187,8 @@ function HarpoonList:add(item) self._length = idx end + sync_index(self, { index = idx }) + Extensions.extensions:emit( Extensions.event_names.ADD, { list = self, item = item, idx = idx } @@ -171,6 +209,8 @@ function HarpoonList:prepend(item) self._length = stop_idx end + sync_index(self, { index = 1 }) + Extensions.extensions:emit( Extensions.event_names.ADD, { list = self, item = item, idx = 1 } @@ -191,6 +231,14 @@ function HarpoonList:remove(item) if i == self._length then self._length = determine_length(self.items, self._length) end + + local current_buffer = vim.api.nvim_get_current_buf() + sync_index(self, { + bufnr = current_buffer, + filename = vim.api.nvim_buf_get_name(current_buffer), + -- index = index <= self._index and self._index - 1 or self._index, + }) + Extensions.extensions:emit( Extensions.event_names.REMOVE, { list = self, item = item, idx = i } @@ -212,6 +260,14 @@ function HarpoonList:remove_at(index) if index == self._length then self._length = determine_length(self.items, self._length) end + + local current_buffer = vim.api.nvim_get_current_buf() + sync_index(self, { + bufnr = current_buffer, + filename = vim.api.nvim_buf_get_name(current_buffer), + -- index = index <= self._index and self._index - 1 or self._index, + }) + Extensions.extensions:emit( Extensions.event_names.REMOVE, { list = self, item = self.items[index], idx = index } @@ -242,7 +298,10 @@ end --- much inefficiencies. dun care ---@param displayed string[] ---@param length number -function HarpoonList:resolve_displayed(displayed, length) +---@param options any +function HarpoonList:resolve_displayed(displayed, length, options) + options = options or {} + local new_list = {} local list_displayed = self:display() @@ -278,6 +337,12 @@ function HarpoonList:resolve_displayed(displayed, length) end end + local win_id = options.win_id + if win_id then + local pos = vim.api.nvim_win_get_cursor(win_id) + self._index = pos[1] + end + self.items = new_list self._length = length if change > 0 then @@ -288,6 +353,8 @@ end function HarpoonList:select(index, options) local item = self.items[index] if item or self.config.select_with_nil then + sync_index(self, { index = index }) + Extensions.extensions:emit( Extensions.event_names.SELECT, { list = self, item = item, idx = index } diff --git a/lua/harpoon/ui.lua b/lua/harpoon/ui.lua index e6c568c7..7894c927 100644 --- a/lua/harpoon/ui.lua +++ b/lua/harpoon/ui.lua @@ -153,6 +153,12 @@ function HarpoonUI:toggle_quick_menu(list, opts) local contents = self.active_list:display() vim.api.nvim_buf_set_lines(self.bufnr, 0, -1, false, contents) + local index = list._index + + if index > 0 then + vim.api.nvim_win_set_cursor(win_id, {index, 0}) + end + Extensions.extensions:emit(Extensions.event_names.UI_CREATE, { win_id = win_id, bufnr = bufnr, @@ -173,7 +179,7 @@ function HarpoonUI:select_menu_item(options) -- must first save any updates potentially made to the list before -- navigating local list, length = self:_get_processed_ui_contents() - self.active_list:resolve_displayed(list, length) + self.active_list:resolve_displayed(list, length, { win_id = self.win_id }) Logger:log( "ui#select_menu_item selecting item", @@ -193,7 +199,7 @@ function HarpoonUI:save() local list, length = self:_get_processed_ui_contents() Logger:log("ui#save", list) - self.active_list:resolve_displayed(list, length) + self.active_list:resolve_displayed(list, length, { win_id = self.win_id }) if self.settings.sync_on_ui_close then require("harpoon"):sync() end From 34e9a3bc5abff869752c7f1cef679ce01537e01c Mon Sep 17 00:00:00 2001 From: "Kim A. Brandt" Date: Thu, 11 Apr 2024 21:51:31 +0200 Subject: [PATCH 2/4] fix(encode): use `next()` instead of `ipairs()`, to prevent loss of data This fixes the issue, when there's an empty line inside the quick-menu, which causes everything after the blank line to be forgotten. --- lua/harpoon/list.lua | 17 +++++++++++++---- lua/harpoon/test/harpoon_spec.lua | 25 +++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/lua/harpoon/list.lua b/lua/harpoon/list.lua index 897122fa..d51042ef 100644 --- a/lua/harpoon/list.lua +++ b/lua/harpoon/list.lua @@ -344,8 +344,11 @@ end --- @return string[] function HarpoonList:encode() local out = {} - for _, v in ipairs(self.items) do - table.insert(out, self.config.encode(v)) + local items = self.items + local i, v = next(items, nil) + while i do + out[i] = self.config.encode(v) + i, v = next(items, i) end return out @@ -358,8 +361,14 @@ end function HarpoonList.decode(list_config, name, items) local list_items = {} - for _, item in ipairs(items) do - table.insert(list_items, list_config.decode(item)) + local i, item = next(items, nil) + while i do + if item == vim.NIL then + list_items[i] = nil -- allow nil-values + else + list_items[i] = list_config.decode(item) + end + i, item = next(items, i) end return HarpoonList:new(list_config, name, list_items) diff --git a/lua/harpoon/test/harpoon_spec.lua b/lua/harpoon/test/harpoon_spec.lua index 2dca4c09..96955002 100644 --- a/lua/harpoon/test/harpoon_spec.lua +++ b/lua/harpoon/test/harpoon_spec.lua @@ -122,6 +122,31 @@ describe("harpoon", function() }) end) + it("should ignore removed items", function() + local list = harpoon:list() + + utils.create_file("/tmp/harpoon-test1", {}, 1, 0) + list:add() + + utils.create_file("/tmp/harpoon-test2", {}, 1, 0) + list:add() + + utils.create_file("/tmp/harpoon-test3", {}, 1, 0) + list:add() + + list:remove_at(2) -- remove the second item + + local count_items = 0 + local encoded_items = list:encode() + local i, _ = next(encoded_items, nil) + while i do + count_items = count_items + 1 + i, _ = next(encoded_items, i) + end + + eq(2, count_items, "expecting two items in the list") + end) + it("out of bounds test: row over", function() out_of_bounds_test({ row = 5, From f9553acc4a30784cc42503aae3c45bdd607d613b Mon Sep 17 00:00:00 2001 From: "Kim A. Brandt" Date: Sat, 13 Apr 2024 20:20:09 +0200 Subject: [PATCH 3/4] fix(encode): use a `for`-loop, instead of `next()` For `next()`, the order in which the indices are enumerated is not specified[1], even for numeric indices. Even though it seemed to work, now the encode-/decode-logic uses a numerical `for`-loop, which should keep the order and not miss any items after a `nil`-item. Also, the loop inside the `encode`-function, uses the `list._length` instead of `#items`; which seems to ignore data, when there're 2 or more `nil`-values!? [1] https://www.lua.org/manual/5.1/manual.html#pdf-next --- lua/harpoon/list.lua | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lua/harpoon/list.lua b/lua/harpoon/list.lua index d51042ef..cbab41f1 100644 --- a/lua/harpoon/list.lua +++ b/lua/harpoon/list.lua @@ -345,10 +345,11 @@ end function HarpoonList:encode() local out = {} local items = self.items - local i, v = next(items, nil) - while i do - out[i] = self.config.encode(v) - i, v = next(items, i) + for i = 1, self._length do + local item = items[i] + if item then + out[i] = self.config.encode(items[i]) + end end return out @@ -361,14 +362,13 @@ end function HarpoonList.decode(list_config, name, items) local list_items = {} - local i, item = next(items, nil) - while i do + for i = 1, #items do + local item = items[i] if item == vim.NIL then list_items[i] = nil -- allow nil-values else list_items[i] = list_config.decode(item) end - i, item = next(items, i) end return HarpoonList:new(list_config, name, list_items) From 5bdbb379d949f7a48c08decb10250debe0a0d398 Mon Sep 17 00:00:00 2001 From: "Kim A. Brandt" Date: Sun, 14 Apr 2024 15:47:01 +0200 Subject: [PATCH 4/4] refactor: make pr-ready --- lua/harpoon/list.lua | 13 +++++-------- lua/harpoon/ui.lua | 2 +- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/lua/harpoon/list.lua b/lua/harpoon/list.lua index b93faa63..34b8e520 100644 --- a/lua/harpoon/list.lua +++ b/lua/harpoon/list.lua @@ -89,16 +89,15 @@ local sync_index = function(list, options) local relname = Path:new(filename):make_relative(config.get_root_dir()) if bufnr == vim.fn.bufnr(relname, false) then local element = config.create_list_item(config, relname) - local index_found = index_of(list.items, list._length, element, config) + local index_found = + index_of(list.items, list._length, element, config) if index_found > -1 then list._index = index_found - -- elseif index then - -- list._index = index end elseif index ~= nil then list._index = index end - elseif (index ~= nil) then + elseif index ~= nil then list._index = index end end @@ -115,10 +114,10 @@ function HarpoonList:new(config, name, items) }, self) vim.api.nvim_create_autocmd({ "BufEnter" }, { pattern = { "*" }, - callback = function (args) + callback = function(args) sync_index(list, { bufnr = args.buf, - filename = args.file + filename = args.file, }) end, }) @@ -236,7 +235,6 @@ function HarpoonList:remove(item) sync_index(self, { bufnr = current_buffer, filename = vim.api.nvim_buf_get_name(current_buffer), - -- index = index <= self._index and self._index - 1 or self._index, }) Extensions.extensions:emit( @@ -265,7 +263,6 @@ function HarpoonList:remove_at(index) sync_index(self, { bufnr = current_buffer, filename = vim.api.nvim_buf_get_name(current_buffer), - -- index = index <= self._index and self._index - 1 or self._index, }) Extensions.extensions:emit( diff --git a/lua/harpoon/ui.lua b/lua/harpoon/ui.lua index 7894c927..8ceab398 100644 --- a/lua/harpoon/ui.lua +++ b/lua/harpoon/ui.lua @@ -156,7 +156,7 @@ function HarpoonUI:toggle_quick_menu(list, opts) local index = list._index if index > 0 then - vim.api.nvim_win_set_cursor(win_id, {index, 0}) + vim.api.nvim_win_set_cursor(win_id, { index, 0 }) end Extensions.extensions:emit(Extensions.event_names.UI_CREATE, {