Why Your Emacs Terminal is Slow and How to Fix It (vterm, eat, ghostel, term, and ansi-term)

Emacs terminal buffers like eat, vterm, ghostel, term, and ansi-term can become slow when processing large volumes of standard output. Watching Emacs freeze while a build script dumps thousands of lines is frustrating, but terminal latency is not an unavoidable cost of living inside Emacs. Applying a few targeted configuration changes will eliminate scroll lag and restore immediate responsiveness. This article provides a configuration that speed up terminal buffers.

Before we begin, please consider sharing this article on your website, blog, Mastodon, Reddit, X, LinkedIn, Hacker News, or other social media platforms. Sharing it will help other Emacs users discover how to improve the performance of their terminal emulators.

Complete configuration for high-performance terminal buffers

The following Elisp code speeds up Emacs terminals by applying settings and disabling minor modes that are unnecessary in terminal buffers:

;; Description: Speed up Emacs terminals (vterm, eat, ghostel, term, and ansi-term)
;; Author: James Cherti
;; License: MIT
;; URL: https://www.jamescherti.com/emacs-terminal-performance-vterm-eat-ansi-term-ghostel/
(setq vterm-timer-delay 0.01)
(setq ghostel-timer-delay 0.01)
(setq eat-minimum-latency 0.01)
(setq eat-maximum-latency 0.05)

(setq vterm-max-scrollback 500)
(setq ghostel-max-scrollback (* 1024 1024))
(setq eat-term-scrollback-size (* 64 1024))

(setq eat-enable-shell-prompt-annotation nil)

;; Uncomment -DUSE_SYSTEM_LIBVTERM if you prefer system libvterm
(setq vterm-module-cmake-args
      (concat "-DCMAKE_C_FLAGS='-O3 -march=native -mtune=native' "
              "-DCMAKE_SHARED_LINKER_FLAGS='-Wl,-O2 -Wl,--as-needed' "
              ;; "-DUSE_SYSTEM_LIBVTERM=yes"
              ))

(defun my-speed-up-terminal-buffer ()
  "Reduce unnecessary Emacs features in terminal buffers."
  (let ((ghostel-buffer (derived-mode-p 'ghostel-mode)))
    (setq-local font-lock-defaults '(nil t))

    (setq-local fast-but-imprecise-scrolling t)
    (setq-local redisplay-skip-fontification-on-input t)
    (setq-local scroll-conservatively most-positive-fixnum)
    (setq-local hscroll-margin 0)
    (setq-local scroll-margin 0)
    (setq-local scroll-step 0)
    (setq-local hscroll-step 0)
    (setq-local auto-hscroll-mode nil)

    ;; Uncomment to disable scroll bars to save redisplay cycles
    ;; (setq-local vertical-scroll-bar nil)
    ;; (setq-local horizontal-scroll-bar nil)

    (setq-local truncate-lines t)
    (setq-local nobreak-char-display nil)
    (setq-local bidi-paragraph-direction 'left-to-right)
    (setq-local bidi-inhibit-bpa t)

    ;; Ghostel coordinates row height calculations via ghostel-line-spacing
    (unless ghostel-buffer
      (setq-local line-spacing 0)
      (setq-local mode-line-format nil))

    (setq-local echo-keystrokes 0)

    (setq-local process-adaptive-read-buffering nil)
    (let ((output-max (* 1024 1024)))
      (when (< read-process-output-max output-max)
        (setq-local read-process-output-max output-max)))

    (buffer-disable-undo)

    ;; Evil users
    (remove-hook 'pre-command-hook 'evil--jump-hook t)
    (remove-hook 'post-command-hook 'evil--jump-handle-buffer-crossing t)

    ;; Disable modes
    (let ((inhibit-redisplay t)
          (inhibit-message t)
          (modes '(electric-pair-local-mode
                   electric-indent-local-mode
                   display-line-numbers-mode
                   display-fill-column-indicator-mode
                   hl-line-mode
                   show-paren-local-mode
                   flymake-mode
                   ;; Third-party packages
                   ;; NOTE: Add more modes here
                   flycheck-mode
                   evil-surround-mode
                   evil-snipe-local-mode
                   yas-minor-mode
                   company-mode
                   corfu-mode)))
      ;; ghostel-comint, ghostel-compile, and ghostel-links register a function
      ;; in eldoc-documentation-functions to display target URLs and file under
      ;; point.
      ;;
      ;; Ghostel uses auto-composition-mode in the sync tty composition
      ;; function.
      (unless ghostel-buffer
        (push 'eldoc-mode modes)
        (push 'auto-composition-mode modes))

      (dolist (mode modes)
        (when (and (boundp mode)
                   (symbol-value mode)
                   (fboundp mode))
          (ignore-errors
            (funcall mode -1)))))))

(add-hook 'term-mode-hook 'my-speed-up-terminal-buffer t)
(add-hook 'vterm-mode-hook 'my-speed-up-terminal-buffer t)
(add-hook 'eat-mode-hook 'my-speed-up-terminal-buffer t)
(add-hook 'ghostel-mode-hook 'my-speed-up-terminal-buffer t)

Note: Some of the setq-local settings in this function, excluding the settings that disable minor modes, are provided by modern terminal emulators such as vterm, Eat, and Ghostel. However, each emulator exposes a different set of variables, so this function sets the relevant settings for all supported emulators in one place for consistency and simplicity.

Why these settings help

The code snippet above reduces display, fontification, undo, and minor-mode performance cost in terminal buffers.

Terminal-specific configuration

(setq vterm-timer-delay 0.01)
(setq ghostel-timer-delay 0.01)
(setq eat-minimum-latency 0.01)
(setq eat-maximum-latency 0.05)

(setq vterm-max-scrollback 500)
(setq ghostel-max-scrollback (* 1024 1024))
(setq eat-term-scrollback-size (* 64 1024))

(setq eat-enable-shell-prompt-annotation nil)

;; Uncomment -DUSE_SYSTEM_LIBVTERM if you prefer system libvterm
(setq vterm-module-cmake-args
      (concat "-DCMAKE_C_FLAGS='-O3 -march=native -mtune=native' "
              "-DCMAKE_SHARED_LINKER_FLAGS='-Wl,-O2 -Wl,--as-needed' "
              ;; "-DUSE_SYSTEM_LIBVTERM=yes"
              ))
  • vterm-timer-delay, eat-minimum-latency, eat-maximum-latency, and ghostel-timer-delay: These control how terminal output is batched before redisplay. Lowering these values reduces the delay before output appears, at the cost of more frequent redraws during heavy output. These settings are worth it because they make terminal emulator feel less sluggish.
  • eat-enable-shell-prompt-annotation: Disabling shell prompt annotations avoids the processing associated with maintaining them.
  • vterm-max-scrollback, eat-term-scrollback-size, and ghostel-max-scrollback: These set the amount of history retained by each emulator. ghostel-max-scrollback is measured in bytes, vterm-max-scrollback in lines, and eat-term-scrollback-size in characters.
  • vterm-module-cmake-args: Passes additional CMake arguments when compiling the vterm C module. The -O3, -march=native, and -mtune=native flags request aggressive compiler optimization and CPU-specific tuning. (Building with -march=native optimizes the binary specifically for your current CPU architecture. You can only run this module on machines with the same CPU architecture.)

Line rendering, wrapping, and UI

(setq-local truncate-lines t)
(setq-local nobreak-char-display nil)
(setq-local bidi-paragraph-direction 'left-to-right)
(setq-local bidi-inhibit-bpa t)

;; Ghostel coordinates row height calculations via ghostel-line-spacing
(unless ghostel-buffer
  (setq-local line-spacing 0)
  (setq-local mode-line-format nil))

(setq-local echo-keystrokes 0)
  • truncate-lines: Setting this to t stops long lines from wrapping. This bypasses expensive line-wrapping calculations, allowing the display engine to render text sequentially.
  • nobreak-char-display: Emacs highlights non-breaking spaces and soft hyphens by default. Setting this to nil stops Emacs from overlaying highlight boxes on top of terminal output.
  • line-spacing: TUI applications use box-drawing characters to create borders. Setting this to 0 removes vertical pixel gaps between lines, ensuring pixel-perfect vertical alignment for TUI borders and progress bars.
  • bidi-paragraph-direction and bidi-inhibit-bpa: Emacs scans text by default to determine if it should be rendered right-to-left. Setting bidi-paragraph-direction to 'left-to-right and bidi-inhibit-bpa to t forces left-to-right rendering. This bypasses expensive text scanning. Right-to-left languages will render incorrectly in the shell.
  • echo-keystrokes: Setting this to 0 disables the echoing of unfinished keystrokes in the minibuffer.
  • mode-line-format: Setting this to nil hides the mode-line. This stops Emacs from constantly re-evaluating mode-line functions (which check Git status or active modes) on terminal buffers.

Accelerating display and scrolling

(setq-local fast-but-imprecise-scrolling t)
(setq-local redisplay-skip-fontification-on-input t)
(setq-local scroll-conservatively most-positive-fixnum)
(setq-local hscroll-margin 0)
(setq-local scroll-margin 0)
(setq-local scroll-step 0)
(setq-local hscroll-step 0)
(setq-local auto-hscroll-mode nil)


;; Uncomment to disable scroll bars to save redisplay cycles
;; (setq-local vertical-scroll-bar nil)
;; (setq-local horizontal-scroll-bar nil)
  • fast-but-imprecise-scrolling: Accelerates scrolling by allowing Emacs to skip fontification during rapid scroll events.
  • redisplay-skip-fontification-on-input: Causes the redisplay engine to skip the fontification_functions pass when pending input exists. This improves responsiveness when typing.
  • scroll-conservatively: Setting this variable to most-positive-fixnum makes Emacs scrolls only enough to bring point into view rather than recentering.
  • hscroll-margin and scroll-margin: Define the horizontal and vertical padding around point before scrolling occurs. Setting them to 0 disables this padding, preventing the display engine from performing recentering calculations when point nears the window edge.
  • scroll-step and hscroll-step: Setting these to 0 means the normal centering behavior.
  • auto-hscroll-mode: Disallows automatic horizontal scrolling of windows.
  • vertical-scroll-bar and horizontal-scroll-bar: Disables the scroll bars. Every time new text is inserted, the redisplay engine recalculates the size and position of the scroll bar thumb based on the new buffer size. Disabling scroll bars removes the scroll-bar-related redisplay and UI work for the terminal buffer.

Process I/O and buffering

(setq-local process-adaptive-read-buffering nil)
(let ((output-max (* 1024 1024)))
  (when (< read-process-output-max output-max)
    (setq-local read-process-output-max output-max)))

Increasing read-process-output-max allows Emacs to read up to (* 1024 1024) amounts of output in a single system call. However, a larger chunk size alone is not enough because the default process-adaptive-read-buffering algorithm will still inject micro-sleeps, assuming rapid bursts of escape sequences are caused by a background process that needs throttling.

Ensuring process-adaptive-read-buffering is disabled forces the event loop to stop second-guessing the data stream and immediately consume those chunks as fast as the operating system provides them. (Adaptive read buffering is disabled by default. This is included in case the global value has been modified in your Emacs configuration.)

Disabling syntactic fontification

(setq-local font-lock-defaults '(nil t))

Setting font-lock-defaults to (nil t) turns off automatic fontification for the buffer. The first element specifies that there are no font-lock keywords, while the second sets font-lock-keywords-only, which prevents syntactic fontification.

This is useful for terminal buffers because terminal emulators do not generally need Emacs to apply programming-language fontification to their contents. Terminal applications manage their own colors through terminal escape sequences.

Disabling undo history

(buffer-disable-undo)

Disables recording undo data for the buffer.

Managing Evil mode hooks (Evil-mode users)

(remove-hook 'pre-command-hook 'evil--jump-hook t)
(remove-hook 'post-command-hook 'evil--jump-handle-buffer-crossing t)
  • evil--jump-hook and evil--jump-handle-buffer-crossing: These hooks are used by Evil mode to calculate and store jump list entries on every command. Removing them bypasses the computation and storage of jump list entries for every single command executed in the terminal. The downside is that Evil mode users cannot use C-o or C-i to return to a previous cursor position in terminal buffers.

Disabling intrusive minor modes

(let ((inhibit-redisplay t)
      (inhibit-message t)
      (modes '(electric-pair-local-mode
               electric-indent-local-mode
               display-line-numbers-mode
               display-fill-column-indicator-mode
               hl-line-mode
               show-paren-local-mode
               flymake-mode
               ;; Third-party packages
               ;; NOTE: Add more modes here
               flycheck-mode
               evil-surround-mode
               evil-snipe-local-mode
               yas-minor-mode
               company-mode
               corfu-mode)))
  ;; ghostel-comint, ghostel-compile, and ghostel-links register a function
  ;; in eldoc-documentation-functions to display target URLs and file under
  ;; point.
  ;;
  ;; Ghostel uses auto-composition-mode in the sync tty composition
  ;; function.
  (unless ghostel-buffer
    (push 'eldoc-mode modes)
    (push 'auto-composition-mode modes))

  (dolist (mode modes)
    (when (and (boundp mode)
               (symbol-value mode)
               (fboundp mode))
      (ignore-errors
        (funcall mode -1)))))
  • yas-minor-mode, evil-snipe-local-mode, evil-surround-mode: These modes intercept keystrokes for snippets or character searches. Disabling them bypasses extra keymap lookups and event loop interceptions, reducing latency between keystroke and terminal input.
  • hl-line-mode, display-line-numbers-mode: These modes alter the visual representation of lines. Disabling them prevents the display engine from removing and recreating overlays on every cursor movement or calculating margin widths for every visible line.
  • electric-pair-local-mode, electric-indent-local-mode, show-paren-local-mode, auto-composition-mode: These modes analyze syntax to insert quotes, indent lines, or highlight brackets. Disabling them stops Emacs from running complex regex, syntax table lookups, and text-shaping on every typed character or newline, which speeds up text rendering.
  • company-mode, corfu-mode, flymake-mode, flycheck-mode, eldoc-mode: These modes manage completion, linting, and documentation lookups. Disabling these modes removes their associated hooks, redisplay work, buffer analysis, completion, diagnostics, or other processing from terminal buffers.

Leave a Reply

Your email address will not be published. Required fields are marked *