diff --git a/README.md b/README.md index 2d91ff2..3fb6069 100644 --- a/README.md +++ b/README.md @@ -78,29 +78,29 @@ require("lazy").setup({ ## 🔌 Plugin Spec -| Property | Type | Description | -| ---------------- | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `[1]` | `string?` | Short plugin url. Will be expanded using `config.git.url_format` | -| **dir** | `string?` | A directory pointing to a local plugin | -| **url** | `string?` | A custom git url where the plugin is hosted | -| **name** | `string?` | A custom name for the plugin used for the local plugin directory and as the display name | -| **dev** | `boolean?` | When `true`, a local plugin directory will be used instead. See `config.dev` | -| **lazy** | `boolean?` | When `true`, the plugin will only be loaded when needed. Lazy-loaded plugins are automatically loaded when their lua modules are `required`, or when one of the laz-loading handlers triggers | -| **enabled** | `boolean?` or `fun():boolean` | When `false`, or if the `function` returns false, then this plugin will not be used | -| **dependencies** | `LazySpec[]` | A list of plugin specs that should be loaded when the plugin loads. Dependencies are always lazy-loaded unless specified otherwise | -| **init** | `fun(LazyPlugin)` | `init` functions are always executed during startup | -| **config** | `fun(LazyPlugin)` | `config` is executed when the plugin loads | -| **build** | `fun(LazyPlugin)` | `build` is executed when a plugin is installed or updated | -| **branch** | `string?` | Branch of the repository | -| **tag** | `string?` | Tag of the repository | -| **commit** | `string?` | Commit of the repository | -| **version** | `string?` | Version to use from the repository. Full [Semver](https://devhints.io/semver) ranges are supported | -| **pin** | `boolean?` | When `true`, this plugin will not be included in updates | -| **event** | `string?` or `string[]` | Lazy-load on event | -| **cmd** | `string?` or `string[]` | Lazy-load on command | -| **ft** | `string?` or `string[]` | Lazy-load on filetype | -| **keys** | `string?` or `string[]` | Lazy-load on key mapping | -| **module** | `false?` | Do not automatically load this lua module when it's required somewhere | +| Property | Type | Description | +| ---------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `[1]` | `string?` | Short plugin url. Will be expanded using `config.git.url_format` | +| **dir** | `string?` | A directory pointing to a local plugin | +| **url** | `string?` | A custom git url where the plugin is hosted | +| **name** | `string?` | A custom name for the plugin used for the local plugin directory and as the display name | +| **dev** | `boolean?` | When `true`, a local plugin directory will be used instead. See `config.dev` | +| **lazy** | `boolean?` | When `true`, the plugin will only be loaded when needed. Lazy-loaded plugins are automatically loaded when their lua modules are `required`, or when one of the laz-loading handlers triggers | +| **enabled** | `boolean?` or `fun():boolean` | When `false`, or if the `function` returns false, then this plugin will not be used | +| **dependencies** | `LazySpec[]` | A list of plugin specs that should be loaded when the plugin loads. Dependencies are always lazy-loaded unless specified otherwise | +| **init** | `fun(LazyPlugin)` | `init` functions are always executed during startup | +| **config** | `fun(LazyPlugin)` | `config` is executed when the plugin loads | +| **build** | `fun(LazyPlugin)` | `build` is executed when a plugin is installed or updated | +| **branch** | `string?` | Branch of the repository | +| **tag** | `string?` | Tag of the repository | +| **commit** | `string?` | Commit of the repository | +| **version** | `string?` | Version to use from the repository. Full [Semver](https://devhints.io/semver) ranges are supported | +| **pin** | `boolean?` | When `true`, this plugin will not be included in updates | +| **event** | `string?` or `string[]` | Lazy-load on event | +| **cmd** | `string?` or `string[]` | Lazy-load on command | +| **ft** | `string?` or `string[]` | Lazy-load on filetype | +| **keys** | `string?` or `string[]` or `LazyKeys[]` | Lazy-load on key mapping | +| **module** | `false?` | Do not automatically load this lua module when it's required somewhere | ### Lazy Loading @@ -124,6 +124,33 @@ Plugins will be lazy-loaded when one of the following is `true`: - it defines an `init` method - `config.defaults.lazy == true` +#### ⌨️ Lazy Key Mappings + +The `keys` property can be a `string` or `string[]` for simple normal-mode mappings, or it +can be a `LazyKeys` table with the following key-value pairs: + +- **[1]**: (`string`) lhs **_(required)_** +- **[2]**: (`string|fun()`) rhs **_(optional)_** +- **mode**: (`string|string[]`) mode **_(optional, defaults to `"n"`)_** +- any other option valid for `vim.keymap.set` + +Key mappings will load the plugin the first time they get executed. + +When `[2]` is `nil`, then the real mapping has to be created by the `config()` function. + +```lua +-- Example for neo-tree.nvim +{ + "nvim-neo-tree/neo-tree.nvim", + keys = { + { "ft", "Neotree toggle", desc = "NeoTree" }, + }, + config = function() + require("neo-tree").setup() + end, +} +``` + ### Versioning If you want to install a specific revision of a plugin, you can use `commit`, diff --git a/lua/lazy/core/handler/keys.lua b/lua/lazy/core/handler/keys.lua index e6bf6f2..e65120d 100644 --- a/lua/lazy/core/handler/keys.lua +++ b/lua/lazy/core/handler/keys.lua @@ -1,32 +1,66 @@ local Util = require("lazy.core.util") local Loader = require("lazy.core.loader") +---@class LazyKeys +---@field [1] string lhs +---@field [2]? string|fun() rhs +---@field desc? string +---@field mode? string|string[] +---@field noremap? boolean +---@field remap? boolean +---@field expr? boolean + ---@class LazyKeysHandler:LazyHandler local M = {} ----@param keys string -function M:_add(keys) - vim.keymap.set("n", keys, function() - vim.keymap.del("n", keys) - Util.track({ keys = keys }) - Loader.load(self.active[keys], { keys = keys }) - local extra = "" - while true do - local c = vim.fn.getchar(0) - if c == 0 then - break - end - extra = extra .. vim.fn.nr2char(c) +function M.retrigger(keys) + local pending = "" + while true do + local c = vim.fn.getchar(0) + if c == 0 then + break end - local feed = vim.api.nvim_replace_termcodes(keys .. extra, true, true, true) - vim.api.nvim_feedkeys(feed, "m", false) - Util.track() - end, { silent = true }) + pending = pending .. vim.fn.nr2char(c) + end + local feed = vim.api.nvim_replace_termcodes(keys .. pending, true, true, true) + vim.api.nvim_feedkeys(feed, "m", false) end ----@param keys string -function M:_del(keys) - pcall(vim.keymap.del, "n", keys) +---@param value string|LazyKeys +function M.parse(value) + return (type(value) == "string" and { value } or value) --[[@as LazyKeys]] +end + +function M.opts(keys) + local opts = {} + for k, v in pairs(keys) do + if type(k) ~= "number" and k ~= "mode" then + opts[k] = v + end + end + return opts +end + +---@param value string|LazyKeys +function M:_add(value) + local keys = M.parse(value) + local lhs = keys[1] + vim.keymap.set(keys.mode or "n", lhs, function() + Util.track({ keys = lhs }) + self:_del(value) + Loader.load(self.active[value], { keys = lhs }) + M.retrigger(lhs) + Util.track() + end, M.opts(keys)) +end + +---@param value string|LazyKeys +function M:_del(value) + local keys = M.parse(value) + pcall(vim.keymap.del, "n", keys[1]) + if keys[2] then + vim.keymap.set(keys.mode or "n", keys[1], keys[2], M.opts(keys)) + end end return M diff --git a/lua/lazy/util.lua b/lua/lazy/util.lua index fdff2ad..0dbe496 100644 --- a/lua/lazy/util.lua +++ b/lua/lazy/util.lua @@ -156,7 +156,7 @@ end function M.foreach(t, fn) ---@type string[] local keys = vim.tbl_keys(t) - table.sort(keys) + pcall(table.sort, keys) for _, key in ipairs(keys) do fn(key, t[key]) end diff --git a/lua/lazy/view/render.lua b/lua/lazy/view/render.lua index 66e7ca3..d0a9800 100644 --- a/lua/lazy/view/render.lua +++ b/lua/lazy/view/render.lua @@ -272,6 +272,9 @@ function M:reason(reason, opts) if key == "event" then value = value:match("User (.*)") or value end + if key == "keys" then + value = type(value) == "string" and value or value[1] + end local hl = "LazyHandler" .. key:sub(1, 1):upper() .. key:sub(2) local icon = Config.options.ui.icons[key] if icon then @@ -493,13 +496,14 @@ function M:debug() ) :nl() - Util.foreach(require("lazy.core.handler").handlers, function(type, handler) + Util.foreach(require("lazy.core.handler").handlers, function(handler_type, handler) Util.foreach(handler.active, function(value, plugins) + value = type(value) == "table" and value[1] or value if not vim.tbl_isempty(plugins) then plugins = vim.tbl_values(plugins) table.sort(plugins) self:append("● ", "LazySpecial", { indent = 2 }) - self:reason({ [type] = value }) + self:reason({ [handler_type] = value }) for _, plugin in pairs(plugins) do self:append(" ") self:reason({ plugin = plugin })