Neovim Tips & Tricks


This chapter compiles many of the best tips and tricks that the Neovim community has developed over the years to turbo-charge your Neovim workflow.

Filed In:editing

It is a common task to search and replace all occurrences of a word within the current buffer. In some cases it can be convenient to use replacement text that is stored in a register. This tip demonstrates how to do so. Starting with the following buffer, note that we have already stored the replacement text in register A. Initial Conditions Beautiful 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 :reg a Type Name Content l "a is way Now, to execute the replacement, we follow the standard replacement pattern:, :s/{pattern}/{replacement} where the replacement text references the contents of a register. Although in NORMAL mode we access a register's contents using "{register} , the search is executed in COMMAND mode. We can access register contents in COMMAND mode as follows: Command Action C-R {regname} insert the contents of a register or object under the cursor as if typed In order to access the contents of register a, we would therefore use a replacement spec of <C-R> a Search and Replace Beautiful is way better than ugly. Explicit is way better than implicit. Simple is way better than complex. Complex is way better than complicated. Flat is way better than nested. Sparse is way better than dense. Readability counts. NORMAL 75% 6:1 6 substitutions on 6 lines Executing this command replaces each occurrence of is with the contents of register a, is way.
Filed In:editing

It 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 Beautiful 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. COMMAND Top 1:1 :%s/is/is way/ Suppose one wants to change all occurrences of is better to is way better. 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}/ is Specifies the pattern to search for {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: Title Beautiful is way better than ugly. Explicit is way better than implicit. Simple is way better than complex. Complex is way better than complicated. Flat is way better than nested. Sparse 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. Writing patterns can sometimes require a bit of trial and error, if the final text is not as expected, simply hit u to return to the original: Titleu Beautiful 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 6 changes; before #1 0 seconds ago
Filed In:editing

Occasionally while editing a document the need to prepend text to a sequence of lines comes up. For example, suppose you want to comment out some lines in a file by prepending each line with a "#" character. A simple way to achieve this is to leverage normal mode commands to apply an operation over each line in a range . To demonstrate, let's start with the following file: Initial Conditions one two three four five six NORMAL Top 1:1 First let move the cursor down a few lines and make a visual selection : Make Selection2jV2j one two three four five six V-LINE 71% 5:1 Now that we have selected the lines to modify, let's build our command. First, hit : to enter command-line mode then enter normal to indicate that this command should execute in normal-mode. This tells Neovim that the following command should be executed once for each line in the selection. Finally, we type the same keystrokes that we would execute in normal mode to achieve the desired changes. In this case, we want to execute I to move the cursor to the beginning of each line and enter insert mode , then # to insert this character. :normI# Before Executing Command one two three four five six COMMAND 71% 5:1 :normal I# After executing this command, we can confirm that a "#" has been inserted at the start of each line in the selection. After Executing Command one two #three #four #five six NORMAL 71% 5:1
Filed In:editing

Reorganizing lines in a buffer is a very common task. While a yank and paste sequence could be used for each line, there is a better way. The following keymaps allow a line or group of lines to be easily moved up or down using a single keystroke: -- single-line vim.keymap.set("n","<A-j>",":move .+1<CR>==") vim.keymap.set("n","<A-k>",":move .-2<CR>==") -- multi-line vim.keymap.set("v","<A-j>",":move '>+1<CR>gv=gv") vim.keymap.set("v","<A-k>",":move '<-2<CR>gv=gv") To see how they work, consider the following buffer: Initial Conditions a = [ "one", "three", "two", "four", "five", ] NORMAL Top 1:1 Note that the words are out of order, and we would like to fix that. We start by moving the cursor to the line that we want to move (i.e. the "target" line): Move to Target Line2j a = [ "one", "three", "two", "four", "five", ] NORMAL 38% 3:1 With this keymap, hitting A-J allows us to easily move the currently down one position, swapping its position with the line below it: Move Current Line<A-j> a = [ "one", "two", "three", "four", "five", ] NORMAL 50% 4:1 To get a better idea about what happened, consider the command that executed when we hit A-J: :move .+1<CR>== The :move command has a call signature: :m[ove] {address} and moves the current line (or selected lines) to the line below the specified address. In this case, the specified address is .+1, which we learned in the ranges chapter means "the line below the current line". The carriage return causes the preceding command to execute, which is follows by == , which formats (indents) the line after it is moved. Let's see how this works with a visual selection . First, let's move the cursor so that the selection spans three lines: Select Multiple Linesv2j a = [ "one", "two", "three", "four", "five", ] VISUAL 75% 6:1 Note that although the selection only touches the third line, the entire line is included in the block of lines to move. Now, move these lines up: Move Multiple Lines<A-k> a = [ "one", "three", "four", "five", "two", ] VISUAL 62% 5:1 3 lines indented The selected lines have each move up one line, and the line containing "two" (which has previously been above the selection) is now located below the selection.
Filed In:formatting

Starting with the cursor in the middle of an un-formatted buffer : Initial Conditions <div> <p>line one</p> <p>line two</p> <p>line three</p> </div> NORMAL 50% 3:1 The first step is to execute gg , which moves the cursor to the top of the buffer: Move to Top of Buffergg <div> <p>line one</p> <p>line two</p> <p>line three</p> </div> NORMAL Top 1:1 Next, we execute = , which tells Vim to format the lines traversed by the associated motion. We follow this with G , which indicates that we want to apply formatting from the current line to the bottom of the file: Format the Entire Buffer=G <div> <p>line one</p> <p>line two</p> <p>line three</p> </div> NORMAL Top 1:1 5 lines indented The end result shows that the combination of gg and G includes all lines in the file.
Filed In:formatting

Starting with the cursor in the middle of an un-formatted buffer : Initial Conditions <div> <p>line one</p> <p>line two</p> <p>line three</p> </div> NORMAL 50% 3:1 Enter visual mode : Enter Visual Mode<Esc> <div> <p>line one</p> <p>line two</p> <p>line three</p> </div> NORMAL 50% 3:1 Move down another row: Create the Selectionv <div> <p>line one</p> <p>line two</p> <p>line three</p> </div> VISUAL 50% 3:1 and finally format the selected lines: Format Selectionj <div> <p>line one</p> <p>line two</p> <p>line three</p> </div> VISUAL 67% 4:1 Note that Vim formatted only the lines contained within the selection (including lines containing the cursors), and didn't touch the other lines.
Filed In:formatting

Normal mode commands can be used to apply an operation over each line in a range . Suppose you are creating a list in python by yanking and pasting some content from another buffer . After pasting, the items are in place but are missing the trailing , required by python: Initial Conditions a = [ "one" "two" "three" ] NORMAL 33% 2:1 These could be manually added in various ways, but Vim's :normal command provides a handy solution by allowing a normal mode command to be applied to each line in a range. To demonstrate, we first create a visual selection: Select Visual Rangev2j a = [ "one" "two" "three" ] VISUAL 67% 4:1 Note that the range could have also been entered manually. Next create the command to execute. We would like to move the cursor to the end of each line, enter insert mode after the line, then enter the , character. Inserting after the line can be achieve with the A command, so the command to execute for each line is: A, and the complete command is: :normal A, Executing this command in our buffer provides the desired result: Execute the Command a = [ "one", "two", "three", ] NORMAL 67% 4:12
Filed In:formatting

When editing text documents, paragraphs can often become split up into lines of varying lengths. For example, starting from the following text: Initial Conditions This 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. Reflowgwip This 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.
Filed In:misc

Sometimes 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
Filed In:misc

When editing a document one often needs to yank the entire buffer content. As with most operations, Neovim provides several different ways to accomplish this task. For each of the examples below, we will start with the following buffer content and cursor position: Initial Conditions red,#f00 green,#0f0 blue,#00f yellow,#ff0 cyan,#0ff magenta,#f0f NORMAL 57% 4:1 Common Method Perhaps the most common method to achieve this task is the following sequence: gg to move the cursor to the top of the buffer. v to enter visual mode . G to move the cursor to the bottom of the buffer. y to yank the selected content. Common MethodggVGy red,#f00 green,#0f0 blue,#00f yellow,#ff0 cyan,#0ff magenta,#f0f NORMAL Top 1:1 6 lines yanked Let's check the unnamed register to confirm that the full buffer content was yanked: Common Method red,#f00 green,#0f0 blue,#00f yellow,#ff0 cyan,#0ff magenta,#f0f COMMAND Top 1:1 :reg " :reg " Type Name Content l "" red,#f00green,#0f0blue,#00fyellow,#ff0cyan,#0ffmagenta,#f0f This method works well, and is probably so common because it is consistent with yanking smaller portions of the buffer. Besides requiring the most keystrokes, this method also has the disadvantage of moving the cursor to the top of the screen. Shorter Method A slightly shorter method leverages motions a bit more directly to save a keystroke: gg to move the cursor to the top of the buffer. y to yank the selected content. G to move the cursor to the bottom of the buffer. Shorter MethodggyG red,#f00 green,#0f0 blue,#00f yellow,#ff0 cyan,#0ff magenta,#f0f NORMAL Top 1:1 6 lines yanked Let's check the unnamed register to confirm it worked: Shorter Method red,#f00 green,#0f0 blue,#00f yellow,#ff0 cyan,#0ff magenta,#f0f NORMAL Top 1:1 :reg " Type Name Content l "" red,#f00green,#0f0blue,#00fyellow,#ff0cyan,#0ffmagenta,#f0f This method is slightly better than than the previous method, but also moves the cursor to the top of the screen. Shortest Method The third, and shortest, method of yanking all buffer content leverages the %range to yank the entire buffer with only 3 keystrokes: Upon hitting <CR> the entire buffer is yanked: Shortest Method red,#f00 green,#0f0 blue,#00f yellow,#ff0 cyan,#0ff magenta,#f0f COMMAND 57% 4:1 :%y Not only is this the shortest method, it has the additional advantage of keeping the cursor in the same position. Shortest Method red,#f00 green,#0f0 blue,#00f yellow,#ff0 cyan,#0ff magenta,#f0f NORMAL 57% 4:1 :reg " Type Name Content l "" red,#f00green,#0f0blue,#00fyellow,#ff0cyan,#0ffmagenta,#f0f
Filed In:shell

Sometimes 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
Filed In:shell

Neovim registers and command-line mode can be combined to a pretty decent REST API client. This tip shows how. First, open a new buffer and add the API request: Initial Conditions https://jsonplaceholder.typicode.com/todos/1 NORMAL Top 1:1 Now, yank the current line, which places the contents of the current line into register 0: Yank the API Request Stringyy https://jsonplaceholder.typicode.com/todos/1 NORMAL Top 1:1 Finally, lets build the command to call the API and insert the response. First, we learned previously that we can use the :read command to call a shell command and insert its output. The call signature is: :read !{cmd} [args] In this case we want to call a REST API, so lets use curl to call the API and receive the response. The command to achieve is: curl -s '{request}' where the -s option tells curl to operate in "silent mode". Finally, we could hard-code the request into the command, but we have already yanked it to register 0. We can retrieve register contents in command-line mode using C-R followed by the register name, so our complete command is: :read !curl -s '<C-r>0' Executing this command produces: Insert the API response https://jsonplaceholder.typicode.com/todos/1 { "userId": 1, "id": 1, "title": "delectus aut autem", "completed": false } NORMAL 88% 7:1 6 more lines if you find yourself using this often, consider creating a macro to save yourself a few keystrokes.
Filed In:shell

A 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 bananas 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 bananas 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 bananas 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 apples NORMAL Top 1:1 5 lines filtered
Filed In:shell

Vim'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.
Filed In:keymap

Vim'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 Before Default Line one Line two Line three NORMAL Top 1:6 After After DefaultJ Line one 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 Before Improved Line one Line two Line three NORMAL Top 1:6 After After Improved<A-j> Line one 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 Markmz Line one Line two Line three NORMAL Top 1:6 Now that we have set the mark , execute the join by executing J : Join LinesJ Line one 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 one 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>.
Filed In:keymap

Neovim provides many ways to navigate within documents, and this tip creates a simple keymap that makes it quick and easy to jump between markdown headings, which can be a big speed boost in longer markdown documents. Create a markdown directory under ftplugin if it doesn't already exist, then add a file containing the following keymaps : vim.keymap.set("n","<A-j>","/^#\\+ <CR>") vim.keymap.set("n","<A-k>","?^#\\+ <CR>") These keymaps take advantage of the search operators / and ? to search forward and backward from the current cursor position, respectively. In each case, Neovim searches for the pattern ^#\\+, which matches any line that starts with one or more # characters followed by a space. While this is a fairly naive pattern that may produce some false matches, it is very simple to implement and works quite well for most markdown documents. As a quick demo, let's take the following Markdown file. 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 Before # Heading 1 Text ## Heading 2 Text ### Heading 3 NORMAL Top 1:1 1 Jump to Next Heading<A-j> # Heading 1 Text ## Heading 2 Text ### Heading 3 NORMAL 50% 5:1 2 Jump to Next Heading (Again)<A-j> # Heading 1 Text ## Heading 2 Text ### Heading 3 NORMAL 90% 9:1 3 Jump to Previous Heading<A-k> # Heading 1 Text ## Heading 2 Text ### Heading 3 NORMAL 50% 5:1 This keymap also supports counts , allowing you to navigate even faster by jumping multiple headings at a time. If you would like to see another way to implement the same function but using Treesitter, take a look at this tip.
Filed In:keymap

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 : localts_utils=require("nvim-treesitter.ts_utils") localM={ -- 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 ifvim.v.count>1then 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 ifM.current_line==M.first_line+1then 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 Before # Heading 1 Text ## Heading 2 Text ### Heading 3 NORMAL Top 1:1 1 Jump to Next Heading<A-j> # Heading 1 Text ## Heading 2 Text ### Heading 3 NORMAL 50% 5:1 2 Jump to Next Heading (Again)<A-j> # Heading 1 Text ## Heading 2 Text ### Heading 3 NORMAL 90% 9:1 3 Jump to Previous Heading<A-k> # Heading 1 Text ## Heading 2 Text ### Heading 3 NORMAL 50% 5:1
Filed In:keymap

Reorganizing lines in a buffer is a very common task. While a yank and paste sequence could be used for each line, there is a better way. The following keymaps allow a line or group of lines to be easily moved up or down using a single keystroke: -- single-line vim.keymap.set("n","<A-j>",":move .+1<CR>==") vim.keymap.set("n","<A-k>",":move .-2<CR>==") -- multi-line vim.keymap.set("v","<A-j>",":move '>+1<CR>gv=gv") vim.keymap.set("v","<A-k>",":move '<-2<CR>gv=gv") To see how they work, consider the following buffer: Initial Conditions a = [ "one", "three", "two", "four", "five", ] NORMAL Top 1:1 Note that the words are out of order, and we would like to fix that. We start by moving the cursor to the line that we want to move (i.e. the "target" line): Move to Target Line2j a = [ "one", "three", "two", "four", "five", ] NORMAL 38% 3:1 With this keymap, hitting A-J allows us to easily move the currently down one position, swapping its position with the line below it: Move Current Line<A-j> a = [ "one", "two", "three", "four", "five", ] NORMAL 50% 4:1 To get a better idea about what happened, consider the command that executed when we hit A-J: :move .+1<CR>== The :move command has a call signature: :m[ove] {address} and moves the current line (or selected lines) to the line below the specified address. In this case, the specified address is .+1, which we learned in the ranges chapter means "the line below the current line". The carriage return causes the preceding command to execute, which is follows by == , which formats (indents) the line after it is moved. Let's see how this works with a visual selection . First, let's move the cursor so that the selection spans three lines: Select Multiple Linesv2j a = [ "one", "two", "three", "four", "five", ] VISUAL 75% 6:1 Note that although the selection only touches the third line, the entire line is included in the block of lines to move. Now, move these lines up: Move Multiple Lines<A-k> a = [ "one", "three", "four", "five", "two", ] VISUAL 62% 5:1 3 lines indented The selected lines have each move up one line, and the line containing "two" (which has previously been above the selection) is now located below the selection.
Filed In:navigating

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 : localts_utils=require("nvim-treesitter.ts_utils") localM={ -- 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 ifvim.v.count>1then 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 ifM.current_line==M.first_line+1then 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 Before # Heading 1 Text ## Heading 2 Text ### Heading 3 NORMAL Top 1:1 1 Jump to Next Heading<A-j> # Heading 1 Text ## Heading 2 Text ### Heading 3 NORMAL 50% 5:1 2 Jump to Next Heading (Again)<A-j> # Heading 1 Text ## Heading 2 Text ### Heading 3 NORMAL 90% 9:1 3 Jump to Previous Heading<A-k> # Heading 1 Text ## Heading 2 Text ### Heading 3 NORMAL 50% 5:1
Filed In:navigating

Neovim provides many ways to navigate within documents, and this tip creates a simple keymap that makes it quick and easy to jump between markdown headings, which can be a big speed boost in longer markdown documents. Create a markdown directory under ftplugin if it doesn't already exist, then add a file containing the following keymaps : vim.keymap.set("n","<A-j>","/^#\\+ <CR>") vim.keymap.set("n","<A-k>","?^#\\+ <CR>") These keymaps take advantage of the search operators / and ? to search forward and backward from the current cursor position, respectively. In each case, Neovim searches for the pattern ^#\\+, which matches any line that starts with one or more # characters followed by a space. While this is a fairly naive pattern that may produce some false matches, it is very simple to implement and works quite well for most markdown documents. As a quick demo, let's take the following Markdown file. 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 Before # Heading 1 Text ## Heading 2 Text ### Heading 3 NORMAL Top 1:1 1 Jump to Next Heading<A-j> # Heading 1 Text ## Heading 2 Text ### Heading 3 NORMAL 50% 5:1 2 Jump to Next Heading (Again)<A-j> # Heading 1 Text ## Heading 2 Text ### Heading 3 NORMAL 90% 9:1 3 Jump to Previous Heading<A-k> # Heading 1 Text ## Heading 2 Text ### Heading 3 NORMAL 50% 5:1 This keymap also supports counts , allowing you to navigate even faster by jumping multiple headings at a time. If you would like to see another way to implement the same function but using Treesitter, take a look at this tip.