diff --git a/plugins/default.nix b/plugins/default.nix index ed3c2468..8641b8f4 100644 --- a/plugins/default.nix +++ b/plugins/default.nix @@ -92,6 +92,7 @@ ./utils/indent-blankline.nix ./utils/intellitab.nix ./utils/lastplace.nix + ./utils/leap.nix ./utils/mark-radar.nix ./utils/mini.nix ./utils/neorg.nix diff --git a/plugins/utils/leap.nix b/plugins/utils/leap.nix new file mode 100644 index 00000000..7b51781d --- /dev/null +++ b/plugins/utils/leap.nix @@ -0,0 +1,195 @@ +{ + pkgs, + config, + lib, + ... +}: +with lib; let + cfg = config.plugins.leap; + helpers = import ../helpers.nix {inherit lib;}; +in { + options.plugins.leap = + helpers.extraOptionsOptions + // { + enable = mkEnableOption "leap.nvim"; + + package = helpers.mkPackageOption "leap.nvim" pkgs.vimPlugins.leap-nvim; + + addDefaultMappings = mkOption { + type = types.bool; + default = true; + description = "Whether to enable the default mappings."; + }; + + maxPhaseOneTargets = helpers.mkNullOrOption types.int '' + By default, the plugin shows labels and/or highlights matches right after the first input + character. + This option disables ahead-of-time displaying of target beacons beyond a certain number of + phase one targets (to mitigate visual noise in extreme cases). + Setting it to 0 disables two-phase processing altogether. + ''; + + highlightUnlabeledPhaseOneTargets = helpers.defaultNullOpts.mkBool false '' + Whether to highlight unlabeled (i.e., directly reachable) matches after the first input + character. + ''; + + maxHighlightedTraversalTargets = helpers.defaultNullOpts.mkInt 10 '' + Number of targets to be highlighted after the cursor in `|leap-traversal|` mode (when there + are no labels at all). + ''; + + caseSensitive = helpers.defaultNullOpts.mkBool false '' + Whether to consider case in search patterns. + ''; + + equivalenceClasses = + helpers.defaultNullOpts.mkNullable + ( + with types; + listOf + ( + either + str + (listOf str) + ) + ) + ''[" \t\r\n"]'' + '' + A character will match any other in its equivalence class. The sets can + either be defined as strings or tables. + + Example: + ``` + [ + "\r\n" + ")]}>" + "([{<" + [ "\"" "'" "`" ] + ] + ``` + + Note: Make sure to have a set containing `\n` if you want to be able to + target characters at the end of the line. + + Note: Non-mutual aliases are not possible in Leap, for the same reason + that supporting |smartcase| is not possible: we would need to show two + different labels, corresponding to two different futures, at the same + time. + ''; + + substituteChars = helpers.defaultNullOpts.mkNullable (with types; attrsOf str) "{}" '' + The keys in this attrs will be substituted in labels and highlighted matches by the given + characters. + This way special (e.g. whitespace) characters can be made visible in matches, or even be + used as labels. + + Example: `{"\r" = "¬";}` + ''; + + safeLabels = + helpers.defaultNullOpts.mkNullable + (with types; listOf str) + ''["s" "f" "n" "u" "t" "/" "S" "F" "N" "L" "H" "M" "U" "G" "T" "?" "Z"]'' + '' + When the number of matches does not exceed the number of these "safe" labels plus one, the + plugin jumps to the first match automatically after entering the pattern. + Obviously, for this purpose you should choose keys that are unlikely to be used right + after a jump! + + Setting the list to `[]` effectively disables the autojump feature. + + Note: Operator-pending mode ignores this, since we need to be able to select the actual + target before executing the operation. + ''; + + labels = + helpers.defaultNullOpts.mkNullable + (with types; listOf str) + '' + [ + "s" "f" "n" "j" "k" "l" "h" "o" "d" "w" "e" "m" "b" "u" "y" "v" "r" "g" "t" "c" "x" "/" + "z" "S" "F" "N" "J" "K" "L" "H" "O" "D" "W" "E" "M" "B" "U" "Y" "V" "R" "G" "T" "C" "X" + "?" "Z" + ] + '' + '' + Target labels to be used when there are more matches than labels in + `|leap.opts.safe_labels|` plus one. + + Setting the list to `[]` forces autojump to always be on (except for Operator-pending + mode, where it makes no sense). + In this case, do not forget to set `special_keys.next_group` to something "safe" too. + ''; + + specialKeys = { + nextTarget = helpers.defaultNullOpts.mkStr "" '' + Key captured by the plugin at runtime to jump to the next match in traversal mode + (`|leap-traversal|`) + ''; + + prevTarget = helpers.defaultNullOpts.mkStr "" '' + Key captured by the plugin at runtime to jump to the previous match in traversal mode + (`|leap-traversal|`) + ''; + + nextGroup = helpers.defaultNullOpts.mkStr "" '' + Key captured by the plugin at runtime to switch to the next group of matches, when there + are more matches than available labels. + ''; + + prevGroup = helpers.defaultNullOpts.mkStr "" '' + Key captured by the plugin at runtime to switch to the previous group of matches, when + there are more matches than available labels. + ''; + + multiAccept = helpers.defaultNullOpts.mkStr "" '' + Key captured by the plugin at runtime to accept the selection in `|leap-multiselect|` + mode. + ''; + + multiRevert = helpers.defaultNullOpts.mkStr "" '' + Key captured by the plugin at runtime to deselect the last selected target in + `|leap-multiselect|` mode. + ''; + }; + }; + + config = let + options = with cfg; + { + max_phase_one_targets = maxPhaseOneTargets; + highlight_unlabeled_phase_one_targets = highlightUnlabeledPhaseOneTargets; + max_highlighted_traversal_targets = maxHighlightedTraversalTargets; + case_sensitive = caseSensitive; + equivalence_classes = equivalenceClasses; + substitute_chars = substituteChars; + safe_labels = safeLabels; + inherit labels; + special_keys = with specialKeys; { + next_target = nextTarget; + prev_target = prevTarget; + next_group = nextGroup; + prev_group = prevGroup; + multi_accept = multiAccept; + multi_revert = multiRevert; + }; + } + // cfg.extraOptions; + in + mkIf cfg.enable { + extraPlugins = [cfg.package]; + + extraConfigLua = + (optionalString cfg.addDefaultMappings '' + require('leap').add_default_mappings() + '') + + (optionalString (options != {}) '' + require('leap').opts = vim.tbl_deep_extend( + "keep", + ${helpers.toLuaObject options}, + require('leap').opts + ) + ''); + }; +} diff --git a/tests/test-sources/plugins/utils/leap.nix b/tests/test-sources/plugins/utils/leap.nix new file mode 100644 index 00000000..069cd34e --- /dev/null +++ b/tests/test-sources/plugins/utils/leap.nix @@ -0,0 +1,29 @@ +{ + empty = { + plugins.leap.enable = true; + }; + + example = { + plugins.leap = { + enable = true; + + addDefaultMappings = true; + maxPhaseOneTargets = 10; + highlightUnlabeledPhaseOneTargets = false; + maxHighlightedTraversalTargets = 10; + caseSensitive = false; + equivalenceClasses = [" \t\r\n"]; + substituteChars = {"\r" = "¬";}; + safeLabels = ["s" "f" "n" "u" "t" "/" "S" "F" "N" "L" "H" "M" "U" "G" "T" "?" "Z"]; + labels = ["s" "f" "n" "j" "k" "l" "h" "o" "d" "w" "e" "m" "b" "u" "y" "v" "r" "g" "t" "c" "x" "/" "z" "S" "F" "N" "J" "K" "L" "H" "O" "D" "W" "E" "M" "B" "U" "Y" "V" "R" "G" "T" "C" "X" "?" "Z"]; + specialKeys = { + nextTarget = ""; + prevTarget = ""; + nextGroup = ""; + prevGroup = ""; + multiAccept = ""; + multiRevert = ""; + }; + }; + }; +}