feat: hashing on a per config.settings.key() basis

This commit is contained in:
theprimeagen 2024-04-02 15:32:03 -06:00
parent 4ad05be8fe
commit 3e32576076
8 changed files with 179 additions and 116 deletions

View File

@ -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

View File

@ -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

View File

@ -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",

View File

@ -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

View File

@ -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)

View File

@ -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()
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_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 } },
utils.create_file(file_name_2, contents, row_2, col_2)
harpoon:list():prepend()
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()
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 } },
},
})
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)

View File

@ -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()

View File

@ -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()