{ lib, helpers, config, pkgs, ... }: with lib; let cfg = config.plugins.mkdnflow; in { options.plugins.mkdnflow = helpers.neovim-plugin.extraOptionsOptions // { enable = mkEnableOption "mkdnflow.nvim"; package = helpers.mkPluginPackageOption "mkdnflow.nvim" pkgs.vimPlugins.mkdnflow-nvim; modules = mapAttrs ( moduleName: metadata: helpers.defaultNullOpts.mkBool metadata.default '' Enable module ${moduleName}. ${metadata.description} '' ) { bib = { default = true; description = "Required for parsing bib files and following citations."; }; buffers = { default = true; description = "Required for backward and forward navigation through buffers."; }; conceal = { default = true; description = '' Required if you wish to enable link concealing. Note that you must declare `links.conceal` as `true` in addition to enabling this module if you wish to conceal links. ''; }; cursor = { default = true; description = "Required for jumping to links and headings; yanking anchor links."; }; folds = { default = true; description = "Required for folding by section."; }; links = { default = true; description = "Required for creating and destroying links and following links."; }; lists = { default = true; description = "Required for manipulating lists, toggling to-do list items, etc."; }; maps = { default = true; description = '' Required for setting mappings via the mappings table. Set to `false` if you wish to set mappings outside of the plugin. ''; }; paths = { default = true; description = "Required for link interpretation and following links."; }; tables = { default = true; description = "Required for table navigation and formatting."; }; yaml = { default = false; description = "Required for parsing yaml blocks."; }; }; filetypes = helpers.defaultNullOpts.mkNullable (with types; attrsOf bool) "{md = true; rmd = true; markdown = true;}" '' A matching extension will enable the plugin's functionality for a file with that extension. NOTE: This functionality references the file's extension. It does not rely on Neovim's filetype recognition. The extension must be provided in lower case because the plugin converts file names to lowercase. Any arbitrary extension can be supplied. Setting an extension to `false` is the same as not including it in the list. ''; createDirs = helpers.defaultNullOpts.mkBool true '' - `true`: Directories referenced in a link will be (recursively) created if they do not exist. - `false`: No action will be taken when directories referenced in a link do not exist. Neovim will open a new file, but you will get an error when you attempt to write the file. ''; perspective = { priority = helpers.defaultNullOpts.mkEnumFirstDefault [ "first" "current" "root" ] '' Specifies the priority perspective to take when interpreting link paths - `first` (default): Links will be interpreted relative to the first-opened file (when the current instance of Neovim was started) - `current`: Links will be interpreted relative to the current file - `root`: Links will be interpreted relative to the root directory of the current notebook (requires `perspective.root_tell` to be specified) ''; fallback = helpers.defaultNullOpts.mkEnumFirstDefault [ "first" "current" "root" ] '' Specifies the backup perspective to take if priority isn't possible (e.g. if it is `root` but no root directory is found). - `first` (default): Links will be interpreted relative to the first-opened file (when the current instance of Neovim was started) - `current`: Links will be interpreted relative to the current file - `root`: Links will be interpreted relative to the root directory of the current notebook (requires `perspective.root_tell` to be specified) ''; rootTell = helpers.defaultNullOpts.mkNullable (with types; either (enum [ false ]) str) "false" '' - ``: Any arbitrary filename by which the plugin can uniquely identify the root directory of the current notebook. - If `false` is used instead, the plugin will never search for a root directory, even if `perspective.priority` is set to `root`. ''; nvimWdHeel = helpers.defaultNullOpts.mkBool false '' Specifies whether changes in perspective will result in corresponding changes to Neovim's working directory. - `true`: Changes in perspective will be reflected in the nvim working directory. (In other words, the working directory will "heel" to the plugin's perspective.) This helps ensure (at least) that path completions (if using a completion plugin with support for paths) will be accurate and usable. - `false` (default): Neovim's working directory will not be affected by Mkdnflow. ''; update = helpers.defaultNullOpts.mkBool true '' Determines whether the plugin looks to determine if a followed link is in a different notebook/wiki than before. If it is, the perspective will be updated. Requires `rootTell` to be defined and `priority` to be `root`. - `true` (default): Perspective will be updated when following a link to a file in a separate notebook/wiki (or navigating backwards to a file in another notebook/wiki). - `false`: Perspective will be not updated when following a link to a file in a separate notebook/wiki. Under the hood, links in the file in the separate notebook/wiki will be interpreted relative to the original notebook/wiki. ''; }; wrap = helpers.defaultNullOpts.mkBool false '' - `true`: When jumping to next/previous links or headings, the cursor will continue searching at the beginning/end of the file. - `false`: When jumping to next/previous links or headings, the cursor will stop searching at the end/beginning of the file. ''; bib = { defaultPath = helpers.mkNullOrOption types.str '' Specifies a path to a default `.bib` file to look for citation keys in (need not be in root directory of notebook). ''; findInRoot = helpers.defaultNullOpts.mkBool true '' - `true`: When `perspective.priority` is also set to `root` (and a root directory was found), the plugin will search for bib files to reference in the notebook's top-level directory. If `bib.default_path` is also specified, the default path will be appended to the list of bib files found in the top level directory so that it will also be searched. - `false`: The notebook's root directory will not be searched for bib files. ''; }; silent = helpers.defaultNullOpts.mkBool false '' - `true`: The plugin will not display any messages in the console except compatibility warnings related to your config. - `false`: The plugin will display messages to the console (all messages from the plugin start with ⬇️ ). ''; links = { style = helpers.defaultNullOpts.mkEnumFirstDefault [ "markdown" "wiki" ] '' - `markdown`: Links will be expected in the standard markdown format: `[](<source>)` - `wiki`: Links will be expected in the unofficial wiki-link style, specifically the title-after-pipe format (see https://github.com/jgm/pandoc/pull/7705): `[[<source>|<title>]]`. ''; conceal = helpers.defaultNullOpts.mkBool false '' - `true`: Link sources and delimiters will be concealed (depending on which link style is selected) - `false`: Link sources and delimiters will not be concealed by mkdnflow ''; context = helpers.defaultNullOpts.mkInt 0 '' - `0` (default): When following or jumping to links, assume that no link will be split over multiple lines - `n`: When following or jumping to links, consider `n` lines before and after a given line (useful if you ever permit links to be interrupted by a hard line break) ''; implicitExtension = helpers.mkNullOrOption types.str '' A string that instructs the plugin (a) how to interpret links to files that do not have an extension, and (b) that new links should be created without an explicit extension. ''; transformExplicit = helpers.defaultNullOpts.mkStrLuaFnOr (types.enum [ false ]) "false" '' A function that transforms the text to be inserted as the source/path of a link when a link is created. Anchor links are not currently customizable. If you want all link paths to be explicitly prefixed with the year, for instance, and for the path to be converted to uppercase, you could provide the following function under this key. (NOTE: The previous functionality specified under the `prefix` key has been migrated here to provide greater flexibility.) Example: ```lua function(input) return(string.upper(os.date('%Y-')..input)) end ``` ''; transformImplicit = helpers.defaultNullOpts.mkStrLuaFnOr (types.enum [ false ]) '' function(text) text = text:gsub(" ", "-") text = text:lower() text = os.date('%Y-%m-%d_')..text return(text) end '' '' A function that transforms the path of a link immediately before interpretation. It does not transform the actual text in the buffer but can be used to modify link interpretation. For instance, link paths that match a date pattern can be opened in a `journals` subdirectory of your notebook, and all others can be opened in a `pages` subdirectory, using the following function: ```lua function(input) if input:match('%d%d%d%d%-%d%d%-%d%d') then return('journals/'..input) else return('pages/'..input) end end ``` ''; }; toDo = { symbols = helpers.defaultNullOpts.mkNullable (with types; listOf str) ''[" " "-" "X"]'' '' A list of symbols (each no more than one character) that represent to-do list completion statuses. `MkdnToggleToDo` references these when toggling the status of a to-do item. Three are expected: one representing not-yet-started to-dos (default: `' '`), one representing in-progress to-dos (default: `-`), and one representing complete to-dos (default: `X`). NOTE: Native Lua support for UTF-8 characters is limited, so in order to ensure all functionality works as intended if you are using non-ascii to-do symbols, you'll need to install the luarocks module "luautf8". ''; updateParents = helpers.defaultNullOpts.mkBool true '' Whether parent to-dos' statuses should be updated based on child to-do status changes performed via `MkdnToggleToDo` - `true` (default): Parent to-do statuses will be inferred and automatically updated when a child to-do's status is changed - `false`: To-do items can be toggled, but parent to-do statuses (if any) will not be automatically changed ''; notStarted = helpers.defaultNullOpts.mkStr " " '' This option can be used to stipulate which symbols shall be used when updating a parent to-do's status when a child to-do's status is changed. This is **not required**: if `toDo.symbols` is customized but this option is not provided, the plugin will attempt to infer what the meanings of the symbols in your list are by their order. For example, if you set `toDo.symbols` as `[" " "⧖" "✓"]`, `" "` will be assigned to `toDo.notStarted`, "⧖" will be assigned to `toDo.inProgress`, etc. If more than three symbols are specified, the first will be used as `notStarted`, the second will be used as `inProgress`, and the last will be used as `complete`. If two symbols are provided (e.g. `" ", "✓"`), the first will be used as both `notStarted` and `inProgress`, and the second will be used as `complete`. `toDo.notStarted` stipulates which symbol represents a not-yet-started to-do. ''; inProgress = helpers.defaultNullOpts.mkStr "-" '' This option can be used to stipulate which symbols shall be used when updating a parent to-do's status when a child to-do's status is changed. This is **not required**: if `toDo.symbols` is customized but this option is not provided, the plugin will attempt to infer what the meanings of the symbols in your list are by their order. For example, if you set `toDo.symbols` as `[" " "⧖" "✓"]`, `" "` will be assigned to `toDo.notStarted`, "⧖" will be assigned to `toDo.inProgress`, etc. If more than three symbols are specified, the first will be used as `notStarted`, the second will be used as `inProgress`, and the last will be used as `complete`. If two symbols are provided (e.g. `" ", "✓"`), the first will be used as both `notStarted` and `inProgress`, and the second will be used as `complete`. `toDo.inProgress` stipulates which symbol represents an in-progress to-do. ''; complete = helpers.defaultNullOpts.mkStr "X" '' This option can be used to stipulate which symbols shall be used when updating a parent to-do's status when a child to-do's status is changed. This is **not required**: if `toDo.symbols` is customized but this option is not provided, the plugin will attempt to infer what the meanings of the symbols in your list are by their order. For example, if you set `toDo.symbols` as `[" " "⧖" "✓"]`, `" "` will be assigned to `toDo.notStarted`, "⧖" will be assigned to `toDo.inProgress`, etc. If more than three symbols are specified, the first will be used as `notStarted`, the second will be used as `inProgress`, and the last will be used as `complete`. If two symbols are provided (e.g. `" ", "✓"`), the first will be used as both `notStarted` and `inProgress`, and the second will be used as `complete`. `toDo.complete` stipulates which symbol represents a complete to-do. ''; }; tables = { trimWhitespace = helpers.defaultNullOpts.mkBool true '' Whether extra whitespace should be trimmed from the end of a table cell when a table is formatted. ''; formatOnMove = helpers.defaultNullOpts.mkBool true '' Whether tables should be formatted each time the cursor is moved via `MkdnTableNext/PrevCell` or `MkdnTableNext/Prev/Row`. ''; autoExtendRows = helpers.defaultNullOpts.mkBool false '' Whether a new row should automatically be added to a table when `:MkdnTableNextRow` is triggered when the cursor is in the final row of the table. If `false`, the cursor will simply leave the table for the next line. ''; autoExtendCols = helpers.defaultNullOpts.mkBool false '' Whether a new column should automatically be added to a table when `:MkdnTableNextCell` is triggered when the cursor is in the final column of the table. If false, the cursor will jump to the first cell of the next row, unless the cursor is already in the last row, in which case nothing will happen. ''; }; yaml = { bib = { override = helpers.defaultNullOpts.mkBool false '' Whether or not a bib path specified in a yaml block should be the only source considered for bib references in that file. ''; }; }; mappings = helpers.defaultNullOpts.mkNullable ( with types; attrsOf ( either (enum [ false ]) (submodule { options = { modes = mkOption { type = either str (listOf str); description = '' Either a string or list representing the mode(s) that the mapping should apply in. ''; example = [ "n" "v" ]; }; key = mkOption { type = str; description = "String representing the keymap."; example = "<Space>"; }; }; }) ) ) '' { MkdnEnter = { modes = ["n" "v" "i"]; key = "<CR>"; }; MkdnTab = false; MkdnSTab = false; MkdnNextLink = { modes = "n"; key = "<Tab>"; }; MkdnPrevLink = { modes = "n"; key = "<S-Tab>"; }; MkdnNextHeading = { modes = "n"; key = "]]"; }; MkdnPrevHeading = { modes = "n"; key = "[["; }; MkdnGoBack = { modes = "n"; key = "<BS>"; }; MkdnGoForward = { modes = "n"; key = "<Del>"; }; MkdnFollowLink = false; # see MkdnEnter MkdnCreateLink = false; # see MkdnEnter MkdnCreateLinkFromClipboard = { modes = ["n" "v"]; key = "<leader>p"; }; # see MkdnEnter MkdnDestroyLink = { modes = "n"; key = "<M-CR>"; }; MkdnMoveSource = { modes = "n"; key = "<F2>"; }; MkdnYankAnchorLink = { modes = "n"; key = "ya"; }; MkdnYankFileAnchorLink = { modes = "n"; key = "yfa"; }; MkdnIncreaseHeading = { modes = "n"; key = "+"; }; MkdnDecreaseHeading = { modes = "n"; key = "-"; }; MkdnToggleToDo = { modes = ["n" "v"]; key = "<C-Space>"; }; MkdnNewListItem = false; MkdnNewListItemBelowInsert = { modes = "n"; key = "o"; }; MkdnNewListItemAboveInsert = { modes = "n"; key = "O"; }; MkdnExtendList = false; MkdnUpdateNumbering = { modes = "n"; key = "<leader>nn"; }; MkdnTableNextCell = { modes = "i"; key = "<Tab>"; }; MkdnTablePrevCell = { modes = "i"; key = "<S-Tab>"; }; MkdnTableNextRow = false; MkdnTablePrevRow = { modes = "i"; key = "<M-CR>"; }; MkdnTableNewRowBelow = { modes = "n"; key = "<leader>ir"; }; MkdnTableNewRowAbove = { modes = "n"; key = "<leader>iR"; }; MkdnTableNewColAfter = { modes = "n"; key = "<leader>ic"; }; MkdnTableNewColBefore = { modes = "n"; key = "<leader>iC"; }; MkdnFoldSection = { modes = "n"; key = "<leader>f"; }; MkdnUnfoldSection = { modes = "n"; key = "<leader>F"; }; } '' '' An attrs declaring the key mappings. The keys should be the name of a commands defined in `mkdnflow.nvim/plugin/mkdnflow.lua` (see `:h Mkdnflow-commands` for a list). Set to `false` to disable a mapping. ''; }; config = let setupOptions = with cfg; { modules = with modules; { inherit bib buffers conceal cursor folds links lists maps paths tables yaml ; }; inherit filetypes; create_dirs = createDirs; perspective = with perspective; { inherit priority fallback; root_tell = rootTell; nvim_wd_heel = nvimWdHeel; inherit update; }; inherit wrap; bib = with bib; { default_path = defaultPath; find_in_root = findInRoot; }; inherit silent; links = with links; { inherit style conceal context; implicit_extension = implicitExtension; transform_implicit = transformImplicit; transform_explicit = transformExplicit; }; to_do = with toDo; { inherit symbols; update_parents = updateParents; not_started = notStarted; in_progress = inProgress; inherit complete; }; tables = with tables; { trim_whitespace = trimWhitespace; format_on_move = formatOnMove; auto_extend_rows = autoExtendRows; auto_extend_cols = autoExtendCols; }; yaml = with yaml; { bib = with bib; { inherit override; }; }; mappings = helpers.ifNonNull' mappings ( mapAttrs ( action: options: if isBool options then options else [ options.modes options.key ] ) mappings ); } // cfg.extraOptions; in mkIf cfg.enable { extraPlugins = [ cfg.package ]; extraConfigLua = '' require("mkdnflow").setup(${helpers.toLuaObject setupOptions}) ''; }; }