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 ## Key mappings
It is fully possible to define key mappings from within NixVim. This is done It is fully possible to define key mappings from within NixVim. This is done
using the `maps` attribute: using the `keymaps` attribute:
```nix ```nix
{ {
programs.nixvim = { programs.nixvim = {
maps = { keymaps = [
normalVisualOp.";" = ":"; {
normal."<leader>m" = { key = ";";
silent = true; action = ":";
action = "<cmd>make<CR>"; }
{
mode = "n";
key = "<leader>m";
options.silent = true;
action = "<cmd>!make<CR>";
}; };
}; ];
}; };
} }
``` ```
@ -344,34 +349,35 @@ noremap ; :
nnoremap <leader>m <silent> <cmd>make<CR> 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 | | Short | Description |
|----------------|--------------------------------------------------| |-------|--------------------------------------------------|
| normal | Normal mode | | `"n"` | Normal mode |
| insert | Insert mode | | `"i"` | Insert mode |
| visual | Visual and Select mode | | `"v"` | Visual and Select mode |
| select | Select mode | | `"s"` | Select mode |
| terminal | Terminal mode | | `"t"` | Terminal mode |
| normalVisualOp | Normal, visual, select and operator-pending mode | | `"" ` | Normal, visual, select and operator-pending mode |
| visualOnly | Visual mode only, without select | | `"x"` | Visual mode only, without select |
| operator | Operator-pending mode | | `"o"` | Operator-pending mode |
| insertCommand | Insert and command-line mode | | `"!"` | Insert and command-line mode |
| lang | Insert, command-line and lang-arg mode | | `"l"` | Insert, command-line and lang-arg mode |
| command | Command-line mode | | `"c"` | Command-line mode |
The map options can be set to either a string, containing just the action, Each keymap can specify the following settings in the `options` attrs.
or to a set describing additional options:
| NixVim | Default | VimScript | | NixVim | Default | VimScript |
|---------|---------|------------------------------------------| |---------|---------|---------------------------------------------------|
| silent | false | `<silent>` | | silent | false | `<silent>` |
| nowait | false | `<silent>` | | nowait | false | `<silent>` |
| script | false | `<script>` | | script | false | `<script>` |
| expr | false | `<expr>` | | expr | false | `<expr>` |
| unique | false | `<unique>` | | unique | false | `<unique>` |
| noremap | true | Use the 'noremap' variant of the mapping | | noremap | true | Use the 'noremap' variant of the mapping |
| action | N/A | Action to execute | | remap | false | Make the mapping recursive (inverses `noremap`) |
| desc | "" | A description of this keymap |
## Globals ## Globals
Sometimes you might want to define a global variable, for example to set the 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. # when compared to just installing NeoVim.
enable = true; enable = true;
maps.normal = { keymaps = [
# Equivalent to nnoremap ; : # Equivalent to nnoremap ; :
";" = ":"; {
key = ";";
action = ":";
}
# Equivalent to nmap <silent> <buffer> <leader>gg <cmd>Man<CR> # Equivalent to nmap <silent> <buffer> <leader>gg <cmd>Man<CR>
"<leader>gg" = { {
silent = true; key = "<leader>gg";
remap = false;
action = "<cmd>Man<CR>"; action = "<cmd>Man<CR>";
# Etc... options = {
}; silent = true;
remap = false;
};
}
# Etc...
];
# We can set the leader key: # We can set the leader key:
leader = ","; leader = ",";
# We can create maps for every mode! # We can create maps for every mode!
# There is .normal, .insert, .visual, .operator, etc! # There is .normal, .insert, .visual, .operator, etc!
# We can also set options: # We can also set options:
options = { options = {
tabstop = 4; tabstop = 4;
shiftwidth = 4; shiftwidth = 4;
expandtab = false; expandtab = false;
mouse = "a"; mouse = "a";
# etc... # etc...
}; };
# Of course, we can still use comfy vimscript: # Of course, we can still use comfy vimscript:
extraConfigVim = builtins.readFile ./init.vim; extraConfigVim = builtins.readFile ./init.vim;
# Or lua! # Or lua!
extraConfigLua = builtins.readFile ./init.lua; extraConfigLua = builtins.readFile ./init.lua;
# One of the big advantages of NixVim is how it provides modules for # One of the big advantages of NixVim is how it provides modules for
# popular vim plugins # popular vim plugins
# Enabling a plugin this way skips all the boring configuration that # Enabling a plugin this way skips all the boring configuration that
# some plugins tend to require. # some plugins tend to require.
plugins = { plugins = {
lightline = { lightline = {
enable = true; enable = true;
# This is optional - it will default to your enabled colorscheme # This is optional - it will default to your enabled colorscheme
colorscheme = "wombat"; colorscheme = "wombat";
# This is one of lightline's example configurations # This is one of lightline's example configurations
active = { active = {
left = [ left = [
["mode" "paste"] ["mode" "paste"]
["redaonly" "filename" "modified" "helloworld"] ["redaonly" "filename" "modified" "helloworld"]
]; ];
};
component = {
helloworld = "Hello, world!";
};
}; };
# Of course, there are a lot more plugins available. component = {
# You can find an up-to-date list here: helloworld = "Hello, world!";
# https://nixvim.pta2002.com/plugins };
}; };
# There is a separate namespace for colorschemes: # Of course, there are a lot more plugins available.
colorschemes.gruvbox.enable = true; # You can find an up-to-date list here:
# https://nixvim.pta2002.com/plugins
# What about plugins not available as a module?
# Use extraPlugins:
extraPlugins = with pkgs.vimPlugins; [vim-toml];
}; };
# 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, ...}: {lib, ...}:
with lib; rec { with lib;
# vim dictionaries are, in theory, compatible with JSON (import ./keymap-helpers.nix {inherit lib;})
toVimDict = args: // rec {
toJSON # vim dictionaries are, in theory, compatible with JSON
(lib.filterAttrs (n: v: v != null) args); toVimDict = args:
toJSON
(lib.filterAttrs (n: v: v != null) args);
# Black functional magic that converts a bunch of different Nix types to their # Black functional magic that converts a bunch of different Nix types to their
# lua equivalents! # lua equivalents!
toLuaObject = args: toLuaObject = args:
if builtins.isAttrs args if builtins.isAttrs args
then then
if hasAttr "__raw" args if hasAttr "__raw" args
then args.__raw then args.__raw
else if hasAttr "__empty" args else if hasAttr "__empty" args
then "{ }" then "{ }"
else else
"{" "{"
+ (concatStringsSep "," + (concatStringsSep ","
(mapAttrsToList (mapAttrsToList
(n: v: (n: v:
if head (stringToCharacters n) == "@" if head (stringToCharacters n) == "@"
then toLuaObject v then toLuaObject v
else if n == "__emptyString" else if n == "__emptyString"
then "[''] = " + (toLuaObject v) then "[''] = " + (toLuaObject v)
else "[${toLuaObject n}] = " + (toLuaObject v)) else "[${toLuaObject n}] = " + (toLuaObject v))
(filterAttrs (filterAttrs
( (
n: v: n: v:
v != null && (toLuaObject v != "{}") v != null && (toLuaObject v != "{}")
) )
args))) args)))
+ "}" + "}"
else if builtins.isList args else if builtins.isList args
then "{" + concatMapStringsSep "," toLuaObject args + "}" then "{" + concatMapStringsSep "," toLuaObject args + "}"
else if builtins.isString args else if builtins.isString args
then then
# This should be enough! # This should be enough!
builtins.toJSON args builtins.toJSON args
else if builtins.isPath args else if builtins.isPath args
then builtins.toJSON (toString args) then builtins.toJSON (toString args)
else if builtins.isBool args else if builtins.isBool args
then "${boolToString args}" then "${boolToString args}"
else if builtins.isFloat args else if builtins.isFloat args
then "${toString args}" then "${toString args}"
else if builtins.isInt args else if builtins.isInt args
then "${toString args}" then "${toString args}"
else if (args == null) else if (args == null)
then "nil" then "nil"
else ""; else "";
emptyTable = {"__empty" = null;}; emptyTable = {"__empty" = null;};
highlightType = with lib.types; highlightType = with lib.types;
submodule { submodule {
# Adds flexibility for other keys # Adds flexibility for other keys
freeformType = types.attrs; freeformType = types.attrs;
# :help nvim_set_hl() # :help nvim_set_hl()
options = { options = {
fg = mkNullOrOption str "Color for the foreground (color name or '#RRGGBB')."; fg = mkNullOrOption str "Color for the foreground (color name or '#RRGGBB').";
bg = mkNullOrOption str "Color for the background (color name or '#RRGGBB')."; bg = mkNullOrOption str "Color for the background (color name or '#RRGGBB').";
sp = mkNullOrOption str "Special color (color name or '#RRGGBB')."; sp = mkNullOrOption str "Special color (color name or '#RRGGBB').";
blend = mkNullOrOption (numbers.between 0 100) "Integer between 0 and 100."; blend = mkNullOrOption (numbers.between 0 100) "Integer between 0 and 100.";
bold = mkNullOrOption bool ""; bold = mkNullOrOption bool "";
standout = mkNullOrOption bool ""; standout = mkNullOrOption bool "";
underline = mkNullOrOption bool ""; underline = mkNullOrOption bool "";
undercurl = mkNullOrOption bool ""; undercurl = mkNullOrOption bool "";
underdouble = mkNullOrOption bool ""; underdouble = mkNullOrOption bool "";
underdotted = mkNullOrOption bool ""; underdotted = mkNullOrOption bool "";
underdashed = mkNullOrOption bool ""; underdashed = mkNullOrOption bool "";
strikethrough = mkNullOrOption bool ""; strikethrough = mkNullOrOption bool "";
italic = mkNullOrOption bool ""; italic = mkNullOrOption bool "";
reverse = mkNullOrOption bool ""; reverse = mkNullOrOption bool "";
nocombine = mkNullOrOption bool ""; nocombine = mkNullOrOption bool "";
link = mkNullOrOption str "Name of another highlight group to link to."; link = mkNullOrOption str "Name of another highlight group to link to.";
default = mkNullOrOption bool "Don't override existing definition."; default = mkNullOrOption bool "Don't override existing definition.";
ctermfg = mkNullOrOption str "Sets foreground of cterm color."; ctermfg = mkNullOrOption str "Sets foreground of cterm color.";
ctermbg = mkNullOrOption str "Sets background of cterm color."; ctermbg = mkNullOrOption str "Sets background of cterm color.";
cterm = mkNullOrOption attrs '' cterm = mkNullOrOption attrs ''
cterm attribute map, like |highlight-args|. cterm attribute map, like |highlight-args|.
If not set, cterm attributes will match those from the attribute map documented above. 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. # Creates an option with a nullable type that defaults to null.
# mkNullOrOption = type: desc:
# Example: lib.mkOption {
# mkModeMaps { silent = true; } { type = lib.types.nullOr type;
# Y = "y$"; default = null;
# "<C-c>" = { action = ":b#<CR>"; silent = false; }; description = desc;
# }; };
#
# 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
);
# Applies some default mapping options to a set of mappings mkIfNonNull' = x: y: (mkIf (x != null) y);
#
# Example:
# maps = mkMaps { silent = true; expr = true; } {
# normal = {
# ...
# };
# visual = {
# ...
# };
# }
mkMaps = defaults:
mapAttrs
(name: modeMaps: (mkModeMaps defaults modeMaps));
# Creates an option with a nullable type that defaults to null. mkIfNonNull = x: (mkIfNonNull' x x);
mkNullOrOption = type: desc:
lib.mkOption {
type = lib.types.nullOr type;
default = null;
description = desc;
};
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: defaultNullOpts = rec {
if (x == null) mkNullable = type: default: desc:
then null mkNullOrOption type (
else y; let
defaultDesc = "default: `${default}`";
in
if desc == ""
then defaultDesc
else ''
${desc}
mkCompositeOption = desc: options: ${defaultDesc}
mkNullOrOption (types.submodule {inherit options;}) desc; ''
);
defaultNullOpts = rec { mkNum = default: mkNullable lib.types.number (toString default);
mkNullable = type: default: desc: mkInt = default: mkNullable lib.types.int (toString default);
mkNullOrOption type ( # Positive: >0
let mkPositiveInt = default: mkNullable lib.types.ints.positive (toString default);
defaultDesc = "default: `${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 in
if desc == "" if desc == ""
then defaultDesc then defaultDesc
else '' else ''
${desc} ${desc}
${defaultDesc} ${defaultDesc}
'' '');
);
mkNum = default: mkNullable lib.types.number (toString default); mkHighlight = default: name: desc:
mkInt = default: mkNullable lib.types.int (toString default); mkNullable
# Positive: >0 highlightType
mkPositiveInt = default: mkNullable lib.types.ints.positive (toString default); default
# Unsigned: >=0 (
mkUnsignedInt = default: mkNullable lib.types.ints.unsigned (toString default); if desc == ""
mkBool = default: then "Highlight settings."
mkNullable lib.types.bool ( else desc
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}";
}; };
mkPlugin = { mkPackageOption = name: default:
config, mkOption {
lib, type = types.package;
... inherit default;
}: { description = "Plugin to use for ${name}";
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;
config = mkIf cfg.enable { mkPlugin = {
inherit extraPackages globals; config,
# does this evaluate package? it would not be desired to evaluate pacakge if we use another package. lib,
extraPlugins = extraPlugins ++ optional (package != null) cfg.package; ...
}; }: {
}; 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: config = mkIf cfg.enable {
if builtins.isBool val inherit extraPackages globals;
then # does this evaluate package? it would not be desired to evaluate pacakge if we use another package.
( extraPlugins = extraPlugins ++ optional (package != null) cfg.package;
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;
}; };
inherit value global; globalVal = val:
}; if builtins.isBool val
then
(
if !val
then 0
else 1
)
else val;
extraOptionsOptions = { mkDefaultOpt = {
extraOptions = mkOption { type,
default = {}; global,
type = types.attrs; description ? null,
description = '' example ? null,
These attributes will be added to the table parameter for the setup function. default ? null,
(Can override other attributes set by nixvim) 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: '' mkRaw = r: {__raw = r;};
do
${string}
end
'';
rawType = mkOptionType { wrapDo = string: ''
name = "rawType"; do
description = "raw lua code"; ${string}
descriptionClass = "noun"; end
merge = mergeEqualOption; '';
check = isRawType;
};
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."; "A textual description of this keybind, to be shown in which-key, if you have it.";
}; };
# Generates maps for a lua config modes = {
genMaps = mode: maps: let normal.short = "n";
/* insert.short = "i";
Take a user-defined action (string or attrs) and return the following attribute set: visual = {
{ desc = "visual and select";
action = (string) the actual action to map to this key short = "v";
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 = {};
}; };
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 { in {
options = { options = {
maps = mkOption { maps =
type = types.submodule { mapAttrs
options = { (
normal = mapOptions "normal"; modeName: modeProps: let
insert = mapOptions "insert"; desc = modeProps.desc or modeName;
select = mapOptions "select"; in
visual = mapOptions "visual and select"; mkOption {
terminal = mapOptions "terminal"; description = "Mappings for ${desc} mode";
normalVisualOp = mapOptions "normal, visual, select and operator-pending (same as plain 'map')"; type = with types;
attrsOf
(
either
str
(
mkMapOptionSubmodule
{
defaultMode = modeProps.short;
withKeyOpt = false;
flatConfig = true;
}
)
);
default = {};
}
)
modes;
visualOnly = mapOptions "visual only"; keymaps = mkOption {
operator = mapOptions "operator-pending"; type = types.listOf (mkMapOptionSubmodule {});
insertCommand = mapOptions "insert and command-line"; default = [];
lang = mapOptions "insert, command-line and lang-arg"; example = [
command = mapOptions "command-line"; {
}; key = "<C-m>";
}; action = "<cmd>make<CR>";
default = {}; options.silent = true;
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>
};
'';
}; };
}; };
config = let config = {
mappings = warnings =
(genMaps "" config.maps.normalVisualOp) optional
++ (genMaps "n" config.maps.normal) (
++ (genMaps "i" config.maps.insert) any
++ (genMaps "v" config.maps.visual) (modeMaps: modeMaps != {})
++ (genMaps "x" config.maps.visualOnly) (attrValues config.maps)
++ (genMaps "s" config.maps.select) )
++ (genMaps "t" config.maps.terminal) ''
++ (genMaps "o" config.maps.operator) The `maps` option will be deprecated in the near future.
++ (genMaps "l" config.maps.lang) Please, use the new `keymaps` option which works as follows:
++ (genMaps "!" config.maps.insertCommand)
++ (genMaps "c" config.maps.command); keymaps = [
in { {
extraConfigLua = # 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 != []) optionalString (mappings != [])
( (
if config.plugins.which-key.enable if config.plugins.which-key.enable
@ -193,9 +288,9 @@ in {
local __nixvim_binds = ${helpers.toLuaObject mappings} local __nixvim_binds = ${helpers.toLuaObject mappings}
for i, map in ipairs(__nixvim_binds) do for i, map in ipairs(__nixvim_binds) do
if not map.action then 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 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 end
end end
@ -206,7 +301,7 @@ in {
do do
local __nixvim_binds = ${helpers.toLuaObject mappings} local __nixvim_binds = ${helpers.toLuaObject mappings}
for i, map in ipairs(__nixvim_binds) do 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
end end
-- }}} -- }}}

View file

@ -1,5 +1,72 @@
{ {helpers, ...}: {
example = { legacy = {
maps.normal."," = "<cmd>echo \"test\"<cr>"; 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";
}
];
};
} }