checkpoint: i must sleep with my beautiful wife, but f... its borked

This commit is contained in:
mpaulson 2023-11-26 21:04:54 -07:00
parent 2fdac4f4df
commit 5d7ee0d894
8 changed files with 318 additions and 222 deletions

121
lua/harpoon2/buffer.lua Normal file
View File

@ -0,0 +1,121 @@
local utils = require("harpoon2.utils")
local M = {}
local HARPOON_MENU = "__harpoon-menu__"
-- simple reason here is that if we are deving harpoon, we will create several
-- ui objects, each with their own buffer, which will cause the name to be duplicated and then we will get a vim error on nvim_buf_set_name
local harpoon_menu_id = 0
local function get_harpoon_menu_name()
harpoon_menu_id = harpoon_menu_id + 1
return HARPOON_MENU .. harpoon_menu_id
end
---TODO: I don't know how to do what i want to do, but i want to be able to
---make this so we use callbacks for these buffer actions instead of using
---strings back into the ui. it feels gross and it puts odd coupling
---@param bufnr number
function M.setup_autocmds_and_keymaps(bufnr)
--[[
-- TODO: Do the highlighting better
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("\\", "\\\\")
)
print(cmd)
vim.cmd(cmd)
--]]
if vim.api.nvim_buf_get_name(bufnr) == "" then
vim.api.nvim_buf_set_name(bufnr, get_harpoon_menu_name())
end
vim.api.nvim_buf_set_option(bufnr, "filetype", "harpoon")
vim.api.nvim_buf_set_option(bufnr, "buftype", "acwrite")
vim.api.nvim_buf_set_option(bufnr, "bufhidden", "delete")
--[[
vim.api.nvim_buf_set_keymap(
bufnr,
"n",
"z",
"<Cmd>lua print('WTF')<CR>",
{ silent = true }
)
--]]
vim.api.nvim_buf_set_keymap(
bufnr,
"n",
"q",
"<Cmd>lua require('harpoon2').ui:toggle_quick_menu()<CR>",
{ silent = true }
)
vim.api.nvim_buf_set_keymap(
bufnr,
"n",
"<ESC>",
"<Cmd>lua require('harpoon2').ui:toggle_quick_menu()<CR>",
{ silent = true }
)
vim.api.nvim_buf_set_keymap(
bufnr,
"n",
"<CR>",
"<Cmd>lua require('harpoon2').ui:select_menu_item()<CR>",
{}
)
-- TODO: Update these to use the new autocmd api
vim.cmd(
string.format(
"autocmd BufWriteCmd <buffer=%s> lua require('harpoon2').ui:on_menu_save()",
bufnr
)
)
-- TODO: Do we want this? is this a thing?
-- its odd... why save on text change? shouldn't we wait until close / w / esc?
--[[
if global_config.save_on_change then
vim.cmd(
string.format(
"autocmd TextChanged,TextChangedI <buffer=%s> lua require('harpoon2').ui:on_menu_save()",
bufnr
)
)
end
--]]
vim.cmd(
string.format(
"autocmd BufModifiedSet <buffer=%s> set nomodified",
bufnr
)
)
vim.cmd(
"autocmd BufLeave <buffer> ++nested ++once silent lua require('harpoon2').ui:toggle_quick_menu()"
)
end
---@param bufnr number
function M.get_contents(bufnr)
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, true)
local indices = {}
for _, line in pairs(lines) do
if not utils.is_white_space(line) then
table.insert(indices, line)
end
end
return indices
end
return M

View File

@ -1,3 +1,4 @@
local utils = require("harpoon2.utils")
local M = {} local M = {}
---@alias HarpoonListItem {value: any, context: any} ---@alias HarpoonListItem {value: any, context: any}
@ -12,6 +13,12 @@ local M = {}
---@field add? fun(item: any?): HarpoonListItem ---@field add? fun(item: any?): HarpoonListItem
---@field BufLeave? fun(evt: any, list: HarpoonList): nil ---@field BufLeave? fun(evt: any, list: HarpoonList): nil
---@field VimLeavePre? fun(evt: any, list: HarpoonList): nil ---@field VimLeavePre? fun(evt: any, list: HarpoonList): nil
---@field get_root_dir? fun(): string
---@class HarpoonWindowSettings
---@field width number
---@field height number
---notehunthoeunthoeunthoeunthoeunthoeunth ---notehunthoeunthoeunthoeunthoeunthoeunth
---@class HarpoonSettings ---@class HarpoonSettings
@ -108,10 +115,21 @@ function M.get_default_config()
return list_item_a.value == list_item_b.value return list_item_a.value == list_item_b.value
end, end,
get_root_dir = function()
return vim.loop.cwd()
end,
---@param name any ---@param name any
---@return HarpoonListItem ---@return HarpoonListItem
add = function(name) add = function(name)
name = name or vim.api.nvim_buf_get_name(vim.api.nvim_get_current_buf()) name = name or
-- TODO: should we do path normalization???
-- i know i have seen sometimes it becoming an absolute
-- path, if that is the case we can use the context to
-- store the bufname and then have value be the normalized
-- value
vim.api.nvim_buf_get_name(vim.api.nvim_get_current_buf())
local bufnr = vim.fn.bufnr(name, false) local bufnr = vim.fn.bufnr(name, false)
local pos = {1, 0} local pos = {1, 0}

View File

@ -1,3 +1,4 @@
local Ui = require("harpoon2.ui")
local Data = require("harpoon2.data") local Data = require("harpoon2.data")
local Config = require("harpoon2.config") local Config = require("harpoon2.config")
local List = require("harpoon2.list") local List = require("harpoon2.list")
@ -12,6 +13,7 @@ local DEFAULT_LIST = "__harpoon_files"
---@class Harpoon ---@class Harpoon
---@field config HarpoonConfig ---@field config HarpoonConfig
---@field ui HarpoonUI
---@field data HarpoonData ---@field data HarpoonData
---@field lists {[string]: {[string]: HarpoonList}} ---@field lists {[string]: {[string]: HarpoonList}}
---@field hooks_setup boolean ---@field hooks_setup boolean
@ -23,18 +25,24 @@ Harpoon.__index = Harpoon
function Harpoon:new() function Harpoon:new()
local config = Config.get_default_config() local config = Config.get_default_config()
return setmetatable({ local harpoon = setmetatable({
config = config, config = config,
data = Data.Data:new(), data = Data.Data:new(),
ui = Ui:new(config.settings),
lists = {}, lists = {},
hooks_setup = false, hooks_setup = false,
}, self) }, self)
return harpoon
end end
---@param partial_config HarpoonPartialConfig ---@param partial_config HarpoonPartialConfig
---@return Harpoon ---@return Harpoon
function Harpoon:setup(partial_config) function Harpoon:setup(partial_config)
self.config = Config.merge_config(partial_config, self.config) self.config = Config.merge_config(partial_config, self.config)
self.ui:configure(self.config.settings)
---TODO: should we go through every seen list and update its config?
if self.hooks_setup == false then if self.hooks_setup == false then
local augroup = vim.api.nvim_create_augroup local augroup = vim.api.nvim_create_augroup
@ -44,6 +52,7 @@ function Harpoon:setup(partial_config)
group = HarpoonGroup, group = HarpoonGroup,
pattern = '*', pattern = '*',
callback = function(ev) callback = function(ev)
--[[
self:_for_each_list(function(list, config) self:_for_each_list(function(list, config)
local fn = config[ev.event] local fn = config[ev.event]
@ -55,6 +64,7 @@ function Harpoon:setup(partial_config)
self:sync() self:sync()
end end
end) end)
--]]
end, end,
}) })
@ -129,6 +139,16 @@ function Harpoon:dump()
return self.data._data return self.data._data
end end
return Harpoon:new() function Harpoon:__debug_reset()
require("plenary.reload").reload_module("harpoon2")
end
local harpoon = Harpoon:new()
HARPOON_DEBUG_VAR = HARPOON_DEBUG_VAR or 0
if HARPOON_DEBUG_VAR == 0 then
harpoon.ui:toggle_quick_menu(harpoon:list())
HARPOON_DEBUG_VAR = 1
end
-- leave this undone, i sometimes use this for debugging
return harpoon

View File

@ -18,6 +18,7 @@ end
--- @class HarpoonList --- @class HarpoonList
--- @field config HarpoonPartialConfigItem --- @field config HarpoonPartialConfigItem
--- @field name string --- @field name string
--- @field _index number
--- @field items HarpoonItem[] --- @field items HarpoonItem[]
local HarpoonList = {} local HarpoonList = {}
@ -27,9 +28,15 @@ function HarpoonList:new(config, name, items)
items = items, items = items,
config = config, config = config,
name = name, name = name,
_index = 1,
}, self) }, self)
end end
---@return number
function HarpoonList:length()
return #self.items
end
---@return HarpoonList ---@return HarpoonList
function HarpoonList:append(item) function HarpoonList:append(item)
item = item or self.config.add() item = item or self.config.add()
@ -113,6 +120,24 @@ function HarpoonList:select(index, options)
end end
end end
function HarpoonList:next()
self._index = self._index + 1
if self._index > #self.items then
self._index = 1
end
self:select(self._index)
end
function HarpoonList:prev()
self._index = self._index - 1
if self._index < 1 then
self._index = #self.items
end
self:select(self._index)
end
--- @return string[] --- @return string[]
function HarpoonList:display() function HarpoonList:display()
local out = {} local out = {}

View File

@ -0,0 +1,18 @@
local utils = require("harpoon2.test.utils")
local eq = assert.are.same
describe("harpoon", function()
before_each(utils.before_each)
it("open the ui without any items in the list", function()
local harpoon = require("harpoon2")
harpoon.ui:toggle_quick_menu(harpoon:list())
-- no test, just wanted it to run without error'ing
end)
end)

View File

@ -1,8 +1,27 @@
local Data = require("harpoon2.data")
local M = {} local M = {}
M.created_files = {} M.created_files = {}
function M.before_each()
Data.set_data_path("/tmp/harpoon2.json")
Data.__dangerously_clear_data()
require("plenary.reload").reload_module("harpoon2")
Data = require("harpoon2.data")
Data.set_data_path("/tmp/harpoon2.json")
local harpoon = require("harpoon2")
M.clean_files()
harpoon:setup({
settings = {
key = function()
return "testies"
end
}
})
end
function M.clean_files() function M.clean_files()
for _, bufnr in ipairs(M.created_files) do for _, bufnr in ipairs(M.created_files) do
vim.api.nvim_buf_delete(bufnr, {force = true}) vim.api.nvim_buf_delete(bufnr, {force = true})

View File

@ -1,42 +1,57 @@
-- TODO: This is just the UI from the previous harpoon. local popup = require("plenary").popup
-- it needs to be cleaned up and converted to the new api local Buffer = require("harpoon2.buffer")
local harpoon = require("harpoon") local DEFAULT_WINDOW_WIDTH = 69 -- nice
local popup = require("plenary.popup")
local Marked = require("harpoon.mark")
local utils = require("harpoon.utils")
local log = require("harpoon.dev").log
local M = {} ---@class HarpoonUI
---@field win_id number
---@field bufnr number
---@field settings HarpoonSettings
---@field active_list HarpoonList
local HarpoonUI = {}
Harpoon_win_id = nil HarpoonUI.__index = HarpoonUI
Harpoon_bufh = nil
-- We save before we close because we use the state of the buffer as the list ---@param settings HarpoonSettings
-- of items. ---@return HarpoonUI
local function close_menu(force_save) function HarpoonUI:new(settings)
force_save = force_save or false return setmetatable({
local global_config = harpoon.get_global_settings() win_id = nil,
bufnr = nil,
if global_config.save_on_toggle or force_save then active_list = nil,
require("harpoon.ui").on_menu_save() settings = settings,
}, self)
end end
vim.api.nvim_win_close(Harpoon_win_id, true) function HarpoonUI:close_menu()
print("CLOSING MENU")
Harpoon_win_id = nil if self.win_id ~= nil and vim.api.nvim_win_is_valid(self.win_id) then
Harpoon_bufh = nil vim.api.nvim_win_close(self.win_id, true)
end end
local function create_window() if self.bufnr ~= nil and vim.api.nvim_buf_is_valid(self.bufnr) then
log.trace("_create_window()") vim.api.nvim_buf_delete(self.bufnr, { force = true })
local config = harpoon.get_menu_config() end
local width = config.width or 60
local height = config.height or 10 self.active_list = nil
local borderchars = config.borderchars self.win_id = nil
or { "", "", "", "", "", "", "", "" } self.bufnr = nil
end
---@return number,number
function HarpoonUI:_create_window()
local win = vim.api.nvim_list_uis()
local width = DEFAULT_WINDOW_WIDTH
if #win > 0 then
-- no ackshual reason for 0.62569, just looks complicated, and i want
-- to make my boss think i am smart
width = math.floor(win[1].width * 0.62569)
end
local height = 8
local borderchars = { "", "", "", "", "", "", "", "" }
local bufnr = vim.api.nvim_create_buf(false, false) local bufnr = vim.api.nvim_create_buf(false, false)
local win_id, _ = popup.create(bufnr, {
local Harpoon_win_id, win = popup.create(bufnr, {
title = "Harpoon", title = "Harpoon",
highlight = "HarpoonWindow", highlight = "HarpoonWindow",
line = math.floor(((vim.o.lines - height) / 2) - 1), line = math.floor(((vim.o.lines - height) / 2) - 1),
@ -45,180 +60,62 @@ local function create_window()
minheight = height, minheight = height,
borderchars = borderchars, borderchars = borderchars,
}) })
Buffer.setup_autocmds_and_keymaps(bufnr)
self.win_id = win_id
vim.api.nvim_win_set_option(self.win_id, "number", true)
vim.api.nvim_win_set_option( vim.api.nvim_win_set_option(
win.border.win_id, win_id,
"winhl", "winhl",
"Normal:HarpoonBorder" "Normal:HarpoonBorder"
) )
return { return win_id, bufnr
bufnr = bufnr,
win_id = Harpoon_win_id,
}
end end
local function get_menu_items() local count = 0
log.trace("_get_menu_items()")
local lines = vim.api.nvim_buf_get_lines(Harpoon_bufh, 0, -1, true)
local indices = {}
for _, line in pairs(lines) do ---@param list HarpoonList
if not utils.is_white_space(line) then function HarpoonUI:toggle_quick_menu(list)
table.insert(indices, line)
end
end
return indices count = count + 1
end print("toggle?", self.win_id, self.bufnr, count)
function M.toggle_quick_menu() if list == nil or self.win_id ~= nil then
log.trace("toggle_quick_menu()") self:close_menu()
if Harpoon_win_id ~= nil and vim.api.nvim_win_is_valid(Harpoon_win_id) then
close_menu()
return return
end end
local curr_file = utils.normalize_path(vim.api.nvim_buf_get_name(0)) local win_id, bufnr = self:_create_window()
vim.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("\\", "\\\\")
)
)
local win_info = create_window() print("_create_window_results", win_id, bufnr, count)
local contents = {} self.win_id = win_id
local global_config = harpoon.get_global_settings() self.bufnr = bufnr
self.active_list = list
Harpoon_win_id = win_info.win_id local contents = self.active_list:display()
Harpoon_bufh = win_info.bufnr vim.api.nvim_buf_set_lines(self.bufnr, 0, -1, false, contents)
for idx = 1, Marked.get_length() do
local file = Marked.get_marked_file_name(idx)
if file == "" then
file = "(empty)"
end
contents[idx] = string.format("%s", file)
end end
vim.api.nvim_win_set_option(Harpoon_win_id, "number", true) function HarpoonUI:select_menu_item()
vim.api.nvim_buf_set_name(Harpoon_bufh, "harpoon-menu") error("select_menu_item...?")
vim.api.nvim_buf_set_lines(Harpoon_bufh, 0, #contents, false, contents)
vim.api.nvim_buf_set_option(Harpoon_bufh, "filetype", "harpoon")
vim.api.nvim_buf_set_option(Harpoon_bufh, "buftype", "acwrite")
vim.api.nvim_buf_set_option(Harpoon_bufh, "bufhidden", "delete")
vim.api.nvim_buf_set_keymap(
Harpoon_bufh,
"n",
"q",
"<Cmd>lua require('harpoon.ui').toggle_quick_menu()<CR>",
{ silent = true }
)
vim.api.nvim_buf_set_keymap(
Harpoon_bufh,
"n",
"<ESC>",
"<Cmd>lua require('harpoon.ui').toggle_quick_menu()<CR>",
{ silent = true }
)
vim.api.nvim_buf_set_keymap(
Harpoon_bufh,
"n",
"<CR>",
"<Cmd>lua require('harpoon.ui').select_menu_item()<CR>",
{}
)
vim.cmd(
string.format(
"autocmd BufWriteCmd <buffer=%s> lua require('harpoon.ui').on_menu_save()",
Harpoon_bufh
)
)
if global_config.save_on_change then
vim.cmd(
string.format(
"autocmd TextChanged,TextChangedI <buffer=%s> lua require('harpoon.ui').on_menu_save()",
Harpoon_bufh
)
)
end
vim.cmd(
string.format(
"autocmd BufModifiedSet <buffer=%s> set nomodified",
Harpoon_bufh
)
)
vim.cmd(
"autocmd BufLeave <buffer> ++nested ++once silent lua require('harpoon.ui').toggle_quick_menu()"
)
end
function M.select_menu_item()
local idx = vim.fn.line(".") local idx = vim.fn.line(".")
close_menu(true) self.active_list:select(idx)
M.nav_file(idx) self:close_menu()
end end
function M.on_menu_save() function HarpoonUI:on_menu_save()
log.trace("on_menu_save()") error("saving...?")
Marked.set_mark_list(get_menu_items()) local list = Buffer.get_contents(self.bufnr)
self.active_list:resolve_displayed(list)
end end
local function get_or_create_buffer(filename) ---@param settings HarpoonSettings
local buf_exists = vim.fn.bufexists(filename) ~= 0 function HarpoonUI:configure(settings)
if buf_exists then self.settings = settings
return vim.fn.bufnr(filename)
end
return vim.fn.bufadd(filename)
end
function M.nav_file(id)
log.trace("nav_file(): Navigating to", id)
local idx = Marked.get_index_of(id)
if not Marked.valid_index(idx) then
log.debug("nav_file(): No mark exists for id", id)
return
end
local mark = Marked.get_marked_file(idx)
local filename = vim.fs.normalize(mark.filename)
local buf_id = get_or_create_buffer(filename)
local set_row = not vim.api.nvim_buf_is_loaded(buf_id)
local old_bufnr = vim.api.nvim_get_current_buf()
vim.api.nvim_set_current_buf(buf_id)
vim.api.nvim_buf_set_option(buf_id, "buflisted", true)
if set_row and mark.row and mark.col then
vim.cmd(string.format(":call cursor(%d, %d)", mark.row, mark.col))
log.debug(
string.format(
"nav_file(): Setting cursor to row: %d, col: %d",
mark.row,
mark.col
)
)
end
local old_bufinfo = vim.fn.getbufinfo(old_bufnr)
if type(old_bufinfo) == "table" and #old_bufinfo >= 1 then
old_bufinfo = old_bufinfo[1]
local no_name = old_bufinfo.name == ""
local one_line = old_bufinfo.linecount == 1
local unchanged = old_bufinfo.changed == 0
if no_name and one_line and unchanged then
vim.api.nvim_buf_delete(old_bufnr, {})
end
end
end end
--[[
function M.location_window(options) function M.location_window(options)
local default_options = { local default_options = {
relative = "editor", relative = "editor",
@ -239,6 +136,7 @@ function M.location_window(options)
} }
end end
-- TODO: What is this used for?
function M.notification(text) function M.notification(text)
local win_stats = vim.api.nvim_list_uis()[1] local win_stats = vim.api.nvim_list_uis()[1]
local win_width = win_stats.width local win_width = win_stats.width
@ -270,41 +168,6 @@ end
function M.close_notification(bufnr) function M.close_notification(bufnr)
vim.api.nvim_buf_delete(bufnr) vim.api.nvim_buf_delete(bufnr)
end end
--]]
function M.nav_next() return HarpoonUI
log.trace("nav_next()")
local current_index = Marked.get_current_index()
local number_of_items = Marked.get_length()
if current_index == nil then
current_index = 1
else
current_index = current_index + 1
end
if current_index > number_of_items then
current_index = 1
end
M.nav_file(current_index)
end
function M.nav_prev()
log.trace("nav_prev()")
local current_index = Marked.get_current_index()
local number_of_items = Marked.get_length()
if current_index == nil then
current_index = number_of_items
else
current_index = current_index - 1
end
if current_index < 1 then
current_index = number_of_items
end
M.nav_file(current_index)
end
return M

12
lua/harpoon2/utils.lua Normal file
View File

@ -0,0 +1,12 @@
local Path = require("plenary.path")
local M = {}
function M.normalize_path(item)
return Path:new(item):make_relative(M.project_key())
end
function M.is_white_space(str)
return str:gsub("%s", "") == ""
end
return M