If you have ever built a terminal-based text editor in C (such as following the popular Kilo text editor tutorial), you have likely run into this frustrating visual bug: when your program exits, your terminal is pushed down by a massive block of blank lines, leaving your command prompt sitting awkwardly at the bottom or cluttering your terminal history.

Why Does \x1b[2J Cause Empty Lines?

In your screen-clearing function, you are sending two standard ANSI escape sequences:

void editorRefreshScreen() {
    write(STDOUT_FILENO, "\x1b[2J", 4);     // Clear screen
    write(STDOUT_FILENO, "\x1b[H", 3);      // Move cursor to top-left corner
}

Here is what happens behind the scenes:

  • \x1b[2J clears the entire visible viewport. However, in many modern terminal emulators (like GNOME Terminal, iTerm2, or Windows Terminal), clearing the screen does not delete existing content—it pushes the current visible lines into the terminal's scrollback buffer.
  • When your program exits immediately after clearing the screen, the shell prints its new prompt either at the top or bottom of this freshly created blank space, creating the appearance of a huge gap of blank lines.

The Modern Solution: Use the Alternate Screen Buffer

If you look at professional terminal applications like vim, nano, or less, you will notice that when you quit them, your terminal view is seamlessly restored to exactly how it looked before you launched the command. There are no blank lines or altered scrollback history.

These tools achieve this by switching to the Alternate Screen Buffer (part of the standard xterm escape sequences) on startup and switching back to the main buffer on exit.

How to Implement the Alternate Screen Buffer

Instead of clearing your primary screen on exit, switch between the buffers using these two sequences:

  • Enter Alternate Screen: \x1b[?1049h
  • Exit Alternate Screen: \x1b[?1049l

Here is how you can incorporate this into your C application:

#include <unistd.h>
#include <stdlib.h>
#include <termios.h>

struct termios orig_termios;

void disableRawMode() {
    // 1. Switch back to the main screen buffer
    write(STDOUT_FILENO, "\x1b[?1049l", 8);

    // 2. Restore normal terminal attributes
    tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios);
}

void enableRawMode() {
    tcgetattr(STDIN_FILENO, &orig_termios);
    atexit(disableRawMode);

    // Switch to the alternate screen buffer
    write(STDOUT_FILENO, "\x1b[?1049h", 8);

    struct termios raw = orig_termios;
    // Configure raw mode flags...
    tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
}

Because the alternate screen buffer is discarded completely when switching back to the main buffer, your editor's visual output disappears cleanly upon exit without leaving any extra lines or pushing your shell history downward.

Alternative Fix: Clearing the Scrollback Buffer

If you prefer not to use the alternate screen buffer and simply want to ensure your terminal clears the history along with the screen, you can also issue the \x1b[3J sequence (introduced by xterm to clear saved scrollback lines):

void editorClearAll() {
    // \x1b[2J clears visible screen
    // \x1b[3J clears scrollback buffer
    // \x1b[H moves cursor to row 1, col 1
    write(STDOUT_FILENO, "\x1b[2J\x1b[3J\x1b[H", 11);
}

Keep in mind that clearing the scrollback buffer destroys the user's previous terminal history, which can be disruptive. For text editors and full-screen TUI apps, using the alternate screen buffer (?1049h / ?1049l) is the industry-standard best practice.