DEV Community

Armand Halbert
Armand Halbert

Posted on • Originally published at ahalbert.com

Vim + Markdown = Writer's Heaven

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:

  • Spelling
  • English Auto-Completion
  • Auto-Formatting
  • Grammar Checking

Some notes before we begin:

  • I use vim-plug to manage my plugins, and this guide assumes you do too.
  • There are two .vimrc files used here: ~/.vimrc and ~/.vim/after/ftplugin/markdown.vim, which is a file that runs only after ~/.vimrc is loaded and a markdown file is detected.

The Basics

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
Enter fullscreen mode Exit fullscreen mode

I never found much value in line numbers when writing, so I turned them off
specifically for Markdown.

Spelling

~/.vim/after/ftplugin/markdown.vim

" Turn spellcheck on
setlocal spell
nnoremap zs 1z=
" Disable check for sentence capitalization
setlocal spellcapcheck=
Enter fullscreen mode Exit fullscreen mode

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

Vim-markdown, preservim/vim-markdown is a plugin that provides several nice
features, such as:

  • Folding
  • Highlighting of fenced code blocks
  • Highlighting of front matter

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
Enter fullscreen mode Exit fullscreen mode

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.

Completion

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)
Enter fullscreen mode Exit fullscreen mode

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.

Auto-formatting

~/.vimrc

Plug 'dense-analysis/ale'

let g:ale_fixers = {
    \ 'markdown': ['prettier']
    \}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Prettier

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"
Enter fullscreen mode Exit fullscreen mode

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.

Linting

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

Markdown-lint highlights common issues with Markdown files. You can install it
with

npm install -g markdownlint-cli
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

~/.vimrc

let g:ale_linters = {
    \ 'markdown': ['markdownlint']
    \}
Enter fullscreen mode Exit fullscreen mode

Vale

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:

  1. Install Vale. You can find instructions here
  2. Create a ~/.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
Enter fullscreen mode Exit fullscreen mode
  1. Create the folder ~/.vale

  2. Run vale sync

  3. Create the folder ~/.vale/config and ~/.vale/config/vocabularies/Local

  4. 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']
    \}
Enter fullscreen mode Exit fullscreen mode

Harper

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,
\       },
\   },
\}
Enter fullscreen mode Exit fullscreen mode

Grammar Checking

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'
Enter fullscreen mode Exit fullscreen mode

Gramaculate in action

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.

Writer's Heaven

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.

Top comments (0)