Today's Featured Neovim Tips & Tricks:expand_moreCurrent Buffer DiffmiscshellSometimes it can be handy to get a quick diff of the changes that have been made to the current buffer since the previous save. This can be achieved with a simple command-line mode command. To demonstrate, let's start with the following buffer: Initial Conditions a = [ "one", "two", "three", "five", ] COMMAND Top 1:1 :%s/two/changed Now lets edit the buffer. To keep it simple we will make a single edit, changing "two" to "changed": Edit the Buffer a = [ "one", " changed", "three", "five", ] NORMAL 43% 3:5 Now, we can generate a simple diff with the command: :write !diff % - Let's review what this does. To start, this uses the :write command with the following call signature: :w[rite] !{cmd} Which passes the entire contents of the current buffer to the specified command via stdin . In this example we will use the diff shell command, which has the call signature: diff file1 file2 where our command defines file1 and file2 as: file1 = % , which contains the filename of the current file, and file2 = - , which tells diff to read stdin So, putting this all together, we pass the current buffer contents to the diff command, and generate the diff between the saved version of the current file ( % ) and the current buffer contents. Finally, the diff is passed back to Neovim: Generate the Diff a = [ "one", " changed ", "three", "five", ] NORMAL 43% 3:5 :write !diff % - 3c3 < "two", --- > "changed", shell returned 1 The output window shows that the line containing "two" has been changed to "changed". diff also returns a status code if 1 , meaning that the two files were different. This is definitely not the prettiest diff in the world, but is useful when a quick diff is required. Take a look at: diff --help to see what options are available to suit the output to your needs. For example, by leveraging diff 's options one can format the same output as a side-by-side diff: :write !diff -yt -W 60 % - Alternate Output a = [ "one", " changed ", "three", "five", ] NORMAL 43% 3:5 :write !diff -yt -W 60 % - a = [ a = [ "one", "one", "two", | "changed", "three", "three", "five", "five", ] ] shell returned 1Improved JeditingkeymapVim's built-in J command can be used to join the current line with the next line, which can be a very handy tool for keeping text "clean" when editing. Let's take a quick look at the default behavior. Starting from the following buffer , execute J : Before After Before Default Line o ne Line two Line three NORMAL Top 1:6 After Default J Line o ne Line two Line three NORMAL Top 1:9 Note that the second line has merged with the first, but the cursor jumped after joining the lines, which is a bit disorienting. We would prefer to join lines without having the cursor move, so in this tip we will develop a simple keymap that achieved this. For comparison, here is a quick demo of the buffer before and after the improved keymap: Before After Before Improved Line o ne Line two Line three NORMAL Top 1:6 After Improved <A-j> Line o ne Line two Line three NORMAL Top 1:6 Let's set this up, step by step. Since the default J moves the cursor, our goal is to have the cursor return to the original location, which we can achieve by setting a mark to record the cursor position before we execute J . We can use any lower-case letter, let's use z : Set Mark mz Line o ne Line two Line three NORMAL Top 1:6 Now that we have set the mark , execute the join by executing J : Join Lines J Line o ne Line two Line three NORMAL Top 1:9 As we saw before, the cursor jumped. Now, we simply jump back to the mark we previously set using `z : Jump to Mark `z Line o ne Line two Line three NORMAL Top 1:6 Now that we have compiled the steps required to get the behavior we want, let's combine the steps into the following keymap: vim . keymap . set ( "n" , "<A-j>" , "mzJ`z" ) Note that for demonstration purposes this mapping sets the alternate keymap to A-J . If you prefer this functionality over the default, then you can also override the default keymap directly by using J instead of <A-J> . Navigating Markdown Headings with TreesitterkeymapnavigatingWe 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:1Manually Re-Flowing ParagraphsformattingWhen editing text documents, paragraphs can often become split up into lines of varying lengths. For example, starting from the following text: Initial Conditions T his is a single paragraph with lines that have been split up into different lengths. NORMAL Top 1:1 One solution is to combine Vim's text formatting command gw with the paragraph text object selector ip : gw{motion} tells Vim to format the lines that covered by {motion}, and the ip [[text object]] tells Vim to apply this formatting over the inner paragraph . Reflow g w i p T his is a single paragraph with lines that have been split up into different lengths. NORMAL Top 1:1 The lines of the paragraph are combined, then split at word boundaries to produce a sequence of lines with lengths that follow the width set by vim.opt.textwidth .
Current Buffer DiffmiscshellSometimes it can be handy to get a quick diff of the changes that have been made to the current buffer since the previous save. This can be achieved with a simple command-line mode command. To demonstrate, let's start with the following buffer: Initial Conditions a = [ "one", "two", "three", "five", ] COMMAND Top 1:1 :%s/two/changed Now lets edit the buffer. To keep it simple we will make a single edit, changing "two" to "changed": Edit the Buffer a = [ "one", " changed", "three", "five", ] NORMAL 43% 3:5 Now, we can generate a simple diff with the command: :write !diff % - Let's review what this does. To start, this uses the :write command with the following call signature: :w[rite] !{cmd} Which passes the entire contents of the current buffer to the specified command via stdin . In this example we will use the diff shell command, which has the call signature: diff file1 file2 where our command defines file1 and file2 as: file1 = % , which contains the filename of the current file, and file2 = - , which tells diff to read stdin So, putting this all together, we pass the current buffer contents to the diff command, and generate the diff between the saved version of the current file ( % ) and the current buffer contents. Finally, the diff is passed back to Neovim: Generate the Diff a = [ "one", " changed ", "three", "five", ] NORMAL 43% 3:5 :write !diff % - 3c3 < "two", --- > "changed", shell returned 1 The output window shows that the line containing "two" has been changed to "changed". diff also returns a status code if 1 , meaning that the two files were different. This is definitely not the prettiest diff in the world, but is useful when a quick diff is required. Take a look at: diff --help to see what options are available to suit the output to your needs. For example, by leveraging diff 's options one can format the same output as a side-by-side diff: :write !diff -yt -W 60 % - Alternate Output a = [ "one", " changed ", "three", "five", ] NORMAL 43% 3:5 :write !diff -yt -W 60 % - a = [ a = [ "one", "one", "two", | "changed", "three", "three", "five", "five", ] ] shell returned 1
Improved JeditingkeymapVim's built-in J command can be used to join the current line with the next line, which can be a very handy tool for keeping text "clean" when editing. Let's take a quick look at the default behavior. Starting from the following buffer , execute J : Before After Before Default Line o ne Line two Line three NORMAL Top 1:6 After Default J Line o ne Line two Line three NORMAL Top 1:9 Note that the second line has merged with the first, but the cursor jumped after joining the lines, which is a bit disorienting. We would prefer to join lines without having the cursor move, so in this tip we will develop a simple keymap that achieved this. For comparison, here is a quick demo of the buffer before and after the improved keymap: Before After Before Improved Line o ne Line two Line three NORMAL Top 1:6 After Improved <A-j> Line o ne Line two Line three NORMAL Top 1:6 Let's set this up, step by step. Since the default J moves the cursor, our goal is to have the cursor return to the original location, which we can achieve by setting a mark to record the cursor position before we execute J . We can use any lower-case letter, let's use z : Set Mark mz Line o ne Line two Line three NORMAL Top 1:6 Now that we have set the mark , execute the join by executing J : Join Lines J Line o ne Line two Line three NORMAL Top 1:9 As we saw before, the cursor jumped. Now, we simply jump back to the mark we previously set using `z : Jump to Mark `z Line o ne Line two Line three NORMAL Top 1:6 Now that we have compiled the steps required to get the behavior we want, let's combine the steps into the following keymap: vim . keymap . set ( "n" , "<A-j>" , "mzJ`z" ) Note that for demonstration purposes this mapping sets the alternate keymap to A-J . If you prefer this functionality over the default, then you can also override the default keymap directly by using J instead of <A-J> .
Navigating Markdown Headings with TreesitterkeymapnavigatingWe 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
Manually Re-Flowing ParagraphsformattingWhen editing text documents, paragraphs can often become split up into lines of varying lengths. For example, starting from the following text: Initial Conditions T his is a single paragraph with lines that have been split up into different lengths. NORMAL Top 1:1 One solution is to combine Vim's text formatting command gw with the paragraph text object selector ip : gw{motion} tells Vim to format the lines that covered by {motion}, and the ip [[text object]] tells Vim to apply this formatting over the inner paragraph . Reflow g w i p T his is a single paragraph with lines that have been split up into different lengths. NORMAL Top 1:1 The lines of the paragraph are combined, then split at word boundaries to produce a sequence of lines with lengths that follow the width set by vim.opt.textwidth .