Navigating Markdown Headings with Treesitter
We previously looked at how to create a keymap that makes it quick and easy to navigate between Markdown headings. Although that tip was simple to create, there is potential
for false-positives. In this tip
we take a look at how to implement the same keymap with Treesitter so that we can
more-accurately target Markdown headings.
Create a markdown directory under ftplugin if it doesn't
already exist, then add a file containing the following keymaps :
local ts_utils = require ( "nvim-treesitter.ts_utils" ) local M = { -- define the query query = vim . treesitter . query . parse ( "markdown" , "((atx_heading) @header)" ), } M . init = function () -- search the current buffer M . buffer = 0 -- references to lines within the buffer M . first_line = 0 M . current_line = vim . fn . line ( "." ) M . previous_line = M . current_line - 1 M . next_line = M . current_line + 1 M . last_line = - 1 -- default count M . count = 1 if vim . v . count > 1 then M . count = vim . v . count end -- list of captures M . captures = {} -- get the parser M . parser = vim . treesitter . get_parser () -- parse the tree M . tree = M . parser : parse ()[ 1 ] -- get the root of the resulting tree M . root = M . tree : root () end M . next_heading = function () M . init () -- populate captures with all matching nodes from the next line to -- the last line of the buffer for _ , node , _ , _ in M . query : iter_captures ( M . root , M . buffer , M . next_line , M . last_line ) do table.insert ( M . captures , node ) end -- get the node at the specified index ts_utils . goto_node ( M . captures [ M . count ]) end M . previous_heading = function () M . init () -- if we are already at the top of the buffer -- there are no previous headings if M . current_line == M . first_line + 1 then return end -- populate captures with all matching nodes from the first line -- of the buffer to the previous line for _ , node , _ , _ in M . query : iter_captures ( M . root , M . buffer , M . first_line , M . previous_line ) do table.insert ( M . captures , node ) end -- get the node at the specified index ts_utils . goto_node ( M . captures [ # M . captures - M . count + 1 ]) end -- define the keymaps vim . keymap . set ( "n" , "<A-j>" , M . next_heading ) vim . keymap . set ( "n" , "<A-k>" , M . previous_heading ) This is a bit more complicated, but still fairly simple. We start by creating a lua module,
followed by an initialization function that collects information about the buffer , cursor , and count . Finally, separate functions are created
to implement jumping in the forward and reverse directions, respectively. Finally, we create the keymaps .
As a quick demo, let's follow the same steps we did in the previous tip. Starting from the top, in steps 1 and 2 we
hit <A-j> to step forward through the headings, then finally in step 3 we use <A-k> to jump back
up to the previous heading:
Start 1 2 3 Before # Heading 1 Text ## Heading 2 Text ### Heading 3 NORMAL Top 1:1 Jump to Next Heading <A-j> # Heading 1 Text # # Heading 2 Text ### Heading 3 NORMAL 50% 5:1 Jump to Next Heading (Again) <A-j> # Heading 1 Text # # Heading 2 Text # ## Heading 3 NORMAL 90% 9:1 Jump to Previous Heading <A-k> # Heading 1 Text # # Heading 2 Text # ## Heading 3 NORMAL 50% 5:1