Today's Featured Neovim Tips & Tricks:expand_moreExternal FormattingeditingformattingshellVim's internal formatting features work great in many cases, but sometimes it can be helpful to send a buffer's content to an external program to perform the formatting, then replace the buffer contents with the result. For example, suppose we have the following JSON file in a buffer and want to sort the keys. Initial Conditions { "bananas": 12, "strawberries": 15, "apples": 34, "oranges": 24, "grapes": 33 } COMMAND Top 1:1 :%!jq -S . % We will demonstrate how to achieve this using a command-line mode command that calls jq , a popular command-line JSON processor. Obviously jq needs to be installed on a system for this to work. The command to execute is: :%!jq -S . % Let's break this down to review what is happening: To start, this follows the pattern: :[range]!{cmd} {args} where :% specifies the range as the entire file. Next, !{cmd} indicates that an external command is to be called with the specified range and arguments. In this case the external command to be called is: jq -S . % The details of this command are (mostly) outside the scope of Vim itself, but can be found by executing jq --help which indicates that the jq command is called with the format: jq [options] <jq filter> [file] where: [options] = -S (sort object keys) <jq filter> = . (pass all lines back to stdout) [file] = % (Vim inserts the current filename) Now, when we execute this command we get the following result: After External Formatting { "apples": 34, "bananas": 12, "grapes": 33, "oranges": 24, "strawberries": 15 } NORMAL Top 1:1 7 lines filtered which contains our sorted JSON object. 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:1Removing DuplicateseditingshellA common task when editing files is to remove duplicate lines. While this can be done manually, it is often easier to leverage Vim's command-line mode to leverage the uniq command. Suppose we have the following file in a buffer and want to remove duplicates: Initial Conditions b ananas apples apples oranges apples COMMAND Top 1:1 :%!uniq The command to execute is: :%!uniq Let's break this down to review what is happening: To start, this follows the pattern: :[range]!{cmd} {args} where :% specifies the range as the entire file, although this can be replaced with a range specifying a subset of lines, or a visual selection . Next, !{cmd} indicates that an external command is to be called with the specified range and arguments. In this case the external command to be called is: uniq with (initially) no arguments. By default uniq takes input from stdin , so there is no need to specify the input source. Executing this command produces the following output: Execute uniq b ananas apples oranges apples NORMAL Top 1:1 5 lines filtered which is not exactly what we were looking for. It turns out that by default uniq only detects adjacent repeating lines, so apple still appears twice in the output. If we take a look at the help page for uniq we see that the -u option can be used to print only unique lines, which is what we were looking for. Lets undo to get back to the original buffer, then try again using the -u argument: Execute uniq -u b ananas oranges apples NORMAL Top 1:1 5 lines filtered That is more like it. We should note that uniq also offers the -d option, which performs the opposite function and returns only the duplicate lines . Starting from the original buffer we can execute this command to see the result: Execute uniq -d a pples NORMAL Top 1:1 5 lines filteredReplace Word Under CursoreditingIt is a common task to search and replace all occurrences of a word within the current buffer. For example, given the following buffer: Initial Conditions B eautiful is better than ugly. Explicit is better than implicit. Simple is better than complex. Complex is better than complicated. Flat is better than nested. Sparse is better than dense. Readability counts. NORMAL Top 1:1 Suppose one wants to change all occurrences of is better to is way better . The first step in the example if to move the cursor to the word we want to replace: w B eautiful i s better than ugly. Explicit is better than implicit. Simple is better than complex. Complex is better than complicated. Flat is better than nested. Sparse is better than dense. Readability counts. NORMAL Top 1:11 then press & to select all occurrences of the word under the cursor. In addition to selecting all occurrences of is , the cursor moves to the next occurrence. * Beautiful i s better than ugly. Explicit i s better than implicit. Simple is better than complex. Complex is better than complicated. Flat is better than nested. Sparse is better than dense. Readability counts. NORMAL 25% 2:10 The general template for searching and replacing is: :[range]s/{pattern}/{replacement}/[flags] where Template Value Description : Puts Vim into Command Mode [range] % Applies this command to the entire file s/ Indicates that this is a search and replace operation {pattern}/ Not needed, since the words to replace are already selected. {replacement}/ is way Specifies the text to replace the search pattern with. [flags] No flags are specified. Putting this all together and executing the command: Beautiful is way better than ugly. Explicit i s way better than implicit. Simple is way better than complex. Complex is way better than complicated. Flat is way better than nested. S parse is way better than dense. Readability counts. NORMAL 75% 6:1 6 substitutions on 6 lines As expected, each occurrence of is has been replaced with is way .
External FormattingeditingformattingshellVim's internal formatting features work great in many cases, but sometimes it can be helpful to send a buffer's content to an external program to perform the formatting, then replace the buffer contents with the result. For example, suppose we have the following JSON file in a buffer and want to sort the keys. Initial Conditions { "bananas": 12, "strawberries": 15, "apples": 34, "oranges": 24, "grapes": 33 } COMMAND Top 1:1 :%!jq -S . % We will demonstrate how to achieve this using a command-line mode command that calls jq , a popular command-line JSON processor. Obviously jq needs to be installed on a system for this to work. The command to execute is: :%!jq -S . % Let's break this down to review what is happening: To start, this follows the pattern: :[range]!{cmd} {args} where :% specifies the range as the entire file. Next, !{cmd} indicates that an external command is to be called with the specified range and arguments. In this case the external command to be called is: jq -S . % The details of this command are (mostly) outside the scope of Vim itself, but can be found by executing jq --help which indicates that the jq command is called with the format: jq [options] <jq filter> [file] where: [options] = -S (sort object keys) <jq filter> = . (pass all lines back to stdout) [file] = % (Vim inserts the current filename) Now, when we execute this command we get the following result: After External Formatting { "apples": 34, "bananas": 12, "grapes": 33, "oranges": 24, "strawberries": 15 } NORMAL Top 1:1 7 lines filtered which contains our sorted JSON object.
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
Removing DuplicateseditingshellA common task when editing files is to remove duplicate lines. While this can be done manually, it is often easier to leverage Vim's command-line mode to leverage the uniq command. Suppose we have the following file in a buffer and want to remove duplicates: Initial Conditions b ananas apples apples oranges apples COMMAND Top 1:1 :%!uniq The command to execute is: :%!uniq Let's break this down to review what is happening: To start, this follows the pattern: :[range]!{cmd} {args} where :% specifies the range as the entire file, although this can be replaced with a range specifying a subset of lines, or a visual selection . Next, !{cmd} indicates that an external command is to be called with the specified range and arguments. In this case the external command to be called is: uniq with (initially) no arguments. By default uniq takes input from stdin , so there is no need to specify the input source. Executing this command produces the following output: Execute uniq b ananas apples oranges apples NORMAL Top 1:1 5 lines filtered which is not exactly what we were looking for. It turns out that by default uniq only detects adjacent repeating lines, so apple still appears twice in the output. If we take a look at the help page for uniq we see that the -u option can be used to print only unique lines, which is what we were looking for. Lets undo to get back to the original buffer, then try again using the -u argument: Execute uniq -u b ananas oranges apples NORMAL Top 1:1 5 lines filtered That is more like it. We should note that uniq also offers the -d option, which performs the opposite function and returns only the duplicate lines . Starting from the original buffer we can execute this command to see the result: Execute uniq -d a pples NORMAL Top 1:1 5 lines filtered
Replace Word Under CursoreditingIt is a common task to search and replace all occurrences of a word within the current buffer. For example, given the following buffer: Initial Conditions B eautiful is better than ugly. Explicit is better than implicit. Simple is better than complex. Complex is better than complicated. Flat is better than nested. Sparse is better than dense. Readability counts. NORMAL Top 1:1 Suppose one wants to change all occurrences of is better to is way better . The first step in the example if to move the cursor to the word we want to replace: w B eautiful i s better than ugly. Explicit is better than implicit. Simple is better than complex. Complex is better than complicated. Flat is better than nested. Sparse is better than dense. Readability counts. NORMAL Top 1:11 then press & to select all occurrences of the word under the cursor. In addition to selecting all occurrences of is , the cursor moves to the next occurrence. * Beautiful i s better than ugly. Explicit i s better than implicit. Simple is better than complex. Complex is better than complicated. Flat is better than nested. Sparse is better than dense. Readability counts. NORMAL 25% 2:10 The general template for searching and replacing is: :[range]s/{pattern}/{replacement}/[flags] where Template Value Description : Puts Vim into Command Mode [range] % Applies this command to the entire file s/ Indicates that this is a search and replace operation {pattern}/ Not needed, since the words to replace are already selected. {replacement}/ is way Specifies the text to replace the search pattern with. [flags] No flags are specified. Putting this all together and executing the command: Beautiful is way better than ugly. Explicit i s way better than implicit. Simple is way better than complex. Complex is way better than complicated. Flat is way better than nested. S parse is way better than dense. Readability counts. NORMAL 75% 6:1 6 substitutions on 6 lines As expected, each occurrence of is has been replaced with is way .