diff --git a/docs/default.nix b/docs/default.nix
index bbb94d88cc..194c74c62b 100644
--- a/docs/default.nix
+++ b/docs/default.nix
@@ -1,6 +1,7 @@
{
helpers,
system,
+ nixvim,
nixpkgs,
nuschtosSearch,
}:
@@ -107,7 +108,14 @@ lib.fix (
gfm-alerts-to-admonitions = pkgs.python3.pkgs.callPackage ./gfm-alerts-to-admonitions { };
- man-docs = pkgs.callPackage ./man { inherit options-json; };
+ man-docs = pkgs.callPackage ./man {
+ inherit options-json;
+ inherit (self) lib-docs;
+ };
+
+ lib-docs = pkgs.callPackage ./lib {
+ inherit nixvim lib;
+ };
}
// lib.optionalAttrs (!pkgs.stdenv.isDarwin) {
# NuschtOS/search does not seem to work on darwin
@@ -122,7 +130,7 @@ lib.fix (
# > sandbox-exec: pattern serialization length 69298 exceeds maximum (65535)
docs = pkgs.callPackage ./mdbook {
inherit evaledModules transformOptions;
- inherit (self) search;
+ inherit (self) search lib-docs;
};
}
)
diff --git a/docs/lib/default.nix b/docs/lib/default.nix
new file mode 100644
index 0000000000..fb37706f0d
--- /dev/null
+++ b/docs/lib/default.nix
@@ -0,0 +1,178 @@
+# Generates the documentation for library functions using nixdoc.
+# See https://github.com/nix-community/nixdoc
+{
+ lib,
+ runCommand,
+ writers,
+ nixdoc,
+ nixvim,
+ pageSpecs ? import ./pages.nix,
+}:
+
+let
+ # Some pages are just menu entries, others have an actual markdown page that
+ # needs rendering.
+ shouldRenderPage = page: page ? file || page ? markdown;
+
+ # Normalise a page node, recursively normalise its children
+ elaboratePage =
+ loc:
+ {
+ title ? "",
+ markdown ? null,
+ file ? null,
+ pages ? { },
+ }@page:
+ {
+ name = lib.attrsets.showAttrPath loc;
+ loc = lib.throwIfNot (
+ builtins.head loc == "lib"
+ ) "All pages must be within `lib`, unexpected root `${builtins.head loc}`" (builtins.tail loc);
+ }
+ // lib.optionalAttrs (shouldRenderPage page) {
+ inherit
+ file
+ title
+ ;
+ markdown =
+ if builtins.isString markdown then
+ builtins.toFile "${lib.strings.replaceStrings [ "/" "-" ] (lib.lists.last loc)}.md" markdown
+ else
+ markdown;
+ outFile = lib.strings.concatStringsSep "/" (loc ++ [ "index.md" ]);
+ }
+ // lib.optionalAttrs (page ? pages) {
+ pages = elaboratePages loc pages;
+ };
+
+ # Recursively normalise page nodes
+ elaboratePages = prefix: builtins.mapAttrs (name: elaboratePage (prefix ++ [ name ]));
+
+ # Collect all page nodes into a list of page entries
+ collectPages =
+ pages:
+ builtins.concatMap (
+ page:
+ [ (builtins.removeAttrs page [ "pages" ]) ]
+ ++ lib.optionals (page ? pages) (collectPages page.pages)
+ ) (builtins.attrValues pages);
+
+ # Normalised page specs
+ elaboratedPageSpecs = elaboratePages [ ] pageSpecs;
+ pageList = collectPages elaboratedPageSpecs;
+ pagesToRender = builtins.filter (page: page ? outFile) pageList;
+ pagesWithFunctions = builtins.filter (page: page.file or null != null) pageList;
+in
+
+runCommand "nixvim-lib-docs"
+ {
+ nativeBuildInputs = [
+ nixdoc
+ ];
+
+ locations = writers.writeJSON "locations.json" (
+ import ./function-locations.nix {
+ inherit lib;
+ rootPath = nixvim;
+ functionSet = lib.extend nixvim.lib.overlay;
+ pathsToScan = builtins.catAttrs "loc" pagesWithFunctions;
+ revision = nixvim.rev or "main";
+ }
+ );
+
+ passthru.menu = import ./menu.nix {
+ inherit lib;
+ pageSpecs = elaboratedPageSpecs;
+ };
+
+ passthru.pages = builtins.listToAttrs (
+ builtins.map (
+ { name, outFile, ... }:
+ {
+ inherit name;
+ value = outFile;
+ }
+ ) pagesToRender
+ );
+ }
+ ''
+ function docgen {
+ md_file="$1"
+ in_file="$2"
+ name="$3"
+ out_file="$out/$4"
+ title="$5"
+
+ if [[ -z "$in_file" ]]; then
+ if [[ -z "$md_file" ]]; then
+ >&2 echo "No markdown or nix file for $name"
+ exit 1
+ fi
+ elif [[ -f "$in_file/default.nix" ]]; then
+ in_file+="/default.nix"
+ elif [[ ! -f "$in_file" ]]; then
+ >&2 echo "File not found: $in_file"
+ exit 1
+ fi
+
+ if [[ -n "$in_file" ]]; then
+ nixdoc \
+ --file "$in_file" \
+ --locs "$locations" \
+ --category "$name" \
+ --description "REMOVED BY TAIL" \
+ --prefix "" \
+ --anchor-prefix "" \
+ | tail --lines +2 \
+ > functions.md
+ fi
+
+ default_heading="# $name"
+ if [[ -n "$title" ]]; then
+ default_heading+=": $title"
+ fi
+
+ print_heading=true
+ if [[ -f "$md_file" ]] && [[ "$(head --lines 1 "$md_file")" == '# '* ]]; then
+ >&2 echo "NOTE: markdown file for $name starts with a
heading. Skipping default heading \"$default_heading\"."
+ >&2 echo " Found \"$(head --lines 1 "$md_file")\" in: $md_file"
+ print_heading=false
+ fi
+
+ mkdir -p $(dirname "$out_file")
+ (
+ if [[ "$print_heading" = true ]]; then
+ echo "$default_heading"
+ echo
+ fi
+ if [[ -f "$md_file" ]]; then
+ cat "$md_file"
+ echo
+ fi
+ if [[ -f functions.md ]]; then
+ cat functions.md
+ fi
+ ) > "$out_file"
+ }
+
+ mkdir -p "$out"
+
+ ${lib.concatMapStringsSep "\n" (
+ {
+ name,
+ file,
+ markdown,
+ outFile,
+ title ? "",
+ ...
+ }:
+ lib.escapeShellArgs [
+ "docgen"
+ "${lib.optionalString (markdown != null) markdown}" # md_file
+ "${lib.optionalString (file != null) file}" # in_file
+ name # name
+ outFile # out_file
+ title # title
+ ]
+ ) pagesToRender}
+ ''
diff --git a/docs/lib/function-locations.nix b/docs/lib/function-locations.nix
new file mode 100644
index 0000000000..e232d51496
--- /dev/null
+++ b/docs/lib/function-locations.nix
@@ -0,0 +1,85 @@
+# Generates an attrset of "function name" → "markdown location",
+# for use with nixdoc's `--locs` option.
+#
+# {
+# "lib.nixvim.foo.bar" = "[lib/foo.nix:123](https://github.com/nix-community/nixvim/blob/«rev»/lib/foo.nix#L123) in ``";
+# }
+{
+ rootPath,
+ lib,
+ functionSet,
+ pathsToScan,
+ revision,
+ functionSetName ? "lib",
+ url ? "https://github.com/nix-community/nixvim/blob",
+}:
+let
+ rootPathString = toString rootPath;
+ urlPrefix = "${url}/${revision}";
+
+ sanitizeId = builtins.replaceStrings [ "'" ] [ "-prime" ];
+
+ # Like `isAttrs`, but returns `false` if `v` throws
+ tryIsAttrs = v: (builtins.tryEval (builtins.isAttrs v)).value;
+
+ # Collect position entries from an attrset
+ # `prefix` is used in the human-readable name,
+ # and for determining whether to recurse into attrs
+ collectPositionEntriesInSet =
+ prefix: set:
+ builtins.concatMap (
+ name:
+ [
+ {
+ name = lib.showAttrPath (
+ builtins.concatMap lib.toList [
+ functionSetName
+ prefix
+ name
+ ]
+ );
+ location = builtins.unsafeGetAttrPos name set;
+ }
+ ]
+ ++ lib.optionals (prefix == [ ] && tryIsAttrs set.${name}) (
+ collectPositionEntriesInSet (prefix ++ [ name ]) set.${name}
+ )
+ ) (builtins.attrNames set);
+
+ # Collect position entries from each `pathsToScan` in `set`
+ collectPositionEntriesFromPaths =
+ set:
+ builtins.concatMap (loc: collectPositionEntriesInSet loc (lib.getAttrFromPath loc set)) pathsToScan;
+
+ # Remove the tree root (usually the top-level store path)
+ removeNixvimPrefix = lib.flip lib.pipe [
+ (lib.strings.removePrefix rootPathString)
+ (lib.strings.removePrefix "/")
+ ];
+
+ # Create a name-value-pair for use with `listToAttrs`
+ entryToNameValuePair =
+ { name, location }:
+ {
+ name = sanitizeId name;
+ value =
+ let
+ file = removeNixvimPrefix location.file;
+ line = builtins.toString location.line;
+ text = "${file}:${line}";
+ target = "${urlPrefix}/${file}#L${line}";
+ in
+ "[${text}](${target}) in ``";
+ };
+in
+lib.pipe functionSet [
+ # Get the entries
+ collectPositionEntriesFromPaths
+ # Only include entries that have a location
+ (builtins.filter (entry: entry.location != null))
+ # No need to include out-of-tree entries
+ (builtins.filter (entry: lib.strings.hasPrefix rootPathString entry.location.file))
+ # Convert entries to attrset
+ (builtins.map entryToNameValuePair)
+ builtins.listToAttrs
+]
diff --git a/docs/user-guide/helpers.md b/docs/lib/index.md
similarity index 52%
rename from docs/user-guide/helpers.md
rename to docs/lib/index.md
index 37d076f2fc..e52f98a6ce 100644
--- a/docs/user-guide/helpers.md
+++ b/docs/lib/index.md
@@ -1,5 +1,3 @@
-# Helpers
-
## Accessing Nixvim's functions
If Nixvim is built using the standalone method, you can access our "helpers" as part of the `lib` module arg:
@@ -67,47 +65,3 @@ This can be achieved using the lib overlay, available via the `.lib.over
};
}
```
-
-## Common helper functions
-
-A certain number of helpers are defined that can be useful:
-
-- `helpers.emptyTable`: An empty lua table `{}` that will be included in the final lua configuration.
- This is equivalent to `{__empty = {};}`. This form can allow to do `option.__empty = {}`.
-
-- `helpers.mkRaw str`: Write the string `str` as raw lua in the final lua configuration.
- This is equivalent to `{__raw = "lua code";}`. This form can allow to do `option.__raw = "lua code"`.
-
-- `helpers.toLuaObject obj`: Create a string representation of the Nix object. Useful to define your own plugins.
-
-- `helpers.listToUnkeyedAttrs list`: Transforms a list to an "unkeyed" attribute set.
-
- This allows to define mixed table/list in lua:
-
- ```nix
- (listToUnkeyedAttrs ["a" "b"]) // {foo = "bar";}
- ```
-
- Resulting in the following lua:
-
- ```lua
- {"a", "b", [foo] = "bar"}
- ```
-
-- `helpers.enableExceptInTests`: Evaluates to `true`, except in `mkTestDerivationFromNixvimModule`
- where it evaluates to `false`. This allows to skip instantiating plugins that can't be run in tests.
-
-- `helpers.toRawKeys attrs`: Convert the keys of the given `attrs` to raw lua.
- ```nix
- toRawKeys {foo = 1; bar = "hi";}
- ```
- will translate in lua to:
- ```lua
- {[foo] = 1, [bar] = 2,}
- ```
- Otherwise, the keys of a regular `attrs` will be interpreted as lua string:
- ```lua
- {['foo'] = 1, ['bar'] = 2,}
- -- which is the same as
- {foo = 1, bar = 2,}
- ```
diff --git a/docs/lib/menu.nix b/docs/lib/menu.nix
new file mode 100644
index 0000000000..87feb9c9cd
--- /dev/null
+++ b/docs/lib/menu.nix
@@ -0,0 +1,31 @@
+{
+ lib,
+ pageSpecs,
+ indentSize ? " ",
+}:
+let
+ pageToLines =
+ indent: parentName:
+ {
+ name,
+ outFile ? "",
+ pages ? { },
+ ...
+ }:
+ let
+ menuName = lib.strings.removePrefix (parentName + ".") name;
+ children = builtins.attrValues pages;
+ # Only add node to the menu if it has content or multiple children
+ useNodeInMenu = outFile != "" || builtins.length children > 1;
+ parentOfChildren = if useNodeInMenu then name else parentName;
+ in
+ lib.optional useNodeInMenu "${indent}- [${menuName}](${outFile})"
+ ++ lib.optionals (children != [ ]) (
+ builtins.concatMap (pageToLines (indent + indentSize) parentOfChildren) children
+ );
+in
+lib.pipe pageSpecs [
+ builtins.attrValues
+ (builtins.concatMap (pageToLines "" ""))
+ lib.concatLines
+]
diff --git a/docs/lib/pages.nix b/docs/lib/pages.nix
new file mode 100644
index 0000000000..f519339e38
--- /dev/null
+++ b/docs/lib/pages.nix
@@ -0,0 +1,24 @@
+# This file contains a list of function reference docs pages that should be generated by `nixdoc`
+#
+# Note: `nixdoc` uses `rnix` to parse the input file, so the file must return a fairly simple attrset.
+# If there is an issue parsing the file, the resulting markdown will not contain any function docs.
+
+{
+ lib.pages = {
+ nixvim = {
+ title = "Nixvim's functions";
+ markdown = ./index.md;
+
+ pages = {
+ utils = {
+ file = ../../lib/utils.nix;
+ title = "utility functions";
+ };
+ lua = {
+ file = ../../lib/to-lua.nix;
+ title = "lua functions";
+ };
+ };
+ };
+ };
+}
diff --git a/docs/man/default.nix b/docs/man/default.nix
index c60743e134..7524370039 100644
--- a/docs/man/default.nix
+++ b/docs/man/default.nix
@@ -1,12 +1,17 @@
{
lib,
options-json,
+ lib-docs,
runCommand,
installShellFiles,
nixos-render-docs,
pandoc,
}:
let
+ markdownSections = [
+ ../user-guide/faq.md
+ ../user-guide/config-examples.md
+ ] ++ lib.mapAttrsToList (name: file: "${lib-docs}/${file}") lib-docs.pages;
manHeader =
runCommand "nixvim-general-doc-manpage"
{
@@ -22,11 +27,7 @@ let
(
cat ${./nixvim-header-start.5}
- ${lib.concatMapStringsSep "\n" (file: "mkMDSection ${file}") [
- ../user-guide/helpers.md
- ../user-guide/faq.md
- ../user-guide/config-examples.md
- ]}
+ ${lib.concatMapStringsSep "\n" (file: "mkMDSection ${file}") markdownSections}
cat ${./nixvim-header-end.5}
) >$out/nixvim-header.5
diff --git a/docs/mdbook/SUMMARY.md b/docs/mdbook/SUMMARY.md
index d08c19fd7d..88d01d8df3 100644
--- a/docs/mdbook/SUMMARY.md
+++ b/docs/mdbook/SUMMARY.md
@@ -5,11 +5,14 @@
# User guide
- [Installation](./user-guide/install.md)
-- [Helpers](./user-guide/helpers.md)
- [FAQ](./user-guide/faq.md)
- [Configuration examples](./user-guide/config-examples.md)
- [Lazy Loading](./user-guide/lazy-loading.md)
+# Functions
+
+@FUNCTIONS_MENU@
+
# Platforms
- [Nixvim Platforms](./platforms/index.md)
diff --git a/docs/mdbook/book.toml b/docs/mdbook/book.toml
index 2e11c8249c..a74ba126de 100644
--- a/docs/mdbook/book.toml
+++ b/docs/mdbook/book.toml
@@ -10,6 +10,11 @@ site-url = "@SITE_URL@"
additional-js = ["theme/pagetoc.js"]
additional-css = ["theme/pagetoc.css"]
+# Redirect targets must be relative to their origin;
+# absolute paths can break with different baseHrefs.
+[output.html.redirect]
+"/user-guide/helpers.html" = "../lib/index.html" # Since #3049
+
[output.html.fold]
enable = true
level = 0
diff --git a/docs/mdbook/default.nix b/docs/mdbook/default.nix
index a75c0b5e11..d8e779704e 100644
--- a/docs/mdbook/default.nix
+++ b/docs/mdbook/default.nix
@@ -7,6 +7,7 @@
nixosOptionsDoc,
transformOptions,
search,
+ lib-docs,
# The root directory of the site
baseHref ? "/",
# A list of all available docs that should be linked to
@@ -359,6 +360,13 @@ pkgs.stdenv.mkDerivation (finalAttrs: {
# Copy the contributing file
cp $contributing ./CONTRIBUTING.md
+ # Symlink the function docs
+ for path in ${lib-docs}/*
+ do
+ echo "symlinking \"$path\" to \"$(basename "$path")\""
+ ln -s "$path" $(basename "$path")
+ done
+
# Copy the generated MD docs into the build directory
bash -e ${finalAttrs.passthru.copy-docs}
@@ -375,6 +383,7 @@ pkgs.stdenv.mkDerivation (finalAttrs: {
# Patch SUMMARY.md - which defiens mdBook's table of contents
substituteInPlace ./SUMMARY.md \
+ --replace-fail "@FUNCTIONS_MENU@" "$functionsSummary" \
--replace-fail "@PLATFORM_OPTIONS@" "$wrapperOptionsSummary" \
--replace-fail "@NIXVIM_OPTIONS@" "$nixvimOptionsSummary"
@@ -401,6 +410,8 @@ pkgs.stdenv.mkDerivation (finalAttrs: {
wrapperOptionsFiles
;
+ functionsSummary = lib-docs.menu;
+
passthru = {
fix-links = callPackage ../fix-links {
# FIXME: determine values from availableVersions & baseHref
diff --git a/flake/packages.nix b/flake/packages.nix
index a9ba410a5d..03451350c1 100644
--- a/flake/packages.nix
+++ b/flake/packages.nix
@@ -1,4 +1,5 @@
{
+ self,
inputs,
helpers,
...
@@ -12,6 +13,7 @@
}:
{
packages = import ../docs {
+ nixvim = self;
inherit helpers;
inherit system;
inherit (inputs) nixpkgs;
diff --git a/lib/default.nix b/lib/default.nix
index 51dc36c889..2b86d67a2b 100644
--- a/lib/default.nix
+++ b/lib/default.nix
@@ -24,7 +24,7 @@ lib.makeExtensible (
modules = call ./modules.nix { inherit flake; };
options = call ./options.nix { };
plugins = call ./plugins { };
- utils = call ./utils.nix { inherit _nixvimTests; };
+ utils = call ./utils.nix { inherit _nixvimTests; } // call ./utils.internal.nix { };
# Top-level helper aliases:
# TODO: deprecate some aliases
@@ -103,7 +103,7 @@ lib.makeExtensible (
wrapVimscriptForLua
;
- toLuaObject = self.lua.toLua;
+ inherit (self.lua) toLuaObject;
mkLuaInline = self.lua.mkInline;
# TODO: Removed 2024-12-21
diff --git a/lib/modules.nix b/lib/modules.nix
index 5bcba06146..5d73b09e08 100644
--- a/lib/modules.nix
+++ b/lib/modules.nix
@@ -27,7 +27,7 @@ in
assert lib.assertMsg (extraSpecialArgs ? lib -> extraSpecialArgs.lib ? nixvim) ''
Nixvim requires a lib that includes some custom extensions, however the `lib` from `specialArgs` does not have a `nixvim` attr.
Remove `lib` from nixvim's `specialArgs` or ensure you apply nixvim's extensions to your `lib`.
- See https://nix-community.github.io/nixvim/user-guide/helpers.html#using-a-custom-lib-with-nixvim'';
+ See https://nix-community.github.io/nixvim/lib/index.html#using-a-custom-lib-with-nixvim'';
assert lib.assertMsg (system != null -> lib.isString system) ''
When `system` is supplied to `evalNixvim`, it must be a string.
To define a more complex system, please use nixvim's `nixpkgs.hostPlatform` option.'';
diff --git a/lib/to-lua.nix b/lib/to-lua.nix
index 4c4c7434df..3319a0541c 100644
--- a/lib/to-lua.nix
+++ b/lib/to-lua.nix
@@ -38,6 +38,21 @@ rec {
# Whether the value is a lua-inline type
isInline = v: v._type or null == "lua-inline";
+ /**
+ Serialise a nix value as a lua object.
+
+ Useful for defining your own plugins or structured config.
+
+ # Type
+
+ ```
+ toLuaObject :: Any -> String
+ ```
+ */
+ toLuaObject =
+ # toLua' with backwards-compatible options
+ toLua' { };
+
# toLua' with default options, aliased as toLuaObject at the top-level
toLua = toLua' { };
diff --git a/lib/utils.internal.nix b/lib/utils.internal.nix
new file mode 100644
index 0000000000..4940e2066d
--- /dev/null
+++ b/lib/utils.internal.nix
@@ -0,0 +1,348 @@
+{ lib }:
+rec {
+ # Whether a string contains something other than whitespaces
+ hasContent = str: builtins.match "[[:space:]]*" str == null;
+
+ # Concatenate a list of strings, adding a newline at the end of each one,
+ # but skipping strings containing only whitespace characters
+ concatNonEmptyLines = lines: lib.concatLines (builtins.filter hasContent lines);
+
+ /**
+ Add a prefix to the keys of an attrs.
+
+ # Example
+ ```nix
+ applyPrefixToAttrs "prefix_" { foo = 1; bar = 2; }
+ => { prefix_foo = 1; prefix_bar = 2; }
+ ```
+
+ # Type
+
+ ```
+ applyPrefixToAttrs :: String -> AttrSet -> AttrSet
+ ```
+ */
+ applyPrefixToAttrs = prefix: lib.mapAttrs' (n: lib.nameValuePair (prefix + n));
+
+ /*
+ Convert a string from camelCase to snake_case
+ Type: string -> string
+ */
+ toSnakeCase =
+ let
+ splitByWords = builtins.split "([A-Z])";
+ processWord = s: if lib.isString s then s else "_" + lib.toLower (lib.elemAt s 0);
+ in
+ string:
+ let
+ words = splitByWords string;
+ in
+ lib.concatStrings (map processWord words);
+
+ /**
+ Those helpers control the lua sections split in `pre, content, post`
+ */
+ mkBeforeSection = lib.mkOrder 300;
+ mkAfterSection = lib.mkOrder 2000;
+
+ /**
+ Capitalize a string by making the first character uppercase.
+
+ # Example
+
+ ```nix
+ upperFirstChar "hello, world!"
+ => "Hello, world!"
+ ```
+
+ # Type
+
+ ```
+ upperFirstChar :: String -> String
+ ```
+ */
+ upperFirstChar =
+ s:
+ let
+ first = lib.substring 0 1 s;
+ rest = lib.substring 1 (lib.stringLength s) s;
+ result = (lib.toUpper first) + rest;
+ in
+ lib.optionalString (s != "") result;
+
+ mkIfNonNull' = x: y: (lib.mkIf (x != null) y);
+
+ mkIfNonNull = x: (mkIfNonNull' x x);
+
+ ifNonNull' = x: y: if (x == null) then null else y;
+
+ /**
+ Convert the given string into a literalExpression mkRaw.
+
+ For use in option documentation, such as examples and defaults.
+
+ # Example
+
+ ```nix
+ literalLua "print('hi')"
+ => literalExpression ''lib.nixvim.mkRaw "print('hi')"''
+ => {
+ _type = "literalExpression";
+ text = ''lib.nixvim.mkRaw "print('hi')"'';
+ }
+ ```
+
+ # Type
+
+ ```
+ literalLua :: String -> AttrSet
+ ```
+ */
+ literalLua =
+ r:
+ let
+ # Pass the value through mkRaw for validation
+ raw = lib.nixvim.mkRaw r;
+ # TODO: consider switching to lib.generators.mkLuaInline ?
+ exp = "lib.nixvim.mkRaw " + lib.generators.toPretty { } raw.__raw;
+ in
+ lib.literalExpression exp;
+
+ __messagePrefix = scope: "Nixvim (${scope}):";
+
+ /**
+ Process one or several assertions by prepending the common 'Nixvim (): ' prefix to messages.
+ The second argument can either be a list of assertions or a single one.
+
+ # Example
+
+ ```nix
+ assertions = mkAssertions "plugins.foo" {
+ assertion = plugins.foo.settings.barIntegration && (!plugins.bar.enable);
+ message = "`barIntegration` is enabled but the `bar` plugin is not."
+ }
+ ```
+
+ # Type
+
+ ```
+ mkAssertions :: String -> List -> List
+ ```
+ */
+ mkAssertions =
+ scope: assertions:
+ let
+ prefix = __messagePrefix scope;
+ processAssertion = a: {
+ inherit (a) assertion;
+ message = "${prefix} ${lib.trim a.message}";
+ };
+ in
+ builtins.map processAssertion (lib.toList assertions);
+
+ /**
+ Convert one or several conditional warnings to a final warning list.
+ The second argument can either be a list of _conditional warnings_ or a single one.
+
+ # Example
+
+ ```nix
+ warnings = mkWarnings "plugins.foo" {
+ when = plugins.foo.settings.barIntegration && (!plugins.bar.enable);
+ message = "`barIntegration` is enabled but the `bar` plugin is not."
+ }
+ ```
+
+ # Type
+
+ ```
+ mkWarnings :: String -> List -> List
+ ```
+ */
+ mkWarnings =
+ scope: warnings:
+ let
+ prefix = __messagePrefix scope;
+ processWarning =
+ warning: lib.optional (warning.when or true) "${prefix} ${lib.trim (warning.message or warning)}";
+ in
+ builtins.concatMap processWarning (lib.toList warnings);
+
+ /**
+ Convert the given string into a `__pretty` printed mkRaw expression.
+
+ For use in nested values that will be printed by `lib.generators.toPretty`,
+ or `lib.options.renderOptionValue`.
+
+ # Example
+
+ ```nix
+ nestedLiteralLua "print('hi')"
+ => nestedLiteral (literalLua "print('hi')")
+ => {
+ __pretty = lib.getAttr "text";
+ val = literalLua "print('hi')";
+ }
+ ```
+
+ # Type
+
+ ```
+ nestedLiteralLua :: String -> AttrSet
+ ```
+ */
+ nestedLiteralLua = r: nestedLiteral (literalLua r);
+
+ /**
+ Convert the given string or literalExpression into a `__pretty` printed expression.
+
+ For use in nested values that will be printed by `lib.generators.toPretty`,
+ or `lib.options.renderOptionValue`.
+
+ # Examples
+
+ ```nix
+ nestedLiteral "example"
+ => {
+ __pretty = lib.getAttr "text";
+ val = literalExpression "example";
+ }
+ ```
+
+ ```nix
+ nestedLiteral (literalExpression ''"hello, world"'')
+ => {
+ __pretty = lib.getAttr "text";
+ val = literalExpression ''"hello, world"'';
+ }
+ ```
+
+ # Type
+
+ ```
+ nestedLiteral :: (String | literalExpression) -> AttrSet
+ ```
+ */
+ nestedLiteral = val: {
+ __pretty = lib.getAttr "text";
+ val = if val._type or null == "literalExpression" then val else lib.literalExpression val;
+ };
+
+ wrapDo = string: ''
+ do
+ ${string}
+ end
+ '';
+
+ /**
+ Convert the given String to a Lua [long literal].
+ For example, you could use this to safely pass a Vimscript string to the
+ `vim.cmd` function.
+
+ [long literal]: https://www.lua.org/manual/5.4/manual.html#3.1
+
+ # Examples
+
+ ```nix
+ nix-repl> toLuaLongLiteral "simple"
+ "[[simple]]"
+ ```
+
+ ```nix
+ nix-repl> toLuaLongLiteral "]]"
+ "[=[]]]=]"
+ ```
+
+ # Type
+
+ ```
+ toLuaLongLiteral :: String -> String
+ ```
+ */
+ toLuaLongLiteral =
+ string:
+ let
+ findTokens =
+ depth:
+ let
+ infix = lib.strings.replicate depth "=";
+ tokens.open = "[${infix}[";
+ tokens.close = "]${infix}]";
+ in
+ if lib.hasInfix tokens.close string then findTokens (depth + 1) else tokens;
+
+ tokens = findTokens 0;
+ in
+ tokens.open + string + tokens.close;
+
+ /**
+ Convert the given String into a Vimscript [:let-heredoc].
+ For example, you could use this to invoke [:lua].
+
+ [:let-heredoc]: https://neovim.io/doc/user/eval.html#%3Alet-heredoc
+ [:lua]: https://neovim.io/doc/user/lua.html#%3Alua-heredoc
+
+ # Examples
+
+ ```nix
+ toVimscriptHeredoc "simple"
+ => "<< EOF\nsimple\nEOF"
+ ```
+
+ ```nix
+ toVimscriptHeredoc "EOF"
+ => "<< EOFF\nEOF\nEOFF"
+ ```
+
+ # Type
+
+ ```
+ toVimscriptHeredoc :: String -> String
+ ```
+ */
+ toVimscriptHeredoc =
+ string:
+ let
+ findToken =
+ depth:
+ let
+ token = "EOF" + lib.strings.replicate depth "F";
+ in
+ if lib.hasInfix token string then findToken (depth + 1) else token;
+
+ token = findToken 0;
+ in
+ ''
+ << ${token}
+ ${string}
+ ${token}'';
+
+ # Wrap Vimscript for using in lua,
+ # but only if the string contains something other than whitespaces
+ wrapVimscriptForLua =
+ string: lib.optionalString (hasContent string) "vim.cmd(${toLuaLongLiteral string})";
+
+ # Wrap lua script for using in Vimscript,
+ # but only if the string contains something other than whitespaces
+ wrapLuaForVimscript =
+ string: lib.optionalString (hasContent string) "lua ${toVimscriptHeredoc string}";
+
+ # Split a list into a several sub-list, each with a max-size of `size`
+ groupListBySize =
+ size: list:
+ lib.reverseList (
+ lib.foldl' (
+ lists: item:
+ let
+ first = lib.head lists;
+ rest = lib.drop 1 lists;
+ in
+ if lists == [ ] then
+ [ [ item ] ]
+ else if lib.length first < size then
+ [ (first ++ [ item ]) ] ++ rest
+ else
+ [ [ item ] ] ++ lists
+ ) [ ] list
+ );
+}
diff --git a/lib/utils.nix b/lib/utils.nix
index 133c3b3efa..d218220b16 100644
--- a/lib/utils.nix
+++ b/lib/utils.nix
@@ -3,48 +3,67 @@
_nixvimTests,
}:
rec {
- # Whether a string contains something other than whitespaces
- hasContent = str: builtins.match "[[:space:]]*" str == null;
+ /**
+ Transforms a list to an _"unkeyed"_ attribute set.
+
+ This allows to define mixed table/list in lua:
+
+ ```nix
+ listToUnkeyedAttrs ["a" "b"] // { foo = "bar"; }
+ ```
- # Concatenate a list of strings, adding a newline at the end of each one,
- # but skipping strings containing only whitespace characters
- concatNonEmptyLines = lines: lib.concatLines (builtins.filter hasContent lines);
+ Resulting in the following lua:
+ ```lua
+ {"a", "b", foo = "bar"}
+ ```
+ */
listToUnkeyedAttrs =
list:
builtins.listToAttrs (lib.lists.imap0 (idx: lib.nameValuePair "__unkeyed-${toString idx}") list);
+ /**
+ Usually `true`, except when nixvim is being evaluated by
+ `mkTestDerivationFromNixvimModule`, where it is `false`.
+
+ This can be used to dynamically enable plugins that can't be run in the
+ test environment.
+ */
+ # TODO: replace and deprecate
+ # We shouldn't need to use another instance of `lib` when building a test drv
enableExceptInTests = !_nixvimTests;
+ /**
+ An empty lua table `{ }` that will be included in the final lua configuration.
+
+ This is equivalent to `{ __empty = { }; }`.
+ This form can allow to do `option.__empty = { }`.
+ */
emptyTable = {
+ # Keep nested to bind docs to `emptyTable`, not `__empty`.
"__empty" = null;
};
/**
- Add a prefix to the keys of an attrs.
+ Convert the keys of the given attrset to _"raw lua"_.
# Example
+
```nix
- applyPrefixToAttrs "prefix_" { foo = 1; bar = 2; }
- => { prefix_foo = 1; prefix_bar = 2; }
+ toRawKeys { foo = 1; bar = 2; }
+ => { __rawKey__foo = 1; __rawKey__bar = 2; }
```
- # Type
+ Resulting in the following lua:
+ ```lua
+ {[foo] = 1, [bar] = 2}
```
- applyPrefixToAttrs :: String -> AttrSet -> AttrSet
- ```
- */
- applyPrefixToAttrs = prefix: lib.mapAttrs' (n: lib.nameValuePair (prefix + n));
- /**
- Turn all the keys of an attrs into raw lua.
+ If "raw keys" are **not** used, the attr names are treated as string keys:
- # Example
-
- ```nix
- toRawKeys { foo = 1; bar = 2; }
- => { __rawKey__foo = 1; __rawKey__bar = 2; }
+ ```lua
+ {foo = 1, bar = 2}
```
# Type
@@ -78,58 +97,12 @@ rec {
*/
mkRawKey = n: v: toRawKeys { "${n}" = v; };
- /*
- Convert a string from camelCase to snake_case
- Type: string -> string
- */
- toSnakeCase =
- let
- splitByWords = builtins.split "([A-Z])";
- processWord = s: if lib.isString s then s else "_" + lib.toLower (lib.elemAt s 0);
- in
- string:
- let
- words = splitByWords string;
- in
- lib.concatStrings (map processWord words);
-
- /**
- Those helpers control the lua sections split in `pre, content, post`
- */
- mkBeforeSection = lib.mkOrder 300;
- mkAfterSection = lib.mkOrder 2000;
-
/**
- Capitalize a string by making the first character uppercase.
+ Write the string `str` as raw lua in the final lua configuration.
- # Example
-
- ```nix
- upperFirstChar "hello, world!"
- => "Hello, world!"
- ```
-
- # Type
-
- ```
- upperFirstChar :: String -> String
- ```
+ This is equivalent to `{ __raw = "lua code"; }`.
+ This form can allow to do `option.__raw = "lua code"`.
*/
- upperFirstChar =
- s:
- let
- first = lib.substring 0 1 s;
- rest = lib.substring 1 (lib.stringLength s) s;
- result = (lib.toUpper first) + rest;
- in
- lib.optionalString (s != "") result;
-
- mkIfNonNull' = x: y: (lib.mkIf (x != null) y);
-
- mkIfNonNull = x: (mkIfNonNull' x x);
-
- ifNonNull' = x: y: if (x == null) then null else y;
-
mkRaw =
r:
if r == null || r == "" then
@@ -140,273 +113,4 @@ rec {
r
else
throw "mkRaw: invalid input: ${lib.generators.toPretty { multiline = false; } r}";
-
- /**
- Convert the given string into a literalExpression mkRaw.
-
- For use in option documentation, such as examples and defaults.
-
- # Example
-
- ```nix
- literalLua "print('hi')"
- => literalExpression ''lib.nixvim.mkRaw "print('hi')"''
- => {
- _type = "literalExpression";
- text = ''lib.nixvim.mkRaw "print('hi')"'';
- }
- ```
-
- # Type
-
- ```
- literalLua :: String -> AttrSet
- ```
- */
- literalLua =
- r:
- let
- # Pass the value through mkRaw for validation
- raw = mkRaw r;
- # TODO: consider switching to lib.generators.mkLuaInline ?
- exp = "lib.nixvim.mkRaw " + lib.generators.toPretty { } raw.__raw;
- in
- lib.literalExpression exp;
-
- __messagePrefix = scope: "Nixvim (${scope}):";
- /**
- Process one or several assertions by prepending the common 'Nixvim (): ' prefix to messages.
- The second argument can either be a list of assertions or a single one.
-
- # Example
-
- ```nix
- assertions = mkAssertions "plugins.foo" {
- assertion = plugins.foo.settings.barIntegration && (!plugins.bar.enable);
- message = "`barIntegration` is enabled but the `bar` plugin is not."
- }
- ```
-
- # Type
-
- ```
- mkAssertions :: String -> List -> List
- ```
- */
- mkAssertions =
- scope: assertions:
- let
- prefix = __messagePrefix scope;
- processAssertion = a: {
- inherit (a) assertion;
- message = "${prefix} ${lib.trim a.message}";
- };
- in
- builtins.map processAssertion (lib.toList assertions);
-
- /**
- Convert one or several conditional warnings to a final warning list.
- The second argument can either be a list of _conditional warnings_ or a single one.
-
- # Example
-
- ```nix
- warnings = mkWarnings "plugins.foo" {
- when = plugins.foo.settings.barIntegration && (!plugins.bar.enable);
- message = "`barIntegration` is enabled but the `bar` plugin is not."
- }
- ```
-
- # Type
-
- ```
- mkWarnings :: String -> List -> List
- ```
- */
- mkWarnings =
- scope: warnings:
- let
- prefix = __messagePrefix scope;
- processWarning =
- warning: lib.optional (warning.when or true) "${prefix} ${lib.trim (warning.message or warning)}";
- in
- builtins.concatMap processWarning (lib.toList warnings);
-
- /**
- Convert the given string into a `__pretty` printed mkRaw expression.
-
- For use in nested values that will be printed by `lib.generators.toPretty`,
- or `lib.options.renderOptionValue`.
-
- # Example
-
- ```nix
- nestedLiteralLua "print('hi')"
- => nestedLiteral (literalLua "print('hi')")
- => {
- __pretty = lib.getAttr "text";
- val = literalLua "print('hi')";
- }
- ```
-
- # Type
-
- ```
- nestedLiteralLua :: String -> AttrSet
- ```
- */
- nestedLiteralLua = r: nestedLiteral (literalLua r);
-
- /**
- Convert the given string or literalExpression into a `__pretty` printed expression.
-
- For use in nested values that will be printed by `lib.generators.toPretty`,
- or `lib.options.renderOptionValue`.
-
- # Examples
-
- ```nix
- nestedLiteral "example"
- => {
- __pretty = lib.getAttr "text";
- val = literalExpression "example";
- }
- ```
-
- ```nix
- nestedLiteral (literalExpression ''"hello, world"'')
- => {
- __pretty = lib.getAttr "text";
- val = literalExpression ''"hello, world"'';
- }
- ```
-
- # Type
-
- ```
- nestedLiteral :: (String | literalExpression) -> AttrSet
- ```
- */
- nestedLiteral = val: {
- __pretty = lib.getAttr "text";
- val = if val._type or null == "literalExpression" then val else lib.literalExpression val;
- };
-
- wrapDo = string: ''
- do
- ${string}
- end
- '';
-
- /**
- Convert the given String to a Lua [long literal].
- For example, you could use this to safely pass a Vimscript string to the
- `vim.cmd` function.
-
- [long literal]: https://www.lua.org/manual/5.4/manual.html#3.1
-
- # Examples
-
- ```nix
- nix-repl> toLuaLongLiteral "simple"
- "[[simple]]"
- ```
-
- ```nix
- nix-repl> toLuaLongLiteral "]]"
- "[=[]]]=]"
- ```
-
- # Type
-
- ```
- toLuaLongLiteral :: String -> String
- ```
- */
- toLuaLongLiteral =
- string:
- let
- findTokens =
- depth:
- let
- infix = lib.strings.replicate depth "=";
- tokens.open = "[${infix}[";
- tokens.close = "]${infix}]";
- in
- if lib.hasInfix tokens.close string then findTokens (depth + 1) else tokens;
-
- tokens = findTokens 0;
- in
- tokens.open + string + tokens.close;
-
- /**
- Convert the given String into a Vimscript [:let-heredoc].
- For example, you could use this to invoke [:lua].
-
- [:let-heredoc]: https://neovim.io/doc/user/eval.html#%3Alet-heredoc
- [:lua]: https://neovim.io/doc/user/lua.html#%3Alua-heredoc
-
- # Examples
-
- ```nix
- toVimscriptHeredoc "simple"
- => "<< EOF\nsimple\nEOF"
- ```
-
- ```nix
- toVimscriptHeredoc "EOF"
- => "<< EOFF\nEOF\nEOFF"
- ```
-
- # Type
-
- ```
- toVimscriptHeredoc :: String -> String
- ```
- */
- toVimscriptHeredoc =
- string:
- let
- findToken =
- depth:
- let
- token = "EOF" + lib.strings.replicate depth "F";
- in
- if lib.hasInfix token string then findToken (depth + 1) else token;
-
- token = findToken 0;
- in
- ''
- << ${token}
- ${string}
- ${token}'';
-
- # Wrap Vimscript for using in lua,
- # but only if the string contains something other than whitespaces
- wrapVimscriptForLua =
- string: lib.optionalString (hasContent string) "vim.cmd(${toLuaLongLiteral string})";
-
- # Wrap lua script for using in Vimscript,
- # but only if the string contains something other than whitespaces
- wrapLuaForVimscript =
- string: lib.optionalString (hasContent string) "lua ${toVimscriptHeredoc string}";
-
- # Split a list into a several sub-list, each with a max-size of `size`
- groupListBySize =
- size: list:
- lib.reverseList (
- lib.foldl' (
- lists: item:
- let
- first = lib.head lists;
- rest = lib.drop 1 lists;
- in
- if lists == [ ] then
- [ [ item ] ]
- else if lib.length first < size then
- [ (first ++ [ item ]) ] ++ rest
- else
- [ [ item ] ] ++ lists
- ) [ ] list
- );
}