modules/keymaps: refactor + new syntax

This commit is contained in:
Gaetan Lepage 2023-09-10 09:59:22 +02:00 committed by Gaétan Lepage
parent 382973b627
commit 574fb73258
6 changed files with 716 additions and 521 deletions

View file

@ -321,18 +321,23 @@ If you are using `makeNixvimWithModule`, then options is treated as options for
## Key mappings
It is fully possible to define key mappings from within NixVim. This is done
using the `maps` attribute:
using the `keymaps` attribute:
```nix
{
programs.nixvim = {
maps = {
normalVisualOp.";" = ":";
normal."<leader>m" = {
silent = true;
action = "<cmd>make<CR>";
keymaps = [
{
key = ";";
action = ":";
}
{
mode = "n";
key = "<leader>m";
options.silent = true;
action = "<cmd>!make<CR>";
};
};
];
};
}
```
@ -344,34 +349,35 @@ noremap ; :
nnoremap <leader>m <silent> <cmd>make<CR>
```
This table describes all modes for the `maps` option:
This table describes all modes for the `keymaps` option.
You can provide several mode to a single mapping by using a list of strings.
| NixVim | NeoVim |
|----------------|--------------------------------------------------|
| normal | Normal mode |
| insert | Insert mode |
| visual | Visual and Select mode |
| select | Select mode |
| terminal | Terminal mode |
| normalVisualOp | Normal, visual, select and operator-pending mode |
| visualOnly | Visual mode only, without select |
| operator | Operator-pending mode |
| insertCommand | Insert and command-line mode |
| lang | Insert, command-line and lang-arg mode |
| command | Command-line mode |
| Short | Description |
|-------|--------------------------------------------------|
| `"n"` | Normal mode |
| `"i"` | Insert mode |
| `"v"` | Visual and Select mode |
| `"s"` | Select mode |
| `"t"` | Terminal mode |
| `"" ` | Normal, visual, select and operator-pending mode |
| `"x"` | Visual mode only, without select |
| `"o"` | Operator-pending mode |
| `"!"` | Insert and command-line mode |
| `"l"` | Insert, command-line and lang-arg mode |
| `"c"` | Command-line mode |
The map options can be set to either a string, containing just the action,
or to a set describing additional options:
Each keymap can specify the following settings in the `options` attrs.
| NixVim | Default | VimScript |
|---------|---------|------------------------------------------|
| silent | false | `<silent>` |
| nowait | false | `<silent>` |
| script | false | `<script>` |
| expr | false | `<expr>` |
| unique | false | `<unique>` |
| noremap | true | Use the 'noremap' variant of the mapping |
| action | N/A | Action to execute |
| NixVim | Default | VimScript |
|---------|---------|---------------------------------------------------|
| silent | false | `<silent>` |
| nowait | false | `<silent>` |
| script | false | `<script>` |
| expr | false | `<expr>` |
| unique | false | `<unique>` |
| noremap | true | Use the 'noremap' variant of the mapping |
| remap | false | Make the mapping recursive (inverses `noremap`) |
| desc | "" | A description of this keymap |
## Globals
Sometimes you might want to define a global variable, for example to set the

View file

@ -5,74 +5,81 @@
# when compared to just installing NeoVim.
enable = true;
maps.normal = {
keymaps = [
# Equivalent to nnoremap ; :
";" = ":";
{
key = ";";
action = ":";
}
# Equivalent to nmap <silent> <buffer> <leader>gg <cmd>Man<CR>
"<leader>gg" = {
silent = true;
remap = false;
{
key = "<leader>gg";
action = "<cmd>Man<CR>";
# Etc...
};
options = {
silent = true;
remap = false;
};
}
# Etc...
];
# We can set the leader key:
leader = ",";
# We can set the leader key:
leader = ",";
# We can create maps for every mode!
# There is .normal, .insert, .visual, .operator, etc!
# We can create maps for every mode!
# There is .normal, .insert, .visual, .operator, etc!
# We can also set options:
options = {
tabstop = 4;
shiftwidth = 4;
expandtab = false;
# We can also set options:
options = {
tabstop = 4;
shiftwidth = 4;
expandtab = false;
mouse = "a";
mouse = "a";
# etc...
};
# etc...
};
# Of course, we can still use comfy vimscript:
extraConfigVim = builtins.readFile ./init.vim;
# Or lua!
extraConfigLua = builtins.readFile ./init.lua;
# Of course, we can still use comfy vimscript:
extraConfigVim = builtins.readFile ./init.vim;
# Or lua!
extraConfigLua = builtins.readFile ./init.lua;
# One of the big advantages of NixVim is how it provides modules for
# popular vim plugins
# Enabling a plugin this way skips all the boring configuration that
# some plugins tend to require.
plugins = {
lightline = {
enable = true;
# One of the big advantages of NixVim is how it provides modules for
# popular vim plugins
# Enabling a plugin this way skips all the boring configuration that
# some plugins tend to require.
plugins = {
lightline = {
enable = true;
# This is optional - it will default to your enabled colorscheme
colorscheme = "wombat";
# This is optional - it will default to your enabled colorscheme
colorscheme = "wombat";
# This is one of lightline's example configurations
active = {
left = [
["mode" "paste"]
["redaonly" "filename" "modified" "helloworld"]
];
};
component = {
helloworld = "Hello, world!";
};
# This is one of lightline's example configurations
active = {
left = [
["mode" "paste"]
["redaonly" "filename" "modified" "helloworld"]
];
};
# Of course, there are a lot more plugins available.
# You can find an up-to-date list here:
# https://nixvim.pta2002.com/plugins
component = {
helloworld = "Hello, world!";
};
};
# There is a separate namespace for colorschemes:
colorschemes.gruvbox.enable = true;
# What about plugins not available as a module?
# Use extraPlugins:
extraPlugins = with pkgs.vimPlugins; [vim-toml];
# Of course, there are a lot more plugins available.
# You can find an up-to-date list here:
# https://nixvim.pta2002.com/plugins
};
# There is a separate namespace for colorschemes:
colorschemes.gruvbox.enable = true;
# What about plugins not available as a module?
# Use extraPlugins:
extraPlugins = with pkgs.vimPlugins; [vim-toml];
};
}

View file

@ -1,330 +1,286 @@
{lib, ...}:
with lib; rec {
# vim dictionaries are, in theory, compatible with JSON
toVimDict = args:
toJSON
(lib.filterAttrs (n: v: v != null) args);
with lib;
(import ./keymap-helpers.nix {inherit lib;})
// rec {
# vim dictionaries are, in theory, compatible with JSON
toVimDict = args:
toJSON
(lib.filterAttrs (n: v: v != null) args);
# Black functional magic that converts a bunch of different Nix types to their
# lua equivalents!
toLuaObject = args:
if builtins.isAttrs args
then
if hasAttr "__raw" args
then args.__raw
else if hasAttr "__empty" args
then "{ }"
else
"{"
+ (concatStringsSep ","
(mapAttrsToList
(n: v:
if head (stringToCharacters n) == "@"
then toLuaObject v
else if n == "__emptyString"
then "[''] = " + (toLuaObject v)
else "[${toLuaObject n}] = " + (toLuaObject v))
(filterAttrs
(
n: v:
v != null && (toLuaObject v != "{}")
)
args)))
+ "}"
else if builtins.isList args
then "{" + concatMapStringsSep "," toLuaObject args + "}"
else if builtins.isString args
then
# This should be enough!
builtins.toJSON args
else if builtins.isPath args
then builtins.toJSON (toString args)
else if builtins.isBool args
then "${boolToString args}"
else if builtins.isFloat args
then "${toString args}"
else if builtins.isInt args
then "${toString args}"
else if (args == null)
then "nil"
else "";
# Black functional magic that converts a bunch of different Nix types to their
# lua equivalents!
toLuaObject = args:
if builtins.isAttrs args
then
if hasAttr "__raw" args
then args.__raw
else if hasAttr "__empty" args
then "{ }"
else
"{"
+ (concatStringsSep ","
(mapAttrsToList
(n: v:
if head (stringToCharacters n) == "@"
then toLuaObject v
else if n == "__emptyString"
then "[''] = " + (toLuaObject v)
else "[${toLuaObject n}] = " + (toLuaObject v))
(filterAttrs
(
n: v:
v != null && (toLuaObject v != "{}")
)
args)))
+ "}"
else if builtins.isList args
then "{" + concatMapStringsSep "," toLuaObject args + "}"
else if builtins.isString args
then
# This should be enough!
builtins.toJSON args
else if builtins.isPath args
then builtins.toJSON (toString args)
else if builtins.isBool args
then "${boolToString args}"
else if builtins.isFloat args
then "${toString args}"
else if builtins.isInt args
then "${toString args}"
else if (args == null)
then "nil"
else "";
emptyTable = {"__empty" = null;};
emptyTable = {"__empty" = null;};
highlightType = with lib.types;
submodule {
# Adds flexibility for other keys
freeformType = types.attrs;
highlightType = with lib.types;
submodule {
# Adds flexibility for other keys
freeformType = types.attrs;
# :help nvim_set_hl()
options = {
fg = mkNullOrOption str "Color for the foreground (color name or '#RRGGBB').";
bg = mkNullOrOption str "Color for the background (color name or '#RRGGBB').";
sp = mkNullOrOption str "Special color (color name or '#RRGGBB').";
blend = mkNullOrOption (numbers.between 0 100) "Integer between 0 and 100.";
bold = mkNullOrOption bool "";
standout = mkNullOrOption bool "";
underline = mkNullOrOption bool "";
undercurl = mkNullOrOption bool "";
underdouble = mkNullOrOption bool "";
underdotted = mkNullOrOption bool "";
underdashed = mkNullOrOption bool "";
strikethrough = mkNullOrOption bool "";
italic = mkNullOrOption bool "";
reverse = mkNullOrOption bool "";
nocombine = mkNullOrOption bool "";
link = mkNullOrOption str "Name of another highlight group to link to.";
default = mkNullOrOption bool "Don't override existing definition.";
ctermfg = mkNullOrOption str "Sets foreground of cterm color.";
ctermbg = mkNullOrOption str "Sets background of cterm color.";
cterm = mkNullOrOption attrs ''
cterm attribute map, like |highlight-args|.
If not set, cterm attributes will match those from the attribute map documented above.
'';
# :help nvim_set_hl()
options = {
fg = mkNullOrOption str "Color for the foreground (color name or '#RRGGBB').";
bg = mkNullOrOption str "Color for the background (color name or '#RRGGBB').";
sp = mkNullOrOption str "Special color (color name or '#RRGGBB').";
blend = mkNullOrOption (numbers.between 0 100) "Integer between 0 and 100.";
bold = mkNullOrOption bool "";
standout = mkNullOrOption bool "";
underline = mkNullOrOption bool "";
undercurl = mkNullOrOption bool "";
underdouble = mkNullOrOption bool "";
underdotted = mkNullOrOption bool "";
underdashed = mkNullOrOption bool "";
strikethrough = mkNullOrOption bool "";
italic = mkNullOrOption bool "";
reverse = mkNullOrOption bool "";
nocombine = mkNullOrOption bool "";
link = mkNullOrOption str "Name of another highlight group to link to.";
default = mkNullOrOption bool "Don't override existing definition.";
ctermfg = mkNullOrOption str "Sets foreground of cterm color.";
ctermbg = mkNullOrOption str "Sets background of cterm color.";
cterm = mkNullOrOption attrs ''
cterm attribute map, like |highlight-args|.
If not set, cterm attributes will match those from the attribute map documented above.
'';
};
};
};
# Given an attrs of key mappings (for a single mode), applies the defaults to each one of them.
#
# Example:
# mkModeMaps { silent = true; } {
# Y = "y$";
# "<C-c>" = { action = ":b#<CR>"; silent = false; };
# };
#
# would give:
# {
# Y = {
# action = "y$";
# silent = true;
# };
# "<C-c>" = {
# action = ":b#<CR>";
# silent = false;
# };
# };
mkModeMaps = defaults:
mapAttrs
(
shortcut: action: let
actionAttrs =
if isString action
then {inherit action;}
else action;
in
defaults // actionAttrs
);
# Creates an option with a nullable type that defaults to null.
mkNullOrOption = type: desc:
lib.mkOption {
type = lib.types.nullOr type;
default = null;
description = desc;
};
# Applies some default mapping options to a set of mappings
#
# Example:
# maps = mkMaps { silent = true; expr = true; } {
# normal = {
# ...
# };
# visual = {
# ...
# };
# }
mkMaps = defaults:
mapAttrs
(name: modeMaps: (mkModeMaps defaults modeMaps));
mkIfNonNull' = x: y: (mkIf (x != null) y);
# Creates an option with a nullable type that defaults to null.
mkNullOrOption = type: desc:
lib.mkOption {
type = lib.types.nullOr type;
default = null;
description = desc;
};
mkIfNonNull = x: (mkIfNonNull' x x);
mkIfNonNull' = x: y: (mkIf (x != null) y);
ifNonNull' = x: y:
if (x == null)
then null
else y;
mkIfNonNull = x: (mkIfNonNull' x x);
mkCompositeOption = desc: options:
mkNullOrOption (types.submodule {inherit options;}) desc;
ifNonNull' = x: y:
if (x == null)
then null
else y;
defaultNullOpts = rec {
mkNullable = type: default: desc:
mkNullOrOption type (
let
defaultDesc = "default: `${default}`";
in
if desc == ""
then defaultDesc
else ''
${desc}
mkCompositeOption = desc: options:
mkNullOrOption (types.submodule {inherit options;}) desc;
${defaultDesc}
''
);
defaultNullOpts = rec {
mkNullable = type: default: desc:
mkNullOrOption type (
let
defaultDesc = "default: `${default}`";
mkNum = default: mkNullable lib.types.number (toString default);
mkInt = default: mkNullable lib.types.int (toString default);
# Positive: >0
mkPositiveInt = default: mkNullable lib.types.ints.positive (toString default);
# Unsigned: >=0
mkUnsignedInt = default: mkNullable lib.types.ints.unsigned (toString default);
mkBool = default:
mkNullable lib.types.bool (
if default
then "true"
else "false"
);
mkStr = default: mkNullable lib.types.str ''${builtins.toString default}'';
mkAttributeSet = default: mkNullable lib.types.attrs ''${default}'';
mkEnum = enum: default: mkNullable (lib.types.enum enum) ''"${default}"'';
mkEnumFirstDefault = enum: mkEnum enum (head enum);
mkBorder = default: name: desc:
mkNullable
(
with lib.types;
oneOf [
str
(listOf str)
(listOf (listOf str))
]
)
default
(let
defaultDesc = ''
Defines the border to use for ${name}.
Accepts same border values as `nvim_open_win()`. See `:help nvim_open_win()` for more info.
'';
in
if desc == ""
then defaultDesc
else ''
${desc}
${defaultDesc}
''
);
'');
mkNum = default: mkNullable lib.types.number (toString default);
mkInt = default: mkNullable lib.types.int (toString default);
# Positive: >0
mkPositiveInt = default: mkNullable lib.types.ints.positive (toString default);
# Unsigned: >=0
mkUnsignedInt = default: mkNullable lib.types.ints.unsigned (toString default);
mkBool = default:
mkNullable lib.types.bool (
if default
then "true"
else "false"
);
mkStr = default: mkNullable lib.types.str ''${builtins.toString default}'';
mkAttributeSet = default: mkNullable lib.types.attrs ''${default}'';
mkEnum = enum: default: mkNullable (lib.types.enum enum) ''"${default}"'';
mkEnumFirstDefault = enum: mkEnum enum (head enum);
mkBorder = default: name: desc:
mkNullable
(
with lib.types;
oneOf [
str
(listOf str)
(listOf (listOf str))
]
)
default
(let
defaultDesc = ''
Defines the border to use for ${name}.
Accepts same border values as `nvim_open_win()`. See `:help nvim_open_win()` for more info.
'';
in
if desc == ""
then defaultDesc
else ''
${desc}
${defaultDesc}
'');
mkHighlight = default: name: desc:
mkNullable
highlightType
default
(
if desc == ""
then "Highlight settings."
else desc
);
};
mkPackageOption = name: default:
mkOption {
type = types.package;
inherit default;
description = "Plugin to use for ${name}";
mkHighlight = default: name: desc:
mkNullable
highlightType
default
(
if desc == ""
then "Highlight settings."
else desc
);
};
mkPlugin = {
config,
lib,
...
}: {
name,
description,
package ? null,
extraPlugins ? [],
extraPackages ? [],
options ? {},
globalPrefix ? "",
...
}: let
cfg = config.plugins.${name};
# TODO support nested options!
pluginOptions = mapAttrs (k: v: v.option) options;
globals =
mapAttrs'
(name: opt: {
name = globalPrefix + opt.global;
value =
if cfg.${name} != null
then opt.value cfg.${name}
else null;
})
options;
# does this evaluate package?
packageOption =
if package == null
then {}
else {
package = mkPackageOption name package;
mkPackageOption = name: default:
mkOption {
type = types.package;
inherit default;
description = "Plugin to use for ${name}";
};
in {
options.plugins.${name} =
{
enable = mkEnableOption description;
}
// packageOption
// pluginOptions;
config = mkIf cfg.enable {
inherit extraPackages globals;
# does this evaluate package? it would not be desired to evaluate pacakge if we use another package.
extraPlugins = extraPlugins ++ optional (package != null) cfg.package;
};
};
mkPlugin = {
config,
lib,
...
}: {
name,
description,
package ? null,
extraPlugins ? [],
extraPackages ? [],
options ? {},
globalPrefix ? "",
...
}: let
cfg = config.plugins.${name};
# TODO support nested options!
pluginOptions = mapAttrs (k: v: v.option) options;
globals =
mapAttrs'
(name: opt: {
name = globalPrefix + opt.global;
value =
if cfg.${name} != null
then opt.value cfg.${name}
else null;
})
options;
# does this evaluate package?
packageOption =
if package == null
then {}
else {
package = mkPackageOption name package;
};
in {
options.plugins.${name} =
{
enable = mkEnableOption description;
}
// packageOption
// pluginOptions;
globalVal = val:
if builtins.isBool val
then
(
if !val
then 0
else 1
)
else val;
mkDefaultOpt = {
type,
global,
description ? null,
example ? null,
default ? null,
value ? v: (globalVal v),
...
}: {
option = mkOption {
type = types.nullOr type;
inherit default description example;
config = mkIf cfg.enable {
inherit extraPackages globals;
# does this evaluate package? it would not be desired to evaluate pacakge if we use another package.
extraPlugins = extraPlugins ++ optional (package != null) cfg.package;
};
};
inherit value global;
};
globalVal = val:
if builtins.isBool val
then
(
if !val
then 0
else 1
)
else val;
extraOptionsOptions = {
extraOptions = mkOption {
default = {};
type = types.attrs;
description = ''
These attributes will be added to the table parameter for the setup function.
(Can override other attributes set by nixvim)
'';
mkDefaultOpt = {
type,
global,
description ? null,
example ? null,
default ? null,
value ? v: (globalVal v),
...
}: {
option = mkOption {
type = types.nullOr type;
inherit default description example;
};
inherit value global;
};
};
mkRaw = r: {__raw = r;};
extraOptionsOptions = {
extraOptions = mkOption {
default = {};
type = types.attrs;
description = ''
These attributes will be added to the table parameter for the setup function.
(Can override other attributes set by nixvim)
'';
};
};
wrapDo = string: ''
do
${string}
end
'';
mkRaw = r: {__raw = r;};
rawType = mkOptionType {
name = "rawType";
description = "raw lua code";
descriptionClass = "noun";
merge = mergeEqualOption;
check = isRawType;
};
wrapDo = string: ''
do
${string}
end
'';
isRawType = v: lib.isAttrs v && lib.hasAttr "__raw" v && lib.isString v.__raw;
}
rawType = mkOptionType {
name = "rawType";
description = "raw lua code";
descriptionClass = "noun";
merge = mergeEqualOption;
check = isRawType;
};
isRawType = v: lib.isAttrs v && lib.hasAttr "__raw" v && lib.isString v.__raw;
}

64
lib/keymap-helpers.nix Normal file
View file

@ -0,0 +1,64 @@
{lib, ...}:
with lib; rec {
# Correctly merge two attrs (partially) representing a mapping.
mergeKeymap = defaults: keymap: let
# First, merge the `options` attrs of both options.
mergedOpts = (defaults.options or {}) // (keymap.options or {});
in
# Then, merge the root attrs together and add the previously merged `options` attrs.
(defaults // keymap) // {options = mergedOpts;};
# Given an attrs of key mappings (for a single mode), applies the defaults to each one of them.
#
# Example:
# mkModeMaps { silent = true; } {
# Y = "y$";
# "<C-c>" = { action = ":b#<CR>"; silent = false; };
# };
#
# would give:
# {
# Y = {
# action = "y$";
# silent = true;
# };
# "<C-c>" = {
# action = ":b#<CR>";
# silent = false;
# };
# };
mkModeMaps = defaults:
mapAttrs
(
key: action: let
actionAttrs =
if isString action
then {inherit action;}
else action;
in
defaults // actionAttrs
);
# Applies some default mapping options to a set of mappings
#
# Example:
# maps = mkMaps { silent = true; expr = true; } {
# normal = {
# ...
# };
# visual = {
# ...
# };
# }
mkMaps = defaults:
mapAttrs
(
name: modeMaps:
mkModeMaps defaults modeMaps
);
# TODO deprecate `mkMaps` and `mkModeMaps` and leave only this one
mkKeymaps = defaults:
map
(mergeKeymap defaults);
}

View file

@ -41,149 +41,244 @@ with lib; let
"A textual description of this keybind, to be shown in which-key, if you have it.";
};
# Generates maps for a lua config
genMaps = mode: maps: let
/*
Take a user-defined action (string or attrs) and return the following attribute set:
{
action = (string) the actual action to map to this key
config = (attrs) the configuration options for this mapping (noremap, silent...)
}
- If the action is a string:
{
action = action;
config = {};
}
- If the action is an attrs:
{
action = action;
config = {
inherit (action) <values of the config options that have been explicitly set by the user>
};
}
*/
normalizeAction = action:
if isString action
# Case 1: action is a string
then {
inherit action;
config = helpers.emptyTable;
}
else
# Case 2: action is an attrs
let
# Extract the values of the config options that have been explicitly set by the user
config =
filterAttrs (n: v: v != null)
(getAttrs (attrNames mapConfigOptions) action);
in {
config =
if config == {}
then helpers.emptyTable
else config;
action =
if action.lua
then helpers.mkRaw action.action
else action.action;
};
in
builtins.attrValues (builtins.mapAttrs
(key: action: let
normalizedAction = normalizeAction action;
in {
inherit (normalizedAction) action config;
inherit key mode;
})
maps);
mapOption = types.oneOf [
types.str
(types.submodule {
options =
mapConfigOptions
// {
action =
if config.plugins.which-key.enable
then helpers.mkNullOrOption types.str "The action to execute"
else
mkOption {
type = types.str;
description = "The action to execute.";
};
lua = mkOption {
type = types.bool;
description = ''
If true, `action` is considered to be lua code.
Thus, it will not be wrapped in `""`.
'';
default = false;
};
};
})
];
mapOptions = mode:
mkOption {
description = "Mappings for ${mode} mode";
type = types.attrsOf mapOption;
default = {};
modes = {
normal.short = "n";
insert.short = "i";
visual = {
desc = "visual and select";
short = "v";
};
visualOnly = {
desc = "visual only";
short = "x";
};
select.short = "s";
terminal.short = "t";
normalVisualOp = {
desc = "normal, visual, select and operator-pending (same as plain 'map')";
short = "";
};
operator.short = "o";
lang = {
desc = "normal, visual, select and operator-pending (same as plain 'map')";
short = "l";
};
insertCommand = {
desc = "insert and command-line";
short = "!";
};
command.short = "c";
};
mkMapOptionSubmodule = {
defaultMode ? "",
withKeyOpt ? true,
flatConfig ? false,
}:
with types;
either
str
(types.submodule {
options =
(
if withKeyOpt
then {
key = mkOption {
type = types.str;
description = "The key to map.";
example = "<C-m>";
};
}
else {}
)
// {
mode = mkOption {
type = let
modeEnum =
enum
# ["" "n" "v" ...]
(
map
(
{short, ...}: short
)
(attrValues modes)
);
in
either modeEnum (listOf modeEnum);
description = ''
One or several modes.
Use the short-names (`"n"`, `"v"`, ...).
See `:h map-modes` to learn more.
'';
default = defaultMode;
example = ["n" "v"];
};
action =
if config.plugins.which-key.enable
then helpers.mkNullOrOption types.str "The action to execute"
else
mkOption {
type = types.str;
description = "The action to execute.";
};
lua = mkOption {
type = types.bool;
description = ''
If true, `action` is considered to be lua code.
Thus, it will not be wrapped in `""`.
'';
default = false;
};
}
// (
if flatConfig
then mapConfigOptions
else {
options = mapConfigOptions;
}
);
});
in {
options = {
maps = mkOption {
type = types.submodule {
options = {
normal = mapOptions "normal";
insert = mapOptions "insert";
select = mapOptions "select";
visual = mapOptions "visual and select";
terminal = mapOptions "terminal";
normalVisualOp = mapOptions "normal, visual, select and operator-pending (same as plain 'map')";
maps =
mapAttrs
(
modeName: modeProps: let
desc = modeProps.desc or modeName;
in
mkOption {
description = "Mappings for ${desc} mode";
type = with types;
attrsOf
(
either
str
(
mkMapOptionSubmodule
{
defaultMode = modeProps.short;
withKeyOpt = false;
flatConfig = true;
}
)
);
default = {};
}
)
modes;
visualOnly = mapOptions "visual only";
operator = mapOptions "operator-pending";
insertCommand = mapOptions "insert and command-line";
lang = mapOptions "insert, command-line and lang-arg";
command = mapOptions "command-line";
};
};
default = {};
description = ''
Custom keybindings for any mode.
For plain maps (e.g. just 'map' or 'remap') use maps.normalVisualOp.
'';
example = ''
maps = {
normalVisualOp.";" = ":"; # Same as noremap ; :
normal."<leader>m" = {
silent = true;
action = "<cmd>make<CR>";
}; # Same as nnoremap <leader>m <silent> <cmd>make<CR>
};
'';
keymaps = mkOption {
type = types.listOf (mkMapOptionSubmodule {});
default = [];
example = [
{
key = "<C-m>";
action = "<cmd>make<CR>";
options.silent = true;
}
];
};
};
config = let
mappings =
(genMaps "" config.maps.normalVisualOp)
++ (genMaps "n" config.maps.normal)
++ (genMaps "i" config.maps.insert)
++ (genMaps "v" config.maps.visual)
++ (genMaps "x" config.maps.visualOnly)
++ (genMaps "s" config.maps.select)
++ (genMaps "t" config.maps.terminal)
++ (genMaps "o" config.maps.operator)
++ (genMaps "l" config.maps.lang)
++ (genMaps "!" config.maps.insertCommand)
++ (genMaps "c" config.maps.command);
in {
extraConfigLua =
config = {
warnings =
optional
(
any
(modeMaps: modeMaps != {})
(attrValues config.maps)
)
''
The `maps` option will be deprecated in the near future.
Please, use the new `keymaps` option which works as follows:
keymaps = [
{
# Default mode is "" which means normal-visual-op
key = "<C-m>";
action = ":!make<CR>";
}
{
# Mode can be a string or a list of strings
mode = "n";
key = "<leader>p";
action = "require('my-plugin').do_stuff";
lua = true;
# Note that all of the mapping options are now under the `options` attrs
options = {
silent = true;
desc = "My plugin does stuff";
};
}
];
'';
extraConfigLua = let
modeMapsAsList =
flatten
(
mapAttrsToList
(
modeOptionName: modeProps:
mapAttrsToList
(
key: action:
(
if isString action
then {
mode = modeProps.short;
inherit action;
lua = false;
options = {};
}
else
{
inherit
(action)
action
lua
mode
;
}
// {
options =
getAttrs
(attrNames mapConfigOptions)
action;
}
)
// {inherit key;}
)
config.maps.${modeOptionName}
)
modes
);
mappings = let
normalizeMapping = keyMapping: {
inherit
(keyMapping)
mode
key
;
action =
if keyMapping.lua
then helpers.mkRaw keyMapping.action
else keyMapping.action;
options =
if keyMapping.options == {}
then helpers.emptyTable
else keyMapping.options;
};
in
map normalizeMapping
(config.keymaps ++ modeMapsAsList);
in
optionalString (mappings != [])
(
if config.plugins.which-key.enable
@ -193,9 +288,9 @@ in {
local __nixvim_binds = ${helpers.toLuaObject mappings}
for i, map in ipairs(__nixvim_binds) do
if not map.action then
require("which-key").register({[map.key] = {name = map.config.desc }})
require("which-key").register({[map.key] = {name = map.options.desc }})
else
vim.keymap.set(map.mode, map.key, map.action, map.config)
vim.keymap.set(map.mode, map.key, map.action, map.options)
end
end
end
@ -206,7 +301,7 @@ in {
do
local __nixvim_binds = ${helpers.toLuaObject mappings}
for i, map in ipairs(__nixvim_binds) do
vim.keymap.set(map.mode, map.key, map.action, map.config)
vim.keymap.set(map.mode, map.key, map.action, map.options)
end
end
-- }}}

View file

@ -1,5 +1,72 @@
{
example = {
{helpers, ...}: {
legacy = {
maps.normal."," = "<cmd>echo \"test\"<cr>";
};
legacy-mkMaps = {
maps = helpers.mkMaps {silent = true;} {
normal."," = "<cmd>echo \"test\"<cr>";
visual = {
"<C-a>" = {
action = "function() print('toto') end";
lua = true;
silent = false;
};
"<C-z>" = {
action = "bar";
};
};
};
};
legacy-mkModeMaps = {
maps.normal = helpers.mkModeMaps {silent = true;} {
"," = "<cmd>echo \"test\"<cr>";
"<C-a>" = {
action = "function() print('toto') end";
lua = true;
silent = false;
};
"<leader>b" = {
action = "bar";
};
};
};
example = {
keymaps = [
{
key = ",";
action = "<cmd>echo \"test\"<cr>";
mode = ["n" "s"];
};
};
};
mkMaps = {
keymaps =
helpers.mkKeymaps
{
mode = "x";
options.silent = true;
}
[
{
mode = "n";
key = ",";
action = "<cmd>echo \"test\"<cr>";
}
{
key = "<C-a>";
action = "function() print('toto') end";
lua = true;
options.silent = false;
}
{
mode = ["n" "v"];
key = "<C-z>";
action = "bar";
}
];
};
}