I use Jekyll, Markdown and Vim to write content for my blog. Rather than wrestling with a full-fledged CMS or writing raw HTML, I can use a human readable markup language to write my posts. Vim is my editor of choice, and while I like the friendliness of GUI markdown tools, I miss my shortcuts, autocomplete and plugins in vim. Having a minimalistic text editor gives me an environment that is distraction free, version controlled and easy to publish. This article will go into how to set up vim to effectively edit Markdown with these features:
Some notes before we begin:
~/.vimrc and
~/.vim/after/ftplugin/markdown.vim, which is a file that runs only after
~/.vimrc is loaded and a markdown file is detected.Vanilla vim itself comes with a lot of markdown support, such as frontmatter highlighting and spelling.
~/.vim/after/ftplugin/markdown.vim
" Disable line numbers
setlocal nonumber
I never found much value in line numbers when writing, so I turned them off specifically for Markdown.
~/.vim/after/ftplugin/markdown.vim
" Turn spellcheck on
setlocal spell
nnoremap zs 1z=
" Disable check for sentence capitalization
setlocal spellcapcheck=
Vim has a very effective spellchecking system that you can enable with
set spell, and a few keystrokes that make correcting your spelling easy. z=
brings up a list of possible corrections, while 1z= picks the most likely one.
I remapped it to zs to make it easy to correct spelling. If it’s a word vim
doesn’t recognize, you can use zg to add it to the dictionary.
spellcapcheck is a feature that detects if you forgot to capitalize the
beginning of a sentence. Unfortunately, it is just a regular expression, so if
you write something like “vs.” it will decide that you forgot to capitalize the
next word and highlight it. You can disable it by emptying the regex, as I did
with setlocal spellcapcheck= .
Vim-markdown, preservim/vim-markdown is a plugin that provides several nice
features, such as:
and some useful commands like:
:Toc — Create a table of contents in the quickfix list:InsertToc — insert a table of contents into the buffer:SetexToAtx, :HeaderDecrease, :HeaderIncrease~/.vimrc
Plug 'preservim/vim-markdown'
" Enable folding.
let g:vim_markdown_folding_disabled = 0
" Fold heading in with the contents.
let g:vim_markdown_folding_style_pythonic = 1
" Don't use the shipped key bindings.
let g:vim_markdown_no_default_key_mappings = 1
" Filetype names and aliases for fenced code blocks.
let g:vim_markdown_fenced_languages = ['php', 'py=python', 'js=javascript', 'bash=sh', 'viml=vim']
" Highlight front matter (useful for Jekyll/Hugo posts).
let g:vim_markdown_toml_frontmatter = 1
let g:vim_markdown_frontmatter = 1
let g:vim_markdown_json_frontmatter = 1
An explanation of the settings:
vim_markdown_folding_disabled is set to 0 to enable folding, which lets
you collapse sections of your document under their headings. To understand
folding behavior, see :help folding.vim_markdown_folding_style_pythonic changes the fold behavior so that the
heading line itself stays visible when folded, rather than being hidden with
the rest of the section.vim_markdown_no_default_key_mappings disables the plugin’s built-in key
mappings, letting you define your own without conflicts.vim_markdown_fenced_languages defines a list of language names and aliases
for syntax highlighting inside fenced code blocks, so that e.g. a block tagged
bash gets highlighted as sh.vim_markdown_frontmatter, vim_markdown_toml_frontmatter, and
vim_markdown_json_frontmatter enable syntax highlighting for YAML, TOML, and
JSON front matter respectively, which is useful if you write Jekyll or Hugo
posts.Why type each word by hand when you can tab-complete it? I use the plugin
girishji/vimcomplete and its companion, girishji/ngram-complete.vim to
provide auto-completion for English.
~/.vimrc
Plug 'girishji/vimcomplete'
let g:vimcomplete_tab_enable = 1
Plug 'girishji/ngram-complete.vim'
let vimcompleteoptions = {
\ 'buffer': {
\ 'enable': v:true,
\ 'priority': 2
\ },
\ 'ngram': {
\ 'enable': v:true,
\ 'priority': 1,
\ 'filetypes': ['markdown'],
\ 'bigram': v:true,
\ },
\}
autocmd VimEnter * call g:VimCompleteOptionsSet(vimcompleteoptions)
priority determines which completions show up first in the completion menu,
with larger numbers == higher priority.
ngram-complete.vim allows for completion based on the frequency of words,
making it much more useful than standard dictionary completion, which picks
words in alphabetical order. The bigram option allows completion based on
frequency of words given the previous word rather than just the frequency of the
current word you are completing.
~/.vimrc
Plug 'dense-analysis/ale'
let g:ale_fixers = {
\ 'markdown': ['prettier']
\}
The ale plugin (Asynchronous Lint Engine) allows auto-formatting and linting
in vim, running external tools asynchronously so they don’t block your editing.
With the configuration above, you can run :ALEFix to format the current file,
or add the following to have it format on save:
~/.vimrc
let g:ale_fix_on_save = 1
I use prettier to auto-format my markdown
files so that they are easy to read.
~/.prettierrc.yaml
overrides:
- files:
- "*.md"
- "*.markdown"
options:
proseWrap: "always"
proseWrap automatically wraps lines into 80 character columns. Be careful when
enabling it if you haven’t started your post with it, as it can create large
diffs.
The ale plugin enables automatic linting of your posts on save. While I don’t use the lint features personally, I will guide you on how to set them up in case you find them useful.
Markdown-lint highlights common issues with Markdown files. You can install it with
npm install -g markdownlint-cli
Set it up with a .markdownlint.yaml file.
.markdownlint.yaml
# Enable all rules by default
# https://github.com/markdownlint/markdownlint/blob/main/docs/RULES.md
default: true
# Allow inline HTML which is useful in Github-flavour markdown for:
# - crafting headings with friendly hash fragments.
# - adding collapsible sections with <details> and <summary>.
MD033: false
# Ignore line length rules (as Prettier handles this).
MD013: false
~/.vimrc
let g:ale_linters = {
\ 'markdown': ['markdownlint']
\}
Vale is a command-line tool that brings code-like linting to prose. It is not a grammar checker. You can find it here.
I did find it took a bit of effort to install and get working. Here’s a guide for what I did:
~/.vale.ini file# Where the styles are kept.
StylesPath = .vale
Packages = write-good, proselint
MinAlertLevel = suggestion
# Where to look for local vocabulary files.
Vocab = Local
# Define which styles to use for Markdown.
[*.{md,markdown,txt}]
BasedOnStyles = Vale, write-good, proselint
[*]
BasedOnStyles = Vale
# Disable any rules that are more annoying than useful
write-good.E-Prime = NO
Create the folder ~/.vale
Run vale sync
Create the folder ~/.vale/config and ~/.vale/config/vocabularies/Local
Create the files ~/.vale/config/vocabularies/Local/accept.txt and
~/.vale/config/vocabularies/Local/reject.txt
Once you’ve completed these instructions, you can change your ~/.vimrc as
follows:
~/.vimrc
let g:ale_linters = {
\ 'markdown': ['vale', 'markdownlint']
\}
However, I found the above linters didn’t highlight anything useful, and were
more an annoyance than anything else. I next turned to harper, which is not
supported by ALE, so I had to build in support for it.
I am currently trying to merge the PR into the main ALE repository, and will update this post when it happens. But for now, you can use my fork if you want to try it.
You can find instructions on how to install Harper here
~/.vimrc
" Not the standard ALE repository!
Plug 'ahalbert/ale'
let g:ale_linters = {
\ 'markdown': ['harper']
\}
let g:ale_markdown_harper_config = {
\ 'harper-ls': {
\ 'diagnosticSeverity': 'warning',
\ 'dialect': 'American',
\ 'linters': {
\ 'SpellCheck': v:false,
\ 'SentenceCapitalization': v:true,
\ 'RepeatedWords': v:true,
\ 'LongSentences': v:true,
\ 'AnA': v:true,
\ 'Spaces': v:true,
\ 'SpelledNumbers': v:false,
\ 'WrongQuotes': v:false,
\ },
\ },
\}
While Harper was more sophisticated than the linters above, none of the above
tools really worked for me. I wanted a more sophisticated grammar checker for my
writing. I thought an LLM was ideally suited to this task, so I built my own
plugin ahalbert/vim-gramaculate to check my grammar.
Plug 'ahalbert/vim-gramaculate'

You can then use :Gramaculate to check your grammar. By default, this uses a
local model with Ollama, but you can
read the docs
to configure it with any model you want, local or remote.
I find writing with vim a breeze once I got this all set up, and I hope this guide helps you do the same. Between spellcheck, auto-completion, formatting and grammar checking, vim becomes a surprisingly capable writing environment that stays out of your way and lets you focus on the words. If you have any suggestions for other plugins or workflows, feel free to reach out.