diff --git a/docs/default.nix b/docs/default.nix
index bbb94d88..43cd9023 100644
--- a/docs/default.nix
+++ b/docs/default.nix
@@ -1,6 +1,7 @@
{
helpers,
system,
+ nixvim,
nixpkgs,
nuschtosSearch,
}:
@@ -108,6 +109,10 @@ lib.fix (
gfm-alerts-to-admonitions = pkgs.python3.pkgs.callPackage ./gfm-alerts-to-admonitions { };
man-docs = pkgs.callPackage ./man { inherit options-json; };
+
+ lib-docs = pkgs.callPackage ./lib {
+ inherit nixvim lib;
+ };
}
// lib.optionalAttrs (!pkgs.stdenv.isDarwin) {
# NuschtOS/search does not seem to work on darwin
diff --git a/docs/lib/default.nix b/docs/lib/default.nix
new file mode 100644
index 00000000..eeaa43e7
--- /dev/null
+++ b/docs/lib/default.nix
@@ -0,0 +1,168 @@
+# 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;
+ };
+ }
+ ''
+ 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 00000000..e232d514
--- /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/lib/menu.nix b/docs/lib/menu.nix
new file mode 100644
index 00000000..87feb9c9
--- /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 00000000..dba6ff08
--- /dev/null
+++ b/docs/lib/pages.nix
@@ -0,0 +1,8 @@
+# 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.
+
+{
+ # TODO
+}
diff --git a/flake/packages.nix b/flake/packages.nix
index a9ba410a..03451350 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;