From 86508ced91043711ecfcd7a97e1d4bb0fb375765 Mon Sep 17 00:00:00 2001 From: Gaetan Lepage Date: Tue, 19 Dec 2023 15:01:46 +0100 Subject: [PATCH] plugins/yanky: init --- plugins/default.nix | 1 + plugins/utils/yanky.nix | 257 +++++++++++++++++++++ tests/test-sources/plugins/utils/yanky.nix | 58 +++++ 3 files changed, 316 insertions(+) create mode 100644 plugins/utils/yanky.nix create mode 100644 tests/test-sources/plugins/utils/yanky.nix diff --git a/plugins/default.nix b/plugins/default.nix index 7b39fa35..3c561985 100644 --- a/plugins/default.nix +++ b/plugins/default.nix @@ -156,6 +156,7 @@ ./utils/vim-matchup.nix ./utils/which-key.nix ./utils/wilder.nix + ./utils/yanky.nix ./utils/zk.nix ]; } diff --git a/plugins/utils/yanky.nix b/plugins/utils/yanky.nix new file mode 100644 index 00000000..3d477ab9 --- /dev/null +++ b/plugins/utils/yanky.nix @@ -0,0 +1,257 @@ +{ + lib, + helpers, + config, + pkgs, + ... +}: +with lib; let + cfg = config.plugins.yanky; +in { + options.plugins.yanky = + helpers.extraOptionsOptions + // { + enable = mkEnableOption "yanky.nvim"; + + package = helpers.mkPackageOption "yanky.nvim" pkgs.vimPlugins.yanky-nvim; + + ring = { + historyLength = helpers.defaultNullOpts.mkUnsignedInt 100 '' + Define the number of yanked items that will be saved and used for ring. + ''; + + storage = helpers.defaultNullOpts.mkEnumFirstDefault ["shada" "sqlite" "memory"] '' + Define the storage mode for ring values. + + Using `shada`, this will save pesistantly using Neovim ShaDa feature. + This means that history will be persisted between each session of Neovim. + + You can also use this feature to sync the yank history across multiple running instances + of Neovim by updating shada file. + If you execute `:wshada` in the first instance and then `:rshada` in the second instance, + the second instance will be synced with the yank history in the first instance. + + Using `memory`, each Neovim instance will have his own history and il will be lost between + sessions. + + Sqlite is more reliable than ShaDa but requires more dependencies. + You can change the storage path using `ring.storagePath` option. + ''; + + storagePath = + helpers.defaultNullOpts.mkNullable + (with types; either str helpers.rawType) + ''{__raw = "vim.fn.stdpath('data') .. '/databases/yanky.db'";}'' + "Only for sqlite storage."; + + syncWithNumberedRegisters = helpers.defaultNullOpts.mkBool true '' + History can also be synchronized with numbered registers. + Every time the yank history changes the numbered registers 1 - 9 will be updated to sync + with the first 9 entries in the yank history. + See [here](http://vimcasts.org/blog/2013/11/registers-the-good-the-bad-and-the-ugly-parts/) for an explanation of why we would want do do this. + ''; + + cancelEvent = helpers.defaultNullOpts.mkEnumFirstDefault ["update" "move"] '' + Define the event used to cancel ring activation. + `update` will cancel ring on next buffer update, `move` will cancel ring when moving + cursor or content changed. + ''; + + ignoreRegisters = helpers.defaultNullOpts.mkNullable (with types; listOf str) ''["_"]'' '' + Define registeres to be ignored. + By default the black hole register is ignored. + ''; + + updateRegisterOnCycle = helpers.defaultNullOpts.mkBool false '' + If true, when you cycle through the ring, the contents of the register used to update will + be updated with the last content cycled. + ''; + }; + + picker = { + select = { + action = helpers.mkNullOrOption (with types; either helpers.rawType str) '' + This define the action that should be done when selecting an item in the + `vim.ui.select` prompt. + If you let this option to `null`, this will use the default action: put selected item + after cursor. + + This is either: + - a `rawType` value (`action.__raw = "function() foo end";`). + - a string. In this case, Nixvim will automatically interpret it as a builtin yanky + action. + Example: `action = "put('p')";` will translate to + `action = require('yanky.picker').actions.put('p')` in lua. + ''; + }; + + telescope = { + enable = mkEnableOption "the `yank_history` telescope picker."; + + useDefaultMappings = helpers.defaultNullOpts.mkBool true '' + This define if default Telescope mappings should be used. + ''; + + mappings = helpers.mkNullOrOption (with types; attrsOf (attrsOf str)) '' + This define or overrides the mappings available in Telescope. + + If you set `useDefaultMappings` to `true`, mappings will be merged with default + mappings. + + Example: + ```nix + { + i = { + "" = "put('p')"; + "" = "put('P')"; + "" = "delete()"; + "" = "set_register(require('yanky.utils').get_default_register())"; + }; + n = { + p = "put('p')"; + P = "put('P')"; + d = "delete()"; + r = "set_register(require('yanky.utils').get_default_register())"; + }; + } + ``` + ''; + }; + }; + + systemClipboard = { + syncWithRing = helpers.defaultNullOpts.mkBool true '' + Yanky can automatically adds to ring history yanks that occurs outside of Neovim. + This works regardless to your `&clipboard` setting. + + This means, if `&clipboard` is set to `unnamed` and/or `unnamedplus`, if you yank + something outside of Neovim, you can put it immediatly using `p` and it will be added to + your yank ring. + + If `&clipboard` is empty, if you yank something outside of Neovim, this will be the first + value you'll have when cycling through the ring. + Basicly, you can do `p` and then `` to paste yanked text. + ''; + }; + + highlight = { + onPut = helpers.defaultNullOpts.mkBool true '' + Define if highlight put text feature is enabled. + ''; + + onYank = helpers.defaultNullOpts.mkBool true '' + Define if highlight yanked text feature is enabled. + ''; + + timer = helpers.defaultNullOpts.mkUnsignedInt 500 '' + Define the duration of highlight. + ''; + }; + + preserveCursorPosition = { + enabled = helpers.defaultNullOpts.mkBool true '' + Whether cursor position should be preserved on yank. + This works only if mappings has been defined. + ''; + }; + + textobj = { + enabled = helpers.defaultNullOpts.mkBool true '' + Yanky comes with a text object corresponding to last put text. + To use it, you have to enable it and set a keymap. + ''; + }; + }; + + config = let + thereAreSomeTelescopeMappings = + (cfg.picker.telescope.mappings != null) + && (cfg.picker.telescope.mappings != {}); + in + mkIf cfg.enable { + assertions = [ + { + assertion = thereAreSomeTelescopeMappings -> cfg.picker.telescope.enable; + message = '' + Nixvim: You have defined some mappings in `plugins.yanky.picker.telescope.mappings` but + you have not set `plugins.yanky.picker.telescope.enable`. + ''; + } + ]; + + plugins.telescope.enable = true; + plugins.telescope.enabledExtensions = optional cfg.picker.telescope.enable "yank_history"; + + extraPlugins = + [ + cfg.package + ] + ++ ( + optional (cfg.ring.storage == "sqlite") pkgs.vimPlugins.sqlite-lua + ); + + extraConfigLua = let + setupOptions = with cfg; + { + ring = { + history_length = ring.historyLength; + inherit (ring) storage; + storage_path = ring.storagePath; + sync_with_numbered_registers = ring.syncWithNumberedRegisters; + cancel_event = ring.cancelEvent; + ignore_registers = ring.ignoreRegisters; + update_register_on_cycle = ring.updateRegisterOnCycle; + }; + picker = { + select = { + action = + if isString picker.select.action + then helpers.mkRaw "require('yanky.picker').actions.${picker.select.action}" + else picker.select.action; + }; + telescope = { + use_default_mappings = picker.telescope.useDefaultMappings; + mappings = + helpers.ifNonNull' picker.telescope.mappings + ( + mapAttrs + ( + mode: mappings: + mapAttrs + ( + key: action: + helpers.mkRaw "__yanky_telescope_mapping.${action}" + ) + mappings + ) + picker.telescope.mappings + ); + }; + system_clipboard = { + sync_with_ring = systemClipboard.syncWithRing; + }; + highlight = { + on_put = highlight.onPut; + on_yank = highlight.onYank; + inherit (highlight) timer; + }; + preserve_cursor_position = { + inherit (preserveCursorPosition) enabled; + }; + textobj = { + inherit (textobj) enabled; + }; + }; + } + // cfg.extraOptions; + in + ( + optionalString + thereAreSomeTelescopeMappings + "local __yanky_telescope_mapping = require('yanky.telescope.mapping')" + ) + + '' + require('yanky').setup(${helpers.toLuaObject setupOptions}) + ''; + }; +} diff --git a/tests/test-sources/plugins/utils/yanky.nix b/tests/test-sources/plugins/utils/yanky.nix new file mode 100644 index 00000000..f07c24c4 --- /dev/null +++ b/tests/test-sources/plugins/utils/yanky.nix @@ -0,0 +1,58 @@ +{ + empty = { + plugins.yanky.enable = true; + }; + + example = { + plugins.yanky = { + enable = true; + + ring = { + historyLength = 100; + storage = "sqlite"; + storagePath.__raw = "vim.fn.stdpath('data') .. '/databases/yanky.db'"; + syncWithNumberedRegisters = true; + cancelEvent = "update"; + ignoreRegisters = ["_"]; + updateRegisterOnCycle = false; + }; + picker = { + select = { + action = "put('p')"; + }; + telescope = { + enable = true; + useDefaultMappings = true; + mappings = { + i = { + "" = "put('p')"; + "" = "put('P')"; + "" = "delete()"; + "" = "set_register(require('yanky.utils').get_default_register())"; + }; + n = { + p = "put('p')"; + P = "put('P')"; + d = "delete()"; + r = "set_register(require('yanky.utils').get_default_register())"; + }; + }; + }; + }; + systemClipboard = { + syncWithRing = true; + }; + highlight = { + onPut = true; + onYank = true; + timer = 500; + }; + preserveCursorPosition = { + enabled = true; + }; + textobj = { + enabled = true; + }; + }; + }; +}