1. Introduction
Shortcuts are at the heart of the Vi editor. Navigating file contents quickly and efficiently can accumulate hours of saved time.
In this tutorial, we explore how to place the cursor over a specific character by number, line, and content in vi. First, we talk about the humble character when it comes to the Vi editor. Next, we move the cursor position based on character and line numbers. After that, we show how to start Vi with the cursor placed at a prespecified position. Finally, we discuss some potentially useful options when it comes to character navigation based on content rather than position.
For brevity, we use vi (Vi) when referencing both the Vi and Vim editors.
We tested the code in this tutorial on Debian 11 (Bullseye) with GNU Bash 5.1.4. It should work in most POSIX-compliant environments.
2. vi Characters
There are many text objects in Vi. Naturally, the smallest one is the character.
While we can perform operations on characters, working with them directly is usually easier. On the other hand, characters and their positions can serve as guides and pointers for more complex commands:
cw
Before entering Insert mode, the simple cw motion removes the characters up to the end of the current word. Words are defined by their surrounding whitespace.
So, we use two guides:
- position of the character under the cursor
- definition of a word as a sequence of non-whitespace characters
Notably, both reference the atomic building blocks of every text – the character and its position.
Considering this, knowing how to relocate the cursor relative to a sequence of characters, we can easily define borders for many other useful commands.
3. Vi Cursor Position by Character and Line Number
After understanding the importance of character positions, let’s go through some options to set the current cursor based on them.
3.1. Set Current Line
If we look at the contents of a file in Vi as a grid, we can define its two dimensions:
- Y coordinate – current row, i.e., line number
- X coordinate – current column, i.e., character number on a line
Thus, one way to define the position of a character is two-dimensional, including both rows and columns, since they’re correlated. By default, Vi displays the current coordinates in the lower right corner of the editor.
So, let’s see how to move to a given line:
:10
This command instructs Vi to place the cursor before the first character at the beginning of line 10. Alternatively, we can use Shift-g after typing the line number in Normal mode. To move down relative to the current line, we can use j instead of Shift-g.
3.2. Jump to Character on Line
Now, we can jump to a given character on the current line with the | bar or pipe symbol preceded by an integer:
666|
In this case, we move the cursor to column 666. Notably, a Tab represents a single column, regardless of the display settings.
Finally, to combine both motions into one, we can use 10G666| to move the cursor just before character 666 on line 10. Still, there is another way to achieve the same.
3.3. Cursor on Character of Line
By using :cal[l] with the cursor() function, we can place the cursor where we need it:
:call cursor(10,666)
In this case, we go to character 666 of line 10.
Alternatively, we can employ another character positioning scheme.
4. Vi Cursor Position by Character Number
Without regarding newlines as special, we can look at file contents as a simple stream of characters. In a stream, every character has a single coordinate – its number in the sequence.
To place the cursor over a character as identified by its number in the file, we can again use a Vi instruction:
:goto 1666
This :go[to] command repositions the cursor on byte number 1666.
Unlike with the previous method, we should be aware of the line ending convention of the file in question. Because Microsoft Windows lines end with \r\n two characters, but Linux lines end with \n one, there is a byte offset difference of a character per line.
Now, let’s see how to make line and character navigation even more useful.
5. Start Vi on Character
Indeed, we can open a file in Vi with the cursor already over a position by supplying vi with a file path and the + plus or -c switches. Both are roughly similar to the -c switch of Bash, which runs commands.
Any command after + plus or -c is run in Ex mode. For brevity and historical reasons, we use only -c.
5.1. Jump To Line and Character
To begin with, let’s use the internal Vi cursor() function from earlier to place the cursor where necessary upon opening main.c:
$ vim -c'call cursor(10,666)' /main.c
Actually, we can use the above to automate debugging operations when parsing errors with a file location:
$ debug="$(gcc main.c -o main 2>&1)"
$ echo "$debug"
main.c: In function ‘main’:
main.c:2:10: error: ‘a’ undeclared (first use in this function)
2 | return a;
| ^
main.c:2:10: note: each undeclared identifier is reported only once for each function it appears in
$ while IFS= read -r row; do
fields=(${row//:/ })
ln=${fields[1]}
cn=${fields[2]}
type=${fields[3]}
if [[ $ln == +([[:digit:]]) ]] &&
[[ $cn == +([[:digit:]]) ]] &&
[[ $type == 'error' ]]
then
echo "On line $ln, character $cn."
fi
done <<< "$debug"
On line 2, character 10 of main.c.
Here, we parse each gcc GNU C compiler output row for a $ln line number and $cn character number, separated by a : colon. When error rows contain integers at the expected places, we output the character coordinates.
Of course, once we have those, we can simply plug them in a vi invocation of cursor().
5.2. Jump To Byte
To move to byte 1666 after opening /file.csv, we can employ the goto command:
$ vi -c1666goto /file.csv
When running vi with a file path, using goto and an integer prefix opens the file and goes to that byte in it.
Naturally, this method also works for syntax errors, but they should point to a given character by number instead of row (line) and column (place in line).
6. Vi Cursor Position by Character
For even more granularity, we can use a set of movement commands.
Followed by a character, any of t, T, f, and F, place the cursor around the found occurrence of that character on the same line:
- t – one character before found character, forward search
- T – one character before found character, backward search
- f – just before found character, forward search
- F – just before found character, backward search
For example, we can search for the next 0 and place the cursor on it via f0. On the other hand, using Tx moves one character to the left of the last x occurrence before the initial position.
To easily repeat a character search in either direction from the current cursor location, we have two available operators:
- ; semicolon – repeat forward
- , comma – repeat backward
For instance, t ;; moves one character to the left of the third space, starting from our current position and moving forward.
While none of these commands directly specifies a position, in certain circumstances, they can become better guides for locating a given file part.
In fact, we can automate their bigger and more complex relative by calling the search() function:
$ vim -c'call search(SEARCH_STRING)' /file
Here, we can even leverage regular expressions (regex).
7. Summary
In this article, we talked about character positioning and how to employ it when moving the cursor.
In conclusion, Vi has versatile ways to locate characters in a file, providing both manual and automated options.