1. Introduction

Text, in any of its many encodings, formats, and fonts, is the essence of human-computer communication. As such, preserving text characteristics is vital for the correct and easy interpretation by both users and algorithms.

In this tutorial, we look at indenting and ways to preserve proper indentation when pasting text in the Vi editor. First, we briefly explain indenting as a general practice. Next, we check ways of applying the concept in the Vi editor. After that, we explore how a pasted text might end up distorted in vi. Finally, we discuss bracketed paste as a general solution and understand how that works in Vi.

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. Indentation

To indent means to add empty space at the beginning of a line, effectively shifting it away from the edge of the container that holds the text. In many natural languages, the technique is used for easier perception of paragraphs:

    This is an initial paragraph
with two lines.

    This is a new paragraph that
continues as usual on the second
line and ends on the third.

The spacing before each paragraph visually aids when following the text. Regular text is indented mainly as formatting and conceptual ordering. The same goes for structured texts such as ASCII-style tables and graphs.

On the other hand, code indentation can have consequences in terms of logic. In fact, there are several notable programming languages that rely on indenting for proper interpretation. For example, Python, Haskell, and Nim all use special spacing rules.

Of these, Python is an integral part of many Linux versions:

  • Debian is the largest integrated Python distribution
  • Ubuntu in particular, ships with Python from its early versions
  • Red Hat heavily relies on Python for many of its core tools

Let’s see some Python code, so we can appreciate how indenting could affect the logic:

x = 0
if x < 1:
  print('Error')
  print('x must be at least 1')

Here, the two spaces before the last two lines ensure that they only get executed if x is less than 1. Missing this spacing or having different amounts of it for the two lines will result in either an error or unexpected behavior of the code.

Now, after understanding the importance of indenting, let’s see how to use it and ensure it does not get mishandled when moving text.

3. Indenting in vi

The ubiquitous vi editor is part of both the SUS and POSIX standards.

Vi aims to provide support for diverse formatting practices and options, including indentation.

3.1. Indent Settings

We can trigger each indentation SETTING in a similar manner:

  • :set SETTING to enable
  • :set noSETTING to disable

The main option for automatic indentation is autoindent. When on, autoindent ensures vi matches the indentation of the last line when beginning a new one (with the Return key) in insert mode.

Looking at :help autoindent, we see some more alternatives:

During development, we can use smartindent. In fact, smartindent relies on autoindent, but indents according to the style and syntax of code in languages that it understands, e.g., C:

int main() {
  int x = 0;
  return x;
}

Since smartindent is now relatively old and rigid, it’s rarely used in practice outside of C-like code. Even then, a better option is cindent, which has finer configuration options for different styles. Still, it’s much more limited than the last method.

Indeed, as long as we have filetype detection, indentexpr should be the best integrated choice for proper code indentation. By using the filetype plugin indent on option, rules from the indent directory of vi (commonly /usr/share/vim/vim8#/indent) will apply according to the type of file we’re editing.

3.2. Indent Commands

The Vi editor provides several options for manual indentation. The main variable that controls the amount of indenting is shiftwidth. In fact, it plays a key role in many indentation commands vi provides:

  • >> indents and << de-indents the current line or selection
  • N>> or N<< sets the number of lines to apply the command to
  • >% increases and <% decreases the indents of the braced or bracketed block where the cursor is
  • = indents based on cindent rules unless indentexpr is non-empty

In each case, shiftwidth controls the number of spaces. Of course, we use :set shiftwidth=N to assign a value to that special variable.

3.3. Reindent

Vi also has the gg=G command, which indents the contents of an entire file. In command mode, we use gg=G to:

  1. move to the first line with gg
  2. indent with = based on the current configuration
  3. perform the same operation until the end of the file with the motion G

When applied, this combination allows us to reindent a whole file. In fact, there is another way to do the same.

3.4. External Indent

One more option to apply indenting to an entire file is by using external tools.

The Vi editor can run shell commands by using the exclamation mark ! prefix:

:!echo Test.
[...]
Test.

Press ENTER or type command to continue

Importantly, this runs the shell echo, not Vi’s echo. We can apply this feature to our needs by running an external tool for indenting our file. For example, there are the perltidy, autopep8, and astyle automatic formatters.

Similarly, there are vi plugins that can perform this task. A great example is a vim-autoformat plugin.

Considering all of the options the Vi editor provides for handling indents, it’s important to understand what happens when a preformatted text comes from the outside into an already formatted file.

4. Pasted Text Indentation in vi

When using autoindent, Vi relies on the new line character (Return key) to decide when to apply indentation rules. Because of this, we may encounter an unexpected scenario during copy-paste operations.

Let’s use the earlier Python code example:

x = 0
if x < 1:
  print('Error')
  print('x must be at least 1')

When entering the characters manually, pressing Return after the first closing parenthesis ) ensures the next print statement has the same indentation as the first. Vi does this by inserting the necessary spacing.

Critically, the Return key is the same as a newline character for vi. Thus, when pasting the same code directly, the following can happen if we trigger automatic indentation on each newline:

x = 0
if x < 1:
  print('Error')
    print('x must be at least 1')

On the third line, vi attempts to match the indentation of the second by inserting (two) spaces. However, since the third line is already indented, it’s pushed further to the right than the previous one.

The problem compounds when using specific language style rules. As Vi can detect Python code by filetype or specification, we might end up with even more issues in our example:

x = 0
if x < 1:
    print('Error')
      print('x must be at least 1')

The first indentation happens after the newline follows the colon : as per Python’s syntax rules. This doubles the expected spacing at the first print statement. Next, we again have automatic indentation matching and incorrect spacing.

Given that these problems exist in many settings, they are not only solved for vi, but the terminal in general. Let’s explore how.

5. Bracketed Paste

Terminals interpret characters from stdout as they come. This mechanism allows for different operation modes triggered via shortcuts.

One terminal mode is the bracketed paste. This mode brackets pasted contents in special characters, which instructs the terminal to disable some potentially problematic text extrapolation for this block:

non_pasted_text %pasted_text
pasted_text pasted_text
pasted_text%
non_pasted_text

In this example, the percent % indicates where to start and end a pasted text segment.

Since paste bracketing can be specific to a given terminal, there are different characters that can surround the pasted contents. Common examples are the *ESC [ 200 * (*\033[200*) and *ESC [ 201 * (*\033[201*) ANSI escape codes. They are standard in most settings due to their xterm compatibility.

6. Vi Bracketed Pasting

The Vi editor supports bracket pasting via its paste mode. :set paste disables dangerous functions like automatic indentation while pasting. Once done with the operation, we can restore the initial settings via :set nopaste. Effectively, vi adds the necessary escape characters to bracket the new content and does not interpret anything within:

non_pasted_text \033[200~pasted_text
pasted_text pasted_text
pasted_text\033[201~
non_pasted_text

For faster access, we can map a key to toggle the paste mode in Vi:

:set pastetoggle=<F10>

Now, we can use F10 to alternate between :set paste and :set nopaste.

Moreover, we can add the following non-portable solution to ~/.vimrc or /etc/vim/vimrc for automatic bracketed pasting without needing to bother with Vi’s paste mode manually:

let &t_SI .= "\[?2004h"
let &t_EI .= "\[?2004l"

inoremap   [200~ XTermPasteBegin()

function! XTermPasteBegin()
  set pastetoggle=[201~
  set paste
  return ""
endfunction

Due to the hardcoded escape characters, the solution is limited:

In fact, since vi version 8, the problem with pasted text getting distorted is mostly solved. We just need to paste in Normal Mode (as opposed to Insert Mode). This way, no additional processing is done on the content.

Finally, we can simply use shell command execution to paste the graphical user interface clipboard contents:

:r!xclip -out

The r prefix before ! above makes Vi directly insert the output of the command to the current position of the active file.

7. Summary

In this article, we discussed indenting in Vi and ways to avoid problems with automatic indentation and formatting when pasting contents to the editor.

In conclusion, vi provides many options for manual and automatic indentation but also supports easy methods to disable any automated text processing in pasted blocks of text.