12/12/23: Mkdnflow no longer requires the luautf8
lua rock as a dependency. UTF-8 characters can be used as custom to-do symbols out of the box, and table formatting will work out of the box when the table contains UTF-8 characters.
Mkdnflow is designed for the fluent navigation of documents and notebooks (AKA "wikis") written in markdown. The plugin's flexibility and its prioritization of markdown also means it can become part of your webdev workflow if you use static site generators like Jekyll or Hugo, which can generate static sites from markdown documents.
The plugin is an extended set of functions and mappings to those functions which make it easy to navigate and manipulate markdown documents and notebooks in Neovim. I originally started writing Mkdnflow to replicate some features from Vimwiki in Lua instead of Vimscript, but my current goal for this project is to make this plugin as useful as possible for anyone using Neovim who maintains a set of markdown notes and wishes to efficiently navigate those notes and keep them organized and connected. The plugin now includes some convenience features I wished Vimwiki had, including functionality to rename the source part of a link and its associated file simultaneously.
I keep tabs on the project's issues and appreciate feature requests, suggestions, and bug reports. I invite you to use the discussion board if you would like to share your config, share how Mkdnflow fits into your workflow, get help with setup, or contribute feature suggestions or optimizations. Contributions to the plugin are welcome: fork this repo and submit a pull request with your changes or additions. For Lua resources, see this page or call :h lua
or :h api
in Neovim.
yaml
)[name](source)
[name][label]
followed anywhere in the file by [label]: source
, where label
is an integersource
can optionally be surrounded by <
and >
source
can optionally be followed by a title, following any of the formats specified here[[source]]
[[source|name]]
[Link](source.md)
to Link
[[source|Link]]
to Link
or from [[source]]
to source
markdown
and markdown_inline
installed and enabled in markdown filetypes, and in your .vimrc
or init.lua
, enable conceal (set conceallevel=2
or vim.wo.conceallevel = 2
).<CR>
on various kinds of links to "follow" them:.md
links open in the current window.md
links relative to home open in the current window but are interpreted with absolute perspective (e.g. [File](/home/user/file.md)
/[File](C:\Users\user\file.md)
on Windows, or [File](~/Documents/file.md)
)file:
(e.g. [My Xournal notes](file:notes.xopp)
) open with the system's default program for that filetype## Bills to pay
will be jumped to if the path in the anchor link is #bills-to-pay
#### Groceries/other things to buy
will be jumped to if the path in the anchor link is #groceriesother-things-to-buy
[Link](grocery_list.md#produce)
) will open the file in the current window and jump to a bracketed span or heading matching the #
attribute<https://example.org>
) are followed directly<CR>
on citations to open associated files or websites (e.g. @Chomsky1957
, with or without brackets around it)perspective.priority
is root
, simply place your bib files to be searched in your notebook's root directory.howpublished
field is opened.<CR>
ing on @dschrute
in a markdown document would take you to the associated Slack thread.@misc{dschrute,
url={https://dundermifflin.slack.com/archives/P07BFJD82}
}
new_file_template.template
) that gets populated and inserted into new markdown files.{{title}}
or {{ title }}
new_file_template.placeholders
).placeholders.before
or placeholders.after
.{{ title }}
and {{ date }}
are assigned by default to "link_title"
and "os_date"
but can be overridden. "link_title"
refers to the title of the file, as determined by the label of the the link that led to the new document. "os_date"
refers to the system time (formatted as YYYY-MM-DD).In the following example, {{ date }}
will be filled in based on the result of evaluating the date
function at the exact moment one tries to follow a link. {{ filename }}
will be filled in based on the result of evaluating the filename
function after the new buffer has been opened (thereby inserting the filename of the newly-opened buffer, rather than the previous one).
new_file_template = {
template = [[
# {{ title }}
Date: {{ date }}
Filename: {{ filename }}
]],
placeholders = {
before = {
date = function()
return os.date("%A, %B %d, %Y") -- Wednesday, March 1, 2023
end
},
after = {
filename = function()
return vim.api.nvim_buf_get_name(0)
end
}
}
}
perspective.priority
to root
in your config, and pass a file as the value of perspective.root_tell
. The tell is the name of a single file that can be used to identify the root directory (e.g. index.md
, .git
, .root
, .wiki_root
, etc.). See the default config for how to configure the perspective
table./
or ~/
). Links within this file will still receive the relative interpretation, so this is best for references out of the project directory to markdown files without their own dependencies (unless those dependencies are within the project directory).<CR>
on the word under cursor or visual selection to create a notebook-internal linklinks
config key for customization instructions.<leader>p
on the word under cursor or visual selection to create a link using the system clipboard's content as the source<M-CR>
(Alt-Enter) when your cursor is anywhere in a link to destroy it (replace it with the text in [...])#
<M-CR>
in visual mode) using the style specified in the Pandoc bracketed_spans
extension (ID must be assigned with the ID selector—i.e. #
): [This is a span]{#important-span}
.yaa
("yank as anchor link"; formerly ya
) on a heading or bracketed span to add a formatted anchor link for the heading to the default register (ready to paste in the current window)yfa
to do the same, but adding the absolute path of the file before the anchor (for pasting in another buffer)[Some text the link was created from](sometextthelinkwascreatedfrom.md)
require('mkdnflow').setup({
links = {
transform_explicit = function(text)
-- Make lowercase, remove spaces, and reverse the string
return string.lower(text:gsub(' ', ''))
end
}
})
<Tab>
and <S-Tab>
jump to the next and previous links in the file links.style
is set to ('markdown' or 'wiki'), but you can also define your own jump patterns under the cursor
config table.]]
and [[
jump to the next and previous headings in the file, respectivelyrequire("mkdnflow").cursor.goTo(pattern)
to jump to arbitrarily-defined Lua regex matches (see :h Mkdnflow-cursor-goTo
)nvim-cmp
.md
files in the notebook..bib
files (see bib
).cmp
key.To enable completion via cmp
using the provided source, add mkdnflow
as one of the sources in your cmp
setup function.
cmp.setup({
sources = cmp.config.sources({
-- Other cmp sources
{ name = 'mkdnflow' }, -- Add this
-- Other cmp sources
}),
})
Also, don't forget to edit your formatting
options in cmp.setup
.
NOTE: There may be some compatibility issues with the completion module and links.transform_explicit
/links.transform_implicit
functions:
transform_explicit
option for links to organizing in folders then the folder name will be inserted accordingly. Some transformations may not work as expected in completions.[author_year](author_year.md)
and you save the file as ref_author_year.md
. The condition can be if the link name ends with _yyyy. Now cmp
will complete it as [ref_author_year](ref_author_year.md)
(without the transformation applied). Next, when you follow the link completed by cmp
, you will go to a new file that is saved as ref_ref_author_year.md
, which of course does not refer to the intended file.To prevent this, make sure you write sensible transformation functions, preferably using it for folder organization. The other solution is to do a full text search in all the files for links.
Credit: We heavily referenced cmp-pandoc-references and code from other completion sources when writing the cmp
module.
MkdnMoveSource
(mapped to <F2>
by default) to rename a link's source and rename/move the linked file simultaneously<BS>
to go backward (to the previous file/buffer opened in the current window, like clicking the back button in a web browser)<Del>
to go forward (to the subsequent file/buffer opened in the current window, like clicking the forward button in a web browser)filetypes
config table)maps
module)+
/-
by default). Note: Increasing the heading means increasing it in importance (i.e. making it bigger or more prominent when converted to HTML and rendered in a browser), which counterintuitively means removing a hash symbol.<CR>
in normal mode if the cursor is on the heading of the section<CR>
or <leader>F
(both are default mappings; the former maps to a wrapper function that will follow links if the cursor is not on a fold or section heading; the latter is mapped specifically to :MkdnUnfoldSection<CR>
)<CR>
), you'll need to do so by making a visual selection of the text you wish to create a link from and then hitting <CR>
, or otherwise disabling the mapping for MkdnEnter
and mapping MkdnFollowLink
to <CR>
in visual and normal modes.<leader>f
emoji
, nerdfont
(requires you to be using a nerdfont or patched font in your interface), plain
(non-emoji unicode symbols)—see the table below—and the icons are customizable/overridable.Object type | Name | emoji icon |
nerdfont icon |
plain icon |
---|---|---|---|---|
Table | tbl |
📈 | ⊞ | |
Unordered (bulleted) list | ul |
*️⃣ | • | |
Ordered (numbered) list | ol |
1️⃣ | ① | |
To-do list | todo |
✅ | ☑ | |
Image | img |
🖼️ | ⧉ | |
Fenced code block | fncblk |
🧮 | ‵ | |
Section | sec |
🏷️ | § | |
Paragraph | par |
📃 | ¶ | |
Link | link |
🔗 | ⇔ |
-
, *
, and +
<C-Space>
by default). Using the default settings, toggling will result in the following changes. To-do symbols can be customized.* [ ] ...
→ * [-] ...
* [-] ...
→ * [X] ...
* [X] ...
→ * [ ] ...
<C-Space>
by default)<C-Space>
by default)<leader>nn
(default mapping) or :MkdnUpdateNumbering 0<CR>
, e.g., if you want to start numbering at 0<CR>
ing in lists (NOTE: currently not enabled by default. See below.)<CR>
in insert mode (see the following code block).require('mkdnflow').setup({
mappings = {
MkdnEnter = {{'i', 'n', 'v'}, '<CR>'} -- This monolithic command has the aforementioned
-- insert-mode-specific behavior and also will trigger row jumping in tables. Outside
-- of lists and tables, it behaves as <CR> normally does.
-- MkdnNewListItem = {'i', '<CR>'} -- Use this command instead if you only want <CR> in
-- insert mode to add a new list item (and behave as usual outside of lists).
}
})
x
columns and y
rows with :MkdnTable x y
. Table headers are added automatically; to exclude headers, use :MkdnTable x y noh
:MkdnTableFormat
<Tab>
and <S-Tab>
in insert mode by default)<M-CR>
in insert mode by default; jumping forward is not mapped to anything by default; see MkdnEnter
or MkdnTableNextRow
in default mappings)modules
config option descriptions)
Specify multiple bib sources:
```markdown
---
bib:
- /home/user/Documents/Library/library.bib
- /home/user/Projects/special_project/refs.bib
---
require('lazy').setup({
-- Your other plugins
{
'jakewvincent/mkdnflow.nvim',
config = function()
require('mkdnflow').setup({
-- Config goes here; leave blank for defaults
})
end
}
-- Your other plugins
})
require('pckr').add({
-- Your other plugins
{
'jakewvincent/mkdnflow.nvim',
config = function()
require('mkdnflow').setup({
-- Config goes here; leave blank for defaults
})
end
}
-- Your other plugins
})
require('paq')({
-- Your other plugins
'jakewvincent/mkdnflow.nvim',
-- Your other plugins
})
-- Include the setup function somewhere else in your init.lua/vim file, or the
-- plugin won't activate itself:
require('mkdnflow').setup({
-- Config goes here; leave blank for defaults
})
use({'jakewvincent/mkdnflow.nvim',
config = function()
require('mkdnflow').setup({
-- Config goes here; leave blank for defaults
})
end
})
" Vim-Plug
Plug 'jakewvincent/mkdnflow.nvim'
" NeoBundle
NeoBundle 'jakewvincent/mkdnflow.nvim'
" Vundle
Bundle 'jakewvincent/mkdnflow.nvim'
" Pathogen
git clone https://github.com/jakewvincent/mkdnflow.nvim.git ~/.vim/bundle/mkdownflow.nvim
" Dein
call dein#add('jakewvincent/mkdnflow.nvim')
" Include the setup function somewhere else in your init.vim file, or the
" plugin won't activate itself:
lua << EOF
require('mkdnflow').setup({
-- Config goes here; leave blank for defaults
})
EOF
All functionality of the plugin should now work on all operating systems, including Windows! However, since I don't use Windows on my daily driver, there may be edge cases that cause trouble. Please file an issue if anything comes up.
As long as you successfully installed Mkdnflow, you don't need to do anything special to start using the plugin. All of the plugin's features will be enabled for any markdown file (or for any filetype you specify under the filetypes
config key). If you would like to start a notebook (AKA "wiki"), first create a directory for it. If you're using Neovim in the terminal, simply enter nvim index.md
and start writing. I suggest using index.md
as a landing page/table of contents that contains links to all other notes in your notebook. If you use such a landing page, try setting perspective.priority
in your Mkdnflow config to 'root'
and your perspective.root_tell
to 'index.md'
so that Mkdnflow can identify your notebook's root directory and reliably interpret links relative to this directory.
Currently, the setup function uses the defaults shown below. See the descriptions and non-default options in the section below the following block. To use these defaults, simply pass no arguments setup function: require('mkdnflow').setup()
. To change these settings, specify new values for any of them them in the setup function.
-- ** DEFAULT SETTINGS; TO USE THESE, PASS NO ARGUMENTS TO THE SETUP FUNCTION **
require('mkdnflow').setup({
modules = {
bib = true,
buffers = true,
conceal = true,
cursor = true,
folds = true,
foldtext = true,
links = true,
lists = true,
maps = true,
paths = true,
tables = true,
yaml = false,
cmp = false
},
filetypes = {md = true, rmd = true, markdown = true},
create_dirs = true,
perspective = {
priority = 'first',
fallback = 'current',
root_tell = false,
nvim_wd_heel = false,
update = false
},
wrap = false,
bib = {
default_path = nil,
find_in_root = true
},
silent = false,
cursor = {
jump_patterns = nil
},
links = {
style = 'markdown',
name_is_source = false,
conceal = false,
context = 0,
implicit_extension = nil,
transform_implicit = false,
transform_explicit = function(text)
text = text:gsub(" ", "-")
text = text:lower()
text = os.date('%Y-%m-%d_')..text
return(text)
end,
create_on_follow_failure = true
},
new_file_template = {
use_template = false,
placeholders = {
before = {
title = "link_title",
date = "os_date"
},
after = {}
},
template = "# {{ title }}"
},
to_do = {
symbols = {' ', '-', 'X'},
update_parents = true,
not_started = ' ',
in_progress = '-',
complete = 'X'
},
foldtext = {
object_count = true,
object_count_icons = 'emoji',
object_count_opts = function()
return require('mkdnflow').foldtext.default_count_opts()
end,
line_count = true,
line_percentage = true,
word_count = false,
title_transformer = nil,
separator = ' · ',
fill_chars = {
left_edge = '⢾',
right_edge = '⡷',
left_inside = ' ⣹',
right_inside = '⣏ ',
middle = '⣿',
},
},
tables = {
trim_whitespace = true,
format_on_move = true,
auto_extend_rows = false,
auto_extend_cols = false,
style = {
cell_padding = 1,
separator_padding = 1,
outer_pipes = true,
mimic_alignment = true
}
},
yaml = {
bib = { override = false }
},
mappings = {
MkdnEnter = {{'n', 'v'}, '<CR>'},
MkdnTab = false,
MkdnSTab = false,
MkdnNextLink = {'n', '<Tab>'},
MkdnPrevLink = {'n', '<S-Tab>'},
MkdnNextHeading = {'n', ']]'},
MkdnPrevHeading = {'n', '[['},
MkdnGoBack = {'n', '<BS>'},
MkdnGoForward = {'n', '<Del>'},
MkdnCreateLink = false, -- see MkdnEnter
MkdnCreateLinkFromClipboard = {{'n', 'v'}, '<leader>p'}, -- see MkdnEnter
MkdnFollowLink = false, -- see MkdnEnter
MkdnDestroyLink = {'n', '<M-CR>'},
MkdnTagSpan = {'v', '<M-CR>'},
MkdnMoveSource = {'n', '<F2>'},
MkdnYankAnchorLink = {'n', 'yaa'},
MkdnYankFileAnchorLink = {'n', 'yfa'},
MkdnIncreaseHeading = {'n', '+'},
MkdnDecreaseHeading = {'n', '-'},
MkdnToggleToDo = {{'n', 'v'}, '<C-Space>'},
MkdnNewListItem = false,
MkdnNewListItemBelowInsert = {'n', 'o'},
MkdnNewListItemAboveInsert = {'n', 'O'},
MkdnExtendList = false,
MkdnUpdateNumbering = {'n', '<leader>nn'},
MkdnTableNextCell = {'i', '<Tab>'},
MkdnTablePrevCell = {'i', '<S-Tab>'},
MkdnTableNextRow = false,
MkdnTablePrevRow = {'i', '<M-CR>'},
MkdnTableNewRowBelow = {'n', '<leader>ir'},
MkdnTableNewRowAbove = {'n', '<leader>iR'},
MkdnTableNewColAfter = {'n', '<leader>ic'},
MkdnTableNewColBefore = {'n', '<leader>iC'},
MkdnFoldSection = {'n', '<leader>f'},
MkdnUnfoldSection = {'n', '<leader>F'}
}
})
modules
(dictionary-like table)modules.bib
(required for parsing bib files and following citations)modules.buffers
(required for backward and forward navigation through buffers)modules.conceal
(required if you wish to enable link concealing; note that you must declare links.conceal
as true
in addition to leaving this module enabled [it is enabled by default] if you wish to conceal links)modules.cursor
(required for jumping to links and headings; yanking anchor links)modules.folds
(required for folding by section)modules.links
(required for creating and destroying links and following links)modules.lists
(required for manipulating lists, toggling to-do list items, etc.)modules.maps
(required for setting mappings via the mappings table; set to false
if you wish to set mappings outside of the plugin)modules.paths
(required for link interpretation and following links)modules.tables
(required for table navigation and formatting)modules.yaml
(required for parsing yaml blocks)modules.cmp
(required if you wish to enable Completion for nvim-cmp
)create_dirs
(boolean)true
(default): Directories referenced in a link will be (recursively) created if they do not existfalse
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
(dictionary-like table)perspective.priority
(string): 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)perspective.root_tell
(string or boolean)'<any file name>'
: 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
.perspective.fallback
(string): Specifies the backup perspective to take if priority isn't possible (e.g. if it is 'root'
but no root directory is found)'first'
: (see above)'current'
(default): (see above)'root'
: (see above)perspective.nvim_wd_heel
(boolean): Specifies whether changes in perspective will result in corresponding changes to Neovim's working directorytrue
: 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.perspective.update
(boolean): 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 root_tell
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.filetypes
(dictionary-like table)<any arbitrary filetype extension>
(boolean value)true
: A matching extension will enable the plugin's functionality for a file with that extensionNOTE: 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.
wrap
(boolean)true
: When jumping to next/previous links or headings, the cursor will continue searching at the beginning/end of the filefalse
(default): When jumping to next/previous links or headings, the cursor will stop searching at the end/beginning of the filebib
(dictionary-like table)bib.default_path
(string or nil
): Specifies a path to a default .bib file to look for citation keys in (need not be in root directory of notebook)bib.find_in_root
(boolean)true
(default): 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
(boolean)true
: The plugin will not display any messages in the console except compatibility warnings related to your configfalse
(default): The plugin will display messages to the console (all messages from the plugin start with ⬇️ )cursor
(dictionary-like table)cursor.jump_patterns
(nil or table): A list of Lua regex patterns to jump to using :MkdnNextLink
and :MkdnPrevLink
nil
(default): When nil
, the default jump patterns for the configured link style are used (markdown-style links by default){}
(empty table) to disable link jumping without disabling the cursor
modulelinks
(dictionary-like table)links.style
(string)'markdown'
(default): Links will be expected in the standard markdown format: [<title>](<source>)
'wiki'
: Links will be expected in the unofficial wiki-link style, specifically the title-after-pipe format: [[<source>|<title>]]
.links.name_is_source
(boolean)true
: Wiki-style links will be created with the source and name being the same (e.g. [[Link]]
will display as "Link" and go to a file named "Link.md")false
(default): Wiki-style links will be created with separate name and source (e.g. [[link-to-source|Link]]
will display as "Link" and go to a file named "link-to-source.md")links.conceal
(boolean)true
: Link sources and delimiters will be concealed (depending on which link style is selected)false
(default): Link sources and delimiters will not be concealed by mkdnflowlinks.context
(number)0
(default): When following or jumping to links, assume no link will be split over multiple linesn
(an integer): 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)links.implicit_extension
(string) A string that instructs the plugin (a) how to interpret links to files that do not have an extension, and (b) how to create new links from the word under cursor or text selection.nil
(default): Extensions will be explicit when a link is created and must be explicit in any notebook link.<any extension>
(e.g. 'md'
): Links without an extension (e.g. [Homepage](index)
) will be interpreted with the implicit extension (e.g. index.md
), and new links will be created without an extension.links.transform_explicit
(function or 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. (FYI: The previous functionality specified under the prefix
key has been migrated here to provide greater flexibility.)function(input)
return(string.upper(os.date('%Y-')..input))
end
links.transform_implicit
(function or false
): 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:function(input)
if input:match('%d%d%d%d%-%d%d%-%d%d') then
return('journals/'..input)
else
return('pages/'..input)
end
end
links.create_on_follow_failure
(boolean): Whether a link should be created if there is no link to follow under the cursor.true
(default): Create a link if there's no link to followfalse
: Do not create a link if there's no link to follownew_file_template
(dictionary-like table)new_file_template.use_template
(boolean)true
: the template is filled in (if it contains placeholders) and inserted into any new buffers entered by following a link to a buffer that doesn't exist yetfalse
: no templates are filled in and inserted into new buffersnew_file_template.placeholders
(dictionary-like table)new_file_template.placeholders.before
(dictionary-like table) A table whose keys are placeholder names pointing to functions to be evaluated immediately before the buffer is opened in the current windownew_file_template.placeholders.after
(dictionary-like table) A table hose keys are placeholder names pointing to functions to be evaluated immediately after the buffer is opened in the current windownew_file_template.template
(string) A string, optionally containing placeholder names, that will be inserted into new buffersto_do
(dictionary-like table)to_do.symbols
(array-like table): 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
).to_do.update_parents
(boolean): 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 changedfalse
: To-do items can be toggled, but parent to-do statuses (if any) will not be automatically changedto_do.symbols
is customized but these options are 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 to_do.symbols
as {' ', '⧖', '✓'}
, ' '
will be assiged to to_do.not_started
, '⧖' will be assigned to to_do.in_progress
, etc. If more than three symbols are specified, the first will be used as not_started
, the second will be used as in_progress
, and the last will be used as complete
. If two symbols are provided (e.g. ' ', '✓'
), the first will be used as both not_started
and in_progress
, and the second will be used as complete
.to_do.not_started
(string): Stipulates which symbol represents a not-yet-started to-do (default: ' '
)to_do.in_progress
(string): Stipulates which symbol represents an in-progress to-do (default: '-'
)to_do.complete
(string): Stipulates which symbol represents a complete to-do (default: 'X'
)foldtext
(dictionary-like table)foldtext.object_count
(boolean): Whether to show a count of all the objects inside of a folded section (default: true
)foldtext.object_count_icon_set
(string or dictionary-like table): Which icon set to use to represent the counted objects. The pre-packaged icon sets are named 'emoji'
(default), 'plain'
, and 'nerdfont'
.foldtext.object_count_opts
(function => table): A function returning a dictionary-like table specifying various attributes of the objects to be counted (default: function() return require('mkdnflow').foldtext.default_count_opts end
), where the keys are the names of the objects to be counted. If the names are one of the {name}
s tbl
, ul
, ol
, todo
, img
, fncblk
, sec
, par
, or link
, any of the following entries will be filled in with the default value if you do not provide a value for it in your custom table (see below this list for a sample foldtext configuration 'recipe'):foldtext.object_count_opts().{name}.icon
(string): The icon to use to represent the counted object (default: the value for {name}
in the emoji icon set, or if another icon set is named, the value for {name}
in whichever icon set you've specified)foldtext.object_count_opts().{name}.count_method.prep
(function => string): A function that performs any preprocessing manipulations to the text before the pattern is used to count objects according to the tallying method specified. This may be useful if it helps you write a simpler pattern (default: Only tbl
[table] and fncblk
[fenced code block] have default preprocessing functions:tbl
's preprocessor strips wiki-style links from the document so that the vertical bar is not counted as part of a tablefncblk
's preprocessor adds a newline character to the beginning of the section if the section starts immediately with a fenced code block).foldtext.object_count_opts().{name}.count_method.pattern
(table): An array-like table of strings (Lua patterns). Used differently depending on the object type's corresponding tally method (see below).foldtext.object_count_opts().{name}.count_method.tally
(string): One of three tallying methods to use for the object type: 'blocks'
, 'line_matches'
, or 'global_matches'
. The effects of each of these are as follows:'blocks'
: If this tally method is used for an object type, all contiguous blocks of lines matching the pattern(s) for a particular type are counted. (Patterns for this method need to cause a successful match if part of a multi-line object occurs on the line—for instance, '^[-*] '
will match a line with an unordered list item using *
or -
as an item marker.) tbl
, ul
, ol
, and todo
use this method by default.'line_matches'
: If this tally method is used for an object type, one or more matches on a line will count as one match. (Patterns for this method need to cause a successful match if the object occurs on the line—for instance, '^#+%s'
will match a section heading beginning with at least one hash.) sec
uses this method by default.'global_matches'
: If this tally method is used for an object type, every match of an instance across the entire fold section is counted individually. Patterns may take multiple lines into account because the string searched is a concatenation of all lines in the folded section (separated by newlines characters \n
). (Patterns for this method should match every individual occurrence of the object—for instance, '%b[]%b()'
will match every markdown-style link.) img
, fncblk
, par
, and link
use this method by default.foldtext.line_count
(boolean): Whether to show the count of lines contained in the fold (default: true
)foldtext.line_percentage
(boolean): Whether to show the percentage of document lines contained in the fold (default: true
)foldtext.word_count
(boolean): Whether to show the count of words contained in the fold (default: false
)foldtext.title_transformer
(function => [function => string]): A function that returns a function that returns a string. This function accepts a string (the text of the first line in the fold [a section heading]) and returns a transformed string for use in the foldtext. (default: function() require('mkdnflow').foldtext.default_title_transformer end
)foldtext.fill_chars
(dictionary-like table)foldtext.fill_chars.left_edge
(string): The character(s) to use at the very left edge of the foldtext, adjacent to the left edge of the window (default: '⢾⣿⣿'
)foldtext.fill_chars.right_edge
(string): The character(s) to use at the very right edge of the foldtext, adjacent to the right edge of the window (default: '⣿⣿⡷'
)foldtext.fill_chars.item_separator
(string): The character(s) used to separate the items within a section, such as the various object counts (default: ' · '
)foldtext.fill_chars.section_separator
(string): The character(s) used to separate adjacent sections (default: ' ⣹⣿⣏ '
). At time of writing, the only adjacent sections are the item-count section and the line- and word-count section (both on the right end of the foldtext). The section title is a separate section (on the left) but is not adjacent to any other sections.foldtext.fill_chars.left_inside
(string): The character(s) used to separate (default: ' ⣹'
)foldtext.fill_chars.right_inside
(string): (default: '⣏ '
)foldtext.fill_chars.middle
(string): (default: '⣿'
)-- SAMPLE FOLDTEXT CONFIGURATION RECIPE WITH COMMENTS
require('mkdnflow').setup({
-- Other config options
foldtext = {
title_transformer = function()
local function my_title_transformer(text)
local updated_title = text:gsub('%b{}', '')
updated_title = updated_title:gsub('^%s*', '')
updated_title = updated_title:gsub('%s*$', '')
updated_title = updated_title:gsub('^######', '░░░░░▓')
updated_title = updated_title:gsub('^#####', '░░░░▓▓')
updated_title = updated_title:gsub('^####', '░░░▓▓▓')
updated_title = updated_title:gsub('^###', '░░▓▓▓▓')
updated_title = updated_title:gsub('^##', '░▓▓▓▓▓')
updated_title = updated_title:gsub('^#', '▓▓▓▓▓▓')
return updated_title
end
return my_title_transformer
end,
object_count_icon_set = 'nerdfont', -- Use/fall back on the nerdfont icon set
object_count_opts = function()
local opts = {
link = false, -- Prevent links from being counted
blockquote = { -- Count block quotes (these aren't counted by default)
icon = ' ',
count_method = {
pattern = { '^>.+$' },
tally = 'blocks',
}
},
fncblk = {
-- Override the icon for fenced code blocks with
icon = ' '
}
}
return opts
end,
line_count = false, -- Prevent lines from being counted
word_count = true, -- Count the words in the section
fill_chars = {
left_edge = '╾─🖿 ─',
right_edge = '──╼',
item_separator = ' · ',
section_separator = ' // ',
left_inside = ' ┝',
right_inside = '┥ ',
middle = '─',
},
},
-- Other config options
})
The above recipe will produce foldtext like the following (for an h3-level section heading called My section
):
tables
(dictionary-like table)tables.trim_whitespace
(boolean): Whether extra whitespace should be trimmed from the end of a table cell when a table is formatted (default: true
)tables.format_on_move
(boolean): Whether tables should be formatted each time the cursor is moved via MkdnTable{Next/Prev}{Cell/Row} (default: true
)tables.auto_extend_rows
(boolean): Whether calling MkdnTableNextRow
when the cursor is in the last row should add another row instead of leaving the table (default: false
)tables.auto_extend_cols
(boolean): Whether calling MkdnTableNextCol
when the cursor is in the last cell should add another column instead of jumping to the first cell of the next row (default: false
)tables.style
(dictionary-like table)tables.style.cell_padding
(integer): Number of spaces to use as cell padding (default: 1
)tables.style.separator_padding
(integer): Number of spaces to use as cell padding in the row that separates a header row from the table body, if present (default: 1
)tables.style.outer_pipes
(boolean): Whether to use (true
) or exclude (false
) outer pipes when formatting a table or inserting a new table (default: true
)tables.style.mimic_alignment
(boolean): Whether to mimic the cell alignment indicated in the separator row when formatting the table; left-alignment always used when alignment not specified (default: true
)yaml
(dictionary-like table)yaml.bib
(dictionary-like table)yaml.bib.override
(boolean): Whether or not a bib path specified in a yaml block should be the only source considered for bib references in that file (default: false
)mappings
(dictionary-like table)mappings.<name of command>
(array-like table or false
)mappings.<name of command>[1]
string or array table representing the mode (or array of modes) that the mapping should apply in ('n'
, 'v'
, etc.)mappings.<name of command>[2]
string representing the keymap (e.g. '<Space>'
)mappings.<name of command> = false
to disable default mapping without providing a custom mappingNOTE: <name of command>
should be the name of a commands defined in mkdnflow.nvim/plugin/mkdnflow.lua
(see :h Mkdnflow-commands for a list).
I recommended turning on autowriteall
in Neovim for markdown filetypes. This will ensure that changes to buffers are saved when you navigate away from that buffer, e.g. by following a link to another file. See :h awa
. If you have hidden
enabled or if a buffer is hidden by bufhidden
, you may need to use the second option (thanks, @vandalt).
-- If you have an init.lua
vim.api.nvim_create_autocmd("FileType", {pattern = "markdown", command = "set awa"})
-- Use the following if your buffer is set to become hidden
--vim.api.nvim_create_autocmd("BufLeave", {pattern = "*.md", command = "silent! wall"})
" If you have an init.vim
autocmd FileType markdown set autowriteall
" Use the following if your buffer is set to become hidden
autocmd BufLeave *.md silent! wall
These default mappings can be disabled; see Configuration. Commands with no mappings trigger functions that are called by the functions with mappings, but I've given them a command name so you can use them as independent functions if you'd like to.
Keymap | Mode | Command | Description |
---|---|---|---|
<CR> |
n, v(, i) | :MkdnEnter<CR> |
Triggers a wrapper function which will (a) infer your editor mode, and then if in normal or visual mode, either follow a link, create a new link from the word under the cursor or visual selection, or fold a section (if cursor is on a section heading); if in insert mode, it will create a new list item (if cursor is in a list), go to the next row in a table (if cursor is in a table), or behave normally (if cursor is not in a list or a table) NOTE: There is no insert-mode mapping for this command by default since some may find its effects intrusive. To enable the insert-mode functionality, add to the mappings table: MkdnEnter = {{'i', 'n', 'v'}, '<CR>} |
<Tab> |
n | :MkdnNextLink<CR> |
Move cursor to the beginning of the next link (if there is a next link) |
<S-Tab> |
n | :MkdnPrevLink<CR> |
Move the cursor to the beginning of the previous link (if there is one) |
]] |
n | :MkdnNextHeading<CR> |
Move the cursor to the beginning of the next heading (if there is one) |
[[ |
n | :MkdnPrevHeading<CR> |
Move the cursor to the beginning of the previous heading (if there is one) |
<BS> |
n | :MkdnGoBack<CR> |
Open the historically last-active buffer in the current window |
<Del> |
n | :MkdnGoForward<CR> |
Open the buffer that was historically navigated away from in the current window |
-- | -- | :MkdnCreateLink<CR> |
Create a link from the word under the cursor (in normal mode) or from the visual selection (in visual mode) |
<leader>p |
n, v | :MkdnCreateLinkFromClipboard<CR> |
Create a link, using the content from the system clipboard (e.g. a URL) as the source and the word under cursor or visual selection as the link text |
-- | -- | :MkdnFollowLink<CR> |
Open the link under the cursor, creating missing directories if desired, or if there is no link under the cursor, make a link from the word under the cursor |
<M-CR> |
n | :MkdnDestroyLink<CR> |
Destroy the link under the cursor, replacing it with just the text from [...] |
<M-CR> |
v | :MkdnTagSpan<CR> |
Tag a visually-selected span of text with an ID, allowing it to be linked to with an anchor link |
<F2> |
n | :MkdnMoveSource<CR> |
Open a dialog where you can provide a new source for a link and the plugin will rename and move the associated file on the backend (and rename the link source) |
yaa |
n | :MkdnYankAnchorLink<CR> |
Yank a formatted anchor link (if cursor is currently on a line with a heading) |
yfa |
n | :MkdnYankFileAnchorLink<CR> |
Yank a formatted anchor link with the filename included before the anchor (if cursor is currently on a line with a heading) |
+ |
n | :MkdnIncreaseHeading<CR> |
Increase heading importance (remove hashes) |
- |
n | :MkdnDecreaseHeading<CR> |
Decrease heading importance (add hashes) |
<C-Space> |
n | :MkdnToggleToDo<CR> |
Toggle to-do list item's completion status or convert a list item into a to-do list item |
<leader>nn |
n | :MkdnUpdateNumbering<CR> |
Update numbering for all siblings of the list item of the current line |
-- | -- | :MkdnNewListItem<CR> |
Add a new ordered list item, unordered list item, or (uncompleted) to-do list item |
o |
n | :MkdnNewListItemBelowInsert<CR> |
Add a new ordered list item, unordered list item, or (uncompleted) to-do list item below the current line and begin insert mode. Add a new line and enter insert mode when the cursor is not in a list. |
O |
n | :MkdnNewListItemAboveInsert<CR> |
Add a new ordered list item, unordered list item, or (uncompleted) to-do list item above the current line and begin insert mode. Add a new line and enter insert mode when the cursor is not in a list. |
-- | -- | :MkdnExtendList<CR> |
Like above, but the cursor stays on the current line (new list items of the same typ are added below) |
-- | -- | :MkdnTable ncol nrow (noh) |
Make a table of ncol columns and nrow rows. Pass 'noh' as a third argument to exclude table headers. |
-- | -- | :MkdnTableFormat<CR> |
Format a table under the cursor |
<Tab> |
i | :MkdnTableNextCell<CR> |
Move the cursor to the beginning of the next cell in the table, jumping to the next row if needed |
<S-Tab> |
i | :MkdnTablePrevCell<CR> |
Move the cursor to the beginning of the previous cell in the table, jumping to the previous row if needed |
<leader>ir |
n | :MkdnTableNewRowBelow<CR> |
Add a new row below the row the cursor is currently in |
<leader>iR |
n | :MkdnTableNewRowAbove<CR> |
Add a new row above the row the cursor is currently in |
<leader>ic |
n | :MkdnTableNewColAfter<CR> |
Add a new column following the column the cursor is currently in |
<leader>iC |
n | :MkdnTableNewColBefore<CR> |
Add a new column before the column the cursor is currently in |
-- | -- | :MkdnTab<CR> |
Wrapper function which will jump to the next cell in a table (if cursor is in a table) or indent an (empty) list item (if cursor is in a list item) |
-- | -- | :MkdnSTab<CR> |
Wrapper function which will jump to the previous cell in a table (if cursor is in a table) or de-indent an (empty) list item (if cursor is in a list item) |
<leader>f |
-- | :MkdnFoldSection<CR> |
Fold the section the cursor is currently on/in |
<leader>F |
-- | :MkdnUnfoldSection<CR> |
Unfold the folded section the cursor is currently on |
-- | -- | :Mkdnflow<CR> |
Manually start Mkdnflow |
:MkdnGoBack
, require('mkdnflow').buffers.goBack()
, returns a boolean indicating the success of goBack()
(thanks, @pbogut!). This is useful if the user wishes to remap <BS>
so that when goBack()
is unsuccessful, another function is performed.<CR>
in insert mode but can't get it to work, try inspecting your current insert mode mappings and seeing if anything is overriding your mapping. Possible candidates are completion plugins and auto-pair plugins.<CR>
(e.g. nvim-autopairs), see if it provides a way to disable its <CR>
mapping (e.g. nvim-autopairs allows you to disable that mapping by adding map_cr = false
to the table passed to its setup function).:h mkdnflow
)<Tab>
)<CR>
?)perspective
is set to root
)<CR>
when in lists, etc.<S-BS>?
<Del>
)~
or /
)luautf8
no longer required for use of UTF8 symbols in customized to-do symbols or formatted tablesya
to yaa
to prevent interference with yap
(yank around paragraph)luautf8
Luarocks module)+
as a valid unordered list or unordered to-do list marker (requested in issue #112)<
+ >
and lacking the usual markdown link syntax that are automatically rendered as links when compiled into HTML) will now be followed:MkdnNewListItemBelowInsert
and :MkdnNewListItemAboveInsert
) mapped to o
and O
by defaultMkdnToggleToDo
/<C-Space>
) to-do item's status if it has children<Tab>
and <S-Tab>
can be used in both tables and listsMkdnMoveSource
, mapped to <F2>
by default)/
or ~/
)false
in mappings
table to disable mapping<M-CR>
by default (Alt-Enter)<CR>
is pressed~/
require('mkdnflow').buffers.goBack()
to return a boolean (true
if goBack()
succeeds; false
if goBack()
isn't possible). For the default mappings, this causes no change in behavior, but users who wish <BS>
to perform another function in the case that goBack()
fails can now use goBack()
in the antecedent of a conditional. @pbogut's mapping, for reference:if not require('mkdnflow').buffers.goBack() then
vim.cmd('Dirvish %:p')
end
:Mkdnflow
, addressing issue #5