plugins.harpoon: refactor & switch to harpoon2

This commit is contained in:
Gaetan Lepage 2025-04-03 18:54:15 +02:00 committed by Gaétan Lepage
parent af76696a92
commit 9f495dda93
3 changed files with 172 additions and 316 deletions

View file

@ -1,264 +1,41 @@
{
lib,
helpers,
config,
pkgs,
...
}:
with lib;
let
cfg = config.plugins.harpoon;
projectConfigModule = types.submodule {
options = {
termCommands = helpers.mkNullOrOption (with types; listOf str) ''
List of predefined terminal commands for this project.
'';
marks = helpers.mkNullOrOption (with types; listOf str) ''
List of predefined marks (filenames) for this project.
'';
};
};
inherit (lib) mkEnableOption;
in
{
options.plugins.harpoon = lib.nixvim.plugins.neovim.extraOptionsOptions // {
enable = mkEnableOption "harpoon";
lib.nixvim.plugins.mkNeovimPlugin {
name = "harpoon";
package = "harpoon2";
package = lib.mkPackageOption pkgs "harpoon" {
default = [
"vimPlugins"
"harpoon"
maintainers = [ lib.maintainers.GaetanLepage ];
setup = ":setup";
# TODO: introduced 2025-04-03: remove after 25.11
imports = [
./deprecations.nix
];
};
extraOptions = {
enableTelescope = mkEnableOption "telescope integration";
keymapsSilent = mkOption {
type = types.bool;
description = "Whether harpoon keymaps should be silent.";
default = false;
};
keymaps = {
addFile = helpers.mkNullOrOption types.str ''
Keymap for marking the current file.";
'';
toggleQuickMenu = helpers.mkNullOrOption types.str ''
Keymap for toggling the quick menu.";
'';
navFile = helpers.mkNullOrOption (with types; attrsOf str) ''
Keymaps for navigating to marks.
Examples:
navFile = {
"1" = "<C-j>";
"2" = "<C-k>";
"3" = "<C-l>";
"4" = "<C-m>";
};
'';
navNext = helpers.mkNullOrOption types.str ''
Keymap for navigating to next mark.";
'';
navPrev = helpers.mkNullOrOption types.str ''
Keymap for navigating to previous mark.";
'';
gotoTerminal = helpers.mkNullOrOption (with types; attrsOf str) ''
Keymaps for navigating to terminals.
Examples:
gotoTerminal = {
"1" = "<C-j>";
"2" = "<C-k>";
"3" = "<C-l>";
"4" = "<C-m>";
};
'';
cmdToggleQuickMenu = helpers.mkNullOrOption types.str ''
Keymap for toggling the cmd quick menu.
'';
tmuxGotoTerminal = helpers.mkNullOrOption (with types; attrsOf str) ''
Keymaps for navigating to tmux windows/panes.
Attributes can either be tmux window ids or pane identifiers.
Examples:
tmuxGotoTerminal = {
"1" = "<C-1>";
"2" = "<C-2>";
"{down-of}" = "<leader>g";
};
'';
};
saveOnToggle = helpers.defaultNullOpts.mkBool false ''
Sets the marks upon calling `toggle` on the ui, instead of require `:w`.
'';
saveOnChange = helpers.defaultNullOpts.mkBool true ''
Saves the harpoon file upon every change. disabling is unrecommended.
'';
enterOnSendcmd = helpers.defaultNullOpts.mkBool false ''
Sets harpoon to run the command immediately as it's passed to the terminal when calling `sendCommand`.
'';
tmuxAutocloseWindows = helpers.defaultNullOpts.mkBool false ''
Closes any tmux windows harpoon that harpoon creates when you close Neovim.
'';
excludedFiletypes = helpers.defaultNullOpts.mkListOf types.str [ "harpoon" ] ''
Filetypes that you want to prevent from adding to the harpoon list menu.
'';
markBranch = helpers.defaultNullOpts.mkBool false ''
Set marks specific to each git branch inside git repository.
'';
projects = mkOption {
default = { };
description = ''
Predefined projetcs. The keys of this attrs should be the path to the project.
$HOME is working.
'';
example = ''
projects = {
"$HOME/personal/vim-with-me/server" = {
termCommands = [
"./env && npx ts-node src/index.ts"
];
};
};
'';
type = types.attrsOf projectConfigModule;
};
menu = {
width = helpers.defaultNullOpts.mkInt 60 ''
Menu window width
'';
height = helpers.defaultNullOpts.mkInt 10 ''
Menu window height
'';
borderChars = helpers.defaultNullOpts.mkListOf types.str [
""
""
""
""
""
""
""
""
] "Border characters";
settingsExample = {
settings = {
save_on_toggle = true;
sync_on_ui_close = false;
};
};
config =
let
projects = builtins.mapAttrs (name: value: {
term.cmds = value.termCommands;
mark.marks = helpers.ifNonNull' value.marks (map (mark: { filename = mark; }) value.marks);
}) cfg.projects;
setupOptions =
with cfg;
{
global_settings = {
save_on_toggle = saveOnToggle;
save_on_change = saveOnChange;
enter_on_sendcmd = enterOnSendcmd;
tmux_autoclose_windows = tmuxAutocloseWindows;
excluded_filetypes = excludedFiletypes;
mark_branch = markBranch;
};
inherit projects;
menu = {
inherit (menu) width height;
borderchars = menu.borderChars;
};
}
// cfg.extraOptions;
in
mkIf cfg.enable {
assertions = lib.nixvim.mkAssertions "plugins.harpoon" [
{
extraConfig = cfg: {
assertions = lib.nixvim.mkAssertions "plugins.harpoon" {
assertion = cfg.enableTelescope -> config.plugins.telescope.enable;
message = "The harpoon telescope integration needs telescope to function as intended.";
}
];
};
extraPlugins = [ cfg.package ];
extraConfigLua =
let
telescopeCfg = ''require("telescope").load_extension("harpoon")'';
in
''
require('harpoon').setup(${lib.nixvim.toLuaObject setupOptions})
${if cfg.enableTelescope then telescopeCfg else ""}
'';
keymaps =
let
km = cfg.keymaps;
simpleMappings = flatten (
mapAttrsToList
(
optionName: luaFunc:
let
key = km.${optionName};
in
optional (key != null) {
inherit key;
action.__raw = luaFunc;
}
)
{
addFile = "require('harpoon.mark').add_file";
toggleQuickMenu = "require('harpoon.ui').toggle_quick_menu";
navNext = "require('harpoon.ui').nav_next";
navPrev = "require('harpoon.ui').nav_prev";
cmdToggleQuickMenu = "require('harpoon.cmd-ui').toggle_quick_menu";
}
);
mkNavMappings =
name: genLuaFunc:
let
mappingsAttrs = km.${name};
in
flatten (
optionals (mappingsAttrs != null) (
mapAttrsToList (id: key: {
inherit key;
action.__raw = genLuaFunc id;
}) mappingsAttrs
)
);
allMappings =
simpleMappings
++ (mkNavMappings "navFile" (id: "function() require('harpoon.ui').nav_file(${id}) end"))
++ (mkNavMappings "gotoTerminal" (id: "function() require('harpoon.term').gotoTerminal(${id}) end"))
++ (mkNavMappings "tmuxGotoTerminal" (
id: "function() require('harpoon.tmux').gotoTerminal(${id}) end"
));
in
helpers.keymaps.mkKeymaps {
mode = "n";
options.silent = cfg.keymapsSilent;
} allMappings;
plugins.telescope.enabledExtensions = lib.mkIf cfg.enableTelescope [ "harpoon" ];
};
}

View file

@ -0,0 +1,71 @@
{ lib, ... }:
{
imports =
let
basePluginPath = [
"plugins"
"harpoon"
];
commonWarning = ''
/!\ `plugins.harpoon` has been refactored to now use harpoon2 (https://github.com/ThePrimeagen/harpoon/tree/harpoon2).
'';
keymapsWarning = ''
${commonWarning}
The `plugins.harpoon` module no longer allows you to define your keymaps.
Please, manually define your keymaps using the top-level `keymaps` option.
For example,
```
plugins.harpoon.keymaps = {
addFile = "<leader>a";
toggleQuickMenu = "<C-e>";
navFile = {
"1" = "<C-j>";
"2" = "<C-k>";
"3" = "<C-l>";
"4" = "<C-m>";
};
};
```
would become:
```
keymaps = [
{ mode = "n"; key = "<leader>a"; action.__raw = "function() require'harpoon':list():add() end"; }
{ mode = "n"; key = "<C-e>"; action.__raw = "function() require'harpoon'.ui:toggle_quick_menu(require'harpoon':list()) end"; }
{ mode = "n"; key = "<C-j>"; action.__raw = "function() require'harpoon':list():select(1) end"; }
{ mode = "n"; key = "<C-k>"; action.__raw = "function() require'harpoon':list():select(2) end"; }
{ mode = "n"; key = "<C-l>"; action.__raw = "function() require'harpoon':list():select(3) end"; }
{ mode = "n"; key = "<C-m>"; action.__raw = "function() require'harpoon':list():select(4) end"; }
];
```
'';
optionNames = [
"saveOnToggle"
"saveOnChange"
"enterOnSendcmd"
"tmuxAutocloseWindows"
"excludedFiletypes"
"markBranch"
"projects"
"menu"
];
in
(map (
optionName:
lib.mkRemovedOptionModule (basePluginPath ++ [ optionName ]) ''
${commonWarning}
You may now use `plugins.harpoon.settings` option to forward any value to the `require("harpoon"):setup()` call.
''
) optionNames)
++ [
(lib.mkRemovedOptionModule (basePluginPath ++ [ "keymaps" ]) keymapsWarning)
(lib.mkRemovedOptionModule (basePluginPath ++ [ "keymapsSilent" ]) keymapsWarning)
];
}

View file

@ -1,88 +1,96 @@
{
empty = {
# Harpoon expects to access `~/.local/share/nvim/harpoon.json` which is not available in the
# test environment
test.runNvim = false;
plugins.harpoon.enable = true;
};
telescopeEnabled = {
# Harpoon expects to access `~/.local/share/nvim/harpoon.json` which is not available in the
# test environment
test.runNvim = false;
plugins.telescope = {
defaults = {
plugins.harpoon = {
enable = true;
# https://github.com/ThePrimeagen/harpoon/blob/harpoon2/lua/harpoon/config.lua
settings = {
settings = {
save_on_toggle = false;
sync_on_ui_close = false;
key.__raw = ''
function()
return vim.loop.cwd()
end
'';
};
default = {
select_with_nil = false;
encode.__raw = ''
function(obj)
return vim.json.encode(obj)
end
'';
decode.__raw = ''
function(str)
return vim.json.decode(str)
end
'';
display.__raw = ''
function(list_item)
return list_item.value
end
'';
# Very long functions omitted for the sake of conciseness
};
};
};
};
example = {
plugins.harpoon = {
enable = true;
settings = {
settings = {
save_on_toggle = true;
sync_on_ui_close = false;
};
# https://github.com/ThePrimeagen/harpoon/tree/harpoon2?tab=readme-ov-file#-api
cmd = {
add.__raw = ''
function(possible_value)
-- get the current line idx
local idx = vim.fn.line(".")
-- read the current line
local cmd = vim.api.nvim_buf_get_lines(0, idx - 1, idx, false)[1]
if cmd == nil then
return nil
end
return {
value = cmd,
context = { },
}
end
'';
select.__raw = ''
function(list_item, list, option)
vim.cmd(list_item.value)
end
'';
};
};
};
};
telescopeEnabled = {
plugins = {
telescope.enable = true;
web-devicons.enable = true;
harpoon = {
enable = true;
enableTelescope = true;
keymapsSilent = true;
keymaps = {
addFile = "<leader>a";
navFile = {
"1" = "<C-j>";
"2" = "<C-k>";
"3" = "<C-l>";
"4" = "<C-m>";
};
navNext = "<leader>b";
navPrev = "<leader>c";
gotoTerminal = {
"1" = "J";
"2" = "K";
"3" = "L";
"4" = "M";
};
cmdToggleQuickMenu = "<leader>d";
tmuxGotoTerminal = {
"1" = "<C-1>";
"2" = "<C-2>";
"{down-of}" = "<leader>g";
};
};
saveOnToggle = false;
saveOnChange = true;
enterOnSendcmd = false;
tmuxAutocloseWindows = false;
excludedFiletypes = [ "harpoon" ];
markBranch = false;
projects = {
"$HOME/personal/vim-with-me/server" = {
termCommands = [ "./env && npx ts-node src/index.ts" ];
};
};
menu = {
width = 60;
height = 10;
borderChars = [
""
""
""
""
""
""
""
""
];
};
};
plugins.web-devicons.enable = true;
};
telescopeDisabled = {
# Harpoon expects to access `~/.local/share/nvim/harpoon.json` which is not available in the
# test environment
test.runNvim = false;
plugins.harpoon = {
enable = true;
enableTelescope = false;
};
};
}