From 3e32576076a7897eb806f1437cac928ac60d47c3 Mon Sep 17 00:00:00 2001 From: theprimeagen Date: Tue, 2 Apr 2024 15:32:03 -0600 Subject: [PATCH] feat: hashing on a per config.settings.key() basis --- lua/harpoon/config.lua | 15 ++++++ lua/harpoon/data.lua | 83 +++++++++++++++---------------- lua/harpoon/extensions/init.lua | 8 +++ lua/harpoon/init.lua | 34 +++++++++---- lua/harpoon/list.lua | 59 +++++++++++----------- lua/harpoon/test/harpoon_spec.lua | 77 ++++++++++++++++++---------- lua/harpoon/test/utils.lua | 16 ++++-- lua/harpoon/ui.lua | 1 - 8 files changed, 178 insertions(+), 115 deletions(-) diff --git a/lua/harpoon/config.lua b/lua/harpoon/config.lua index fa146102..596d2983 100644 --- a/lua/harpoon/config.lua +++ b/lua/harpoon/config.lua @@ -57,6 +57,7 @@ function M.get_default_config() settings = { save_on_toggle = false, sync_on_ui_close = false, + key = function() return vim.loop.cwd() end, @@ -205,6 +206,11 @@ function M.get_default_config() item.context.row = pos[1] item.context.col = pos[2] + + Extensions.extensions:emit( + Extensions.event_names.POSITION_UPDATED, + item + ) end end, @@ -231,4 +237,13 @@ function M.merge_config(partial_config, latest_config) return config end +---@param settings HarpoonPartialSettings +function M.create_config(settings) + local config = M.get_default_config() + for k, v in ipairs(settings) do + config.settings[k] = v + end + return config +end + return M diff --git a/lua/harpoon/data.lua b/lua/harpoon/data.lua index 2efd1931..1177fae2 100644 --- a/lua/harpoon/data.lua +++ b/lua/harpoon/data.lua @@ -1,43 +1,44 @@ local Path = require("plenary.path") local data_path = vim.fn.stdpath("data") -local full_data_path = string.format("%s/harpoon.json", data_path) + +---@param config HarpoonConfig +local filename = function(config) + return config.settings.key() +end + +local function hash(path) + return vim.fn.sha256(path) +end + +---@param config HarpoonConfig +local function fullpath(config) + local h = hash(filename(config)) + return string.format("%s/%s.json", data_path, h) +end ---@param data any -local function write_data(data) - Path:new(full_data_path):write(vim.json.encode(data), "w") +---@param config HarpoonConfig +local function write_data(data, config) + Path:new(fullpath(config)):write(vim.json.encode(data), "w") end local M = {} -function M.__dangerously_clear_data() - write_data({}) +---@param config HarpoonConfig +function M.__dangerously_clear_data(config) + write_data({}, config) end function M.info() return { data_path = data_path, - full_data_path = full_data_path, } end -function M.set_data_path(path) - full_data_path = path -end - -local function has_keys(t) - -- luacheck: ignore 512 - for _ in pairs(t) do - return true - end - - return false -end - --- @alias HarpoonRawData {[string]: {[string]: string[]}} --- @class HarpoonData ---- @field seen {[string]: {[string]: boolean}} --- @field _data HarpoonRawData --- @field has_error boolean local Data = {} @@ -48,34 +49,37 @@ local Data = {} Data.__index = Data +---@param config HarpoonConfig +---@param provided_path string? ---@return HarpoonRawData -local function read_data() - local path = Path:new(full_data_path) +local function read_data(config, provided_path) + provided_path = provided_path or fullpath(config) + local path = Path:new(provided_path) local exists = path:exists() if not exists then - write_data({}) + write_data({}, config) end local out_data = path:read() if not out_data or out_data == "" then - write_data({}) - out_data = path:read() + write_data({}, config) + out_data = "{}" end local data = vim.json.decode(out_data) return data end +---@param config HarpoonConfig ---@return HarpoonData -function Data:new() - local ok, data = pcall(read_data) +function Data:new(config) + local ok, data = pcall(read_data, config) return setmetatable({ _data = data, has_error = not ok, - seen = {}, }, self) end @@ -100,12 +104,6 @@ function Data:data(key, name) ) end - if not self.seen[key] then - self.seen[key] = {} - end - - self.seen[key][name] = true - return self:_get_data(key, name) end @@ -126,10 +124,6 @@ function Data:sync() return end - if not has_keys(self.seen) then - return - end - local ok, data = pcall(read_data) if not ok then error("Harpoon: unable to sync data, error reading data file") @@ -139,13 +133,16 @@ function Data:sync() data[k] = v end - ok = pcall(write_data, data) - - if ok then - self.seen = {} - end + pcall(write_data, data) end M.Data = Data +M.test = { + set_fullpath = function(fp) + fullpath = fp + end, + + read_data = read_data, +} return M diff --git a/lua/harpoon/extensions/init.lua b/lua/harpoon/extensions/init.lua index d673f0d1..a6638a4a 100644 --- a/lua/harpoon/extensions/init.lua +++ b/lua/harpoon/extensions/init.lua @@ -12,6 +12,7 @@ local HarpoonExtensions = {} ---@field LIST_CREATED? fun(...): nil ---@field LIST_READ? fun(...): nil ---@field NAVIGATE? fun(...): nil +---@field POSITION_UPDATED? fun(...): nil HarpoonExtensions.__index = HarpoonExtensions @@ -71,6 +72,13 @@ return { ADD = "ADD", SELECT = "SELECT", REMOVE = "REMOVE", + POSITION_UPDATED = "POSITION_UPDATED", + + --- This exists because the ui can change the list in dramatic ways + --- so instead of emitting a REMOVE, then an ADD, then a REORDER, we + --- instead just emit LIST_CHANGE + LIST_CHANGE = "LIST_CHANGE", + REORDER = "REORDER", UI_CREATE = "UI_CREATE", SETUP_CALLED = "SETUP_CALLED", diff --git a/lua/harpoon/init.lua b/lua/harpoon/init.lua index a323f626..7482d11f 100644 --- a/lua/harpoon/init.lua +++ b/lua/harpoon/init.lua @@ -18,19 +18,37 @@ local Harpoon = {} Harpoon.__index = Harpoon +---@param harpoon Harpoon +local function sync_on_change(harpoon) + local function sync(_) + return function() + harpoon:sync() + end + end + + Extensions.extensions:add_listener({ + ADD = sync("ADD"), + REMOVE = sync("REMOVE"), + REORDER = sync("REORDER"), + LIST_CHANGE = sync("LIST_CHANGE"), + POSITION_UPDATED = sync("POSITION_UPDATED"), + }) +end + ---@return Harpoon function Harpoon:new() local config = Config.get_default_config() local harpoon = setmetatable({ config = config, - data = Data.Data:new(), + data = Data.Data:new(config), logger = Log, ui = Ui:new(config.settings), _extensions = Extensions.extensions, lists = {}, hooks_setup = false, }, self) + sync_on_change(harpoon) return harpoon end @@ -51,10 +69,6 @@ function Harpoon:list(name) local existing_list = lists[name] if existing_list then - if not self.data.seen[key] then - self.data.seen[key] = {} - end - self.data.seen[key][name] = true self._extensions:emit(Extensions.event_names.LIST_READ, existing_list) return existing_list end @@ -72,16 +86,14 @@ end ---@param cb fun(list: HarpoonList, config: HarpoonPartialConfigItem, name: string) function Harpoon:_for_each_list(cb) local key = self.config.settings.key() - local seen = self.data.seen[key] local lists = self.lists[key] - - if not seen then + if not lists then return end - for list_name, _ in pairs(seen) do - local list_config = Config.get_config(self.config, list_name) - cb(lists[list_name], list_config, list_name) + for name, list in pairs(lists) do + local list_config = Config.get_config(self.config, name) + cb(list, list_config, name) end end diff --git a/lua/harpoon/list.lua b/lua/harpoon/list.lua index bf8008ac..df594662 100644 --- a/lua/harpoon/list.lua +++ b/lua/harpoon/list.lua @@ -134,15 +134,15 @@ function HarpoonList:add(item) end end - Extensions.extensions:emit( - Extensions.event_names.ADD, - { list = self, item = item, idx = idx } - ) - self.items[idx] = item if idx > self._length then self._length = idx end + + Extensions.extensions:emit( + Extensions.event_names.ADD, + { list = self, item = item, idx = idx } + ) end return self @@ -154,14 +154,15 @@ function HarpoonList:prepend(item) local index = index_of(self.items, item, self.config) Logger:log("HarpoonList:prepend", { item = item, index = index }) if index == -1 then - Extensions.extensions:emit( - Extensions.event_names.ADD, - { list = self, item = item, idx = 1 } - ) local stop_idx = prepend_to_array(self.items, item) if stop_idx > self._length then self._length = stop_idx end + + Extensions.extensions:emit( + Extensions.event_names.ADD, + { list = self, item = item, idx = 1 } + ) end return self @@ -173,15 +174,15 @@ function HarpoonList:remove(item) for i = 1, self._length do local v = self.items[i] if self.config.equals(v, item) then - Extensions.extensions:emit( - Extensions.event_names.REMOVE, - { list = self, item = item, idx = i } - ) Logger:log("HarpoonList:remove", { item = item, index = i }) self.items[i] = nil if i == self._length then self._length = determine_length(self.items, self._length) end + Extensions.extensions:emit( + Extensions.event_names.REMOVE, + { list = self, item = item, idx = i } + ) break end end @@ -195,14 +196,14 @@ function HarpoonList:remove_at(index) "HarpoonList:removeAt", { item = self.items[index], index = index } ) - Extensions.extensions:emit( - Extensions.event_names.REMOVE, - { list = self, item = self.items[index], idx = index } - ) self.items[index] = nil if index == self._length then self._length = determine_length(self.items, self._length) end + Extensions.extensions:emit( + Extensions.event_names.REMOVE, + { list = self, item = self.items[index], idx = index } + ) end return self end @@ -228,14 +229,12 @@ function HarpoonList:resolve_displayed(displayed, length) local list_displayed = self:display() + local change = 0 for i = 1, self._length do local v = self.items[i] local index = index_of(displayed, v) if index == -1 then - Extensions.extensions:emit( - Extensions.event_names.REMOVE, - { list = self, item = self.items[i], idx = i } - ) + change = change + 1 end end @@ -246,28 +245,26 @@ function HarpoonList:resolve_displayed(displayed, length) new_list[i] = nil elseif index == -1 then new_list[i] = self.config.create_list_item(self.config, v) - Extensions.extensions:emit( - Extensions.event_names.ADD, - { list = self, item = new_list[i], idx = i } - ) + change = change + 1 else - if index ~= i then - Extensions.extensions:emit( - Extensions.event_names.REORDER, - { list = self, item = self.items[index], idx = i } - ) - end local index_in_new_list = index_of(new_list, self.items[index], self.config) if index_in_new_list == -1 then new_list[i] = self.items[index] end + + if index ~= i then + change = change + 1 + end end end self.items = new_list self._length = length + if change > 0 then + Extensions.extensions:emit(Extensions.event_names.LIST_CHANGE) + end end function HarpoonList:select(index, options) diff --git a/lua/harpoon/test/harpoon_spec.lua b/lua/harpoon/test/harpoon_spec.lua index f172e052..0b0e4640 100644 --- a/lua/harpoon/test/harpoon_spec.lua +++ b/lua/harpoon/test/harpoon_spec.lua @@ -2,11 +2,23 @@ local utils = require("harpoon.test.utils") local harpoon = require("harpoon") local Extensions = require("harpoon.extensions") local Config = require("harpoon.config") +local Data = require("harpoon.data") +local List = require("harpoon.list") local eq = assert.are.same - +local config = Config.get_default_config() local be = utils.before_each(os.tmpname()) +local function expect_data(data) + local read_data = Data.test.read_data(config) + local testies = read_data.testies + + for k, v in pairs(data) do + local list = List.decode(Config.get_config(config, k), k, testies[k]) + eq(v, list.items) + end +end + describe("harpoon", function() before_each(function() be() @@ -24,7 +36,8 @@ describe("harpoon", function() "qux", }, row, col) - local list = harpoon:list():add() + harpoon:setup() + harpoon:list():add() local other_buf = utils.create_file("other-file", { "foo", "bar", @@ -36,11 +49,17 @@ describe("harpoon", function() vim.api.nvim_win_set_cursor(0, { row + 1, col }) vim.api.nvim_set_current_buf(other_buf) - local expected = { - { value = file_name, context = { row = row + 1, col = col } }, - } - - eq(expected, list.items) + expect_data({ + [Config.DEFAULT_LIST] = { + { + context = { + col = 0, + row = 2, + }, + value = "/tmp/harpoon-test", + }, + }, + }) end) it("full harpoon add sync cycle", function() @@ -67,7 +86,6 @@ describe("harpoon", function() end) it("prepend/add double add", function() - local default_list_name = harpoon:info().default_list_name local file_name_1 = "/tmp/harpoon-test" local row_1 = 3 local col_1 = 1 @@ -79,31 +97,38 @@ describe("harpoon", function() local contents = { "foo", "bar", "baz", "qux" } local bufnr_1 = utils.create_file(file_name_1, contents, row_1, col_1) - local list = harpoon:list():add() + harpoon:list():add() + + expect_data({ + [Config.DEFAULT_LIST] = { + { value = file_name_1, context = { row = row_1, col = col_1 } }, + }, + }) utils.create_file(file_name_2, contents, row_2, col_2) harpoon:list():prepend() - - harpoon:sync() - - eq(harpoon:dump(), { - testies = { - [default_list_name] = list:encode(), + expect_data({ + [Config.DEFAULT_LIST] = { + { value = file_name_2, context = { row = row_2, col = col_2 } }, + { value = file_name_1, context = { row = row_1, col = col_1 } }, }, }) - eq(list.items, { - { value = file_name_2, context = { row = row_2, col = col_2 } }, - { value = file_name_1, context = { row = row_1, col = col_1 } }, + harpoon:list():add() + expect_data({ + [Config.DEFAULT_LIST] = { + { value = file_name_2, context = { row = row_2, col = col_2 } }, + { value = file_name_1, context = { row = row_1, col = col_1 } }, + }, }) - harpoon:list():add() vim.api.nvim_set_current_buf(bufnr_1) harpoon:list():prepend() - - eq(list.items, { - { value = file_name_2, context = { row = row_2, col = col_2 } }, - { value = file_name_1, context = { row = row_1, col = col_1 } }, + expect_data({ + [Config.DEFAULT_LIST] = { + { value = file_name_2, context = { row = row_2, col = col_2 } }, + { value = file_name_1, context = { row = row_1, col = col_1 } }, + }, }) end) @@ -111,7 +136,7 @@ describe("harpoon", function() local list_created = false local list_name = "" local setup = false - local config = {} + local ext_config = {} harpoon:extend({ [Extensions.event_names.LIST_CREATED] = function(list) @@ -120,7 +145,7 @@ describe("harpoon", function() end, [Extensions.event_names.SETUP_CALLED] = function(c) setup = true - config = c + ext_config = c end, }) @@ -130,7 +155,7 @@ describe("harpoon", function() harpoon:list() eq(true, setup) - eq({}, config.foo) + eq({}, ext_config.foo) eq(true, list_created) eq(Config.DEFAULT_LIST, list_name) diff --git a/lua/harpoon/test/utils.lua b/lua/harpoon/test/utils.lua index 97192072..75f0a5b0 100644 --- a/lua/harpoon/test/utils.lua +++ b/lua/harpoon/test/utils.lua @@ -1,4 +1,5 @@ local Data = require("harpoon.data") +local Config = require("harpoon.config") local M = {} @@ -20,15 +21,24 @@ function M.return_to_checkpoint() M.clean_files() end +local function fullpath(name) + return function() + return name + end +end + ---@param name string function M.before_each(name) + local set_fullpath = fullpath(name) + local config = Config.get_default_config() return function() - Data.set_data_path(name) - Data.__dangerously_clear_data() + Data.test.set_fullpath(set_fullpath) + --- we don't use the config + Data.__dangerously_clear_data(config) require("plenary.reload").reload_module("harpoon") Data = require("harpoon.data") - Data.set_data_path(name) + Data.test.set_fullpath(set_fullpath) local harpoon = require("harpoon") M.return_to_checkpoint() diff --git a/lua/harpoon/ui.lua b/lua/harpoon/ui.lua index 30ddfe65..a6492919 100644 --- a/lua/harpoon/ui.lua +++ b/lua/harpoon/ui.lua @@ -193,7 +193,6 @@ function HarpoonUI:save() end Logger:log("ui#save", list) - print("saving", vim.inspect(list)) self.active_list:resolve_displayed(list, length) if self.settings.sync_on_ui_close then require("harpoon"):sync()