Notes on Mastering Emacs: Chapter 6: The Practicals of Emacs

By Susam Pal on 28 Dec 2023

The following notes were taken while discussing Chapter 6 of the book Mastering Emacs, 2022 edition (written by Mickey Petersen) in book discussion group meetings.

An index of notes for all chapters are available at notes.html.

Contents

Exploring Emacs

The book suggests the following techniques to explore Emacs:

Project Management

Emacs comes with a project management package named project.el which offers commands to operate on projects. When we use a project management command like C-x p f to visit a file in the current project, this package automatically detects the top-level directory of the project by checking parent directories for version control system artifacts (e.g., .git directory) and presents files within that top-level directory as autocomplete options.

The following complete key sequences demonstrate the package management commands mentioned in the book:

There are several more project management key bindings. Type C-x p C-h to see a complete list of them.

Xref

Xref provides a generic framework to support commands for cross-referencing in Emacs. While there are several ways to set it up and configure it, the book mentions a particular way to set it up using a couple of external tools. The next two subsections discuss the setup work involved before we can use Xref in a modern way. The remaining subsections discuss how to use Xref.

Xref Setup

By default when we try to look up a definition of an identifier in, say, a C file or Python file, by typing M-., it presents a minibuffer for us to select a tags table file (typically named TAGS). This requires setting up a TAGS file with a tool like ctags. The book, however, does not explore this method for good reason. Typically the TAGS file needs to be created with a tool like ctags or etags for every project we work on. This file contains an index of names found in source code files. We need to periodically update it as the code of our projects evolve, so that this index remains up-to-date. For a long time, this was the only way to maintain an index of the names found in a source code, so that we could perform cross-referencing in editors like Vim and Emacs. Relying on a tool like grep to search the code on the fly was deemed to be quite slow. However, with modern, fast hardware we do not have to work like this anymore. Further, there are search tools like ag and rg which are extremely fast. Given these modern developments, there are simpler ways to set up cross-referencing in Emacs.

The book suggets installing an external package named dumb-jump. It can be installed from MELPA with the key sequence M-x package-install dumb-jump RET. See github.com/jacktasia/dumb-jump for more details about this package. After installing this package, add the following code to the Emacs initialisation file:

(add-hook 'xref-backend-functions #'dumb-jump-xref-activate)

Here is a minimal Elisp code that sets up dumb-jump from scratch and configures it as mentioned above:

(require 'package)
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
(package-initialize)
(unless package-archive-contents
  (package-refresh-contents))
(dolist (package '(dumb-jump))
  (unless (package-installed-p package)
    (package-install package)))
(add-hook 'xref-backend-functions #'dumb-jump-xref-activate)

The above code configures Emacs to use MELPA, retrieve the latest list of packages available there, install dumb-jump from it, as well as set up a hook to activate it automatically when we use certain Xref commands.

Search Tools for Xref

Once Xref is set up with dumb-jump as explained in the previous section, open a source code file (say, a C file or a Python file), move the cursor over to some identifier and type M-. to search that identifier in your environment. By default, it searches for the identifier in files of the same type found under the home directory with a tool like ag, rg, or grep (the first one it finds). There is an exception to this rule though. If neither ag nor rg is found and only GNU grep is found, then typing M-. on an indentifier searches the identifier in all files in the home directory (as opposed to searching for files of a specific type). If BSD grep is found instead, then this is not a problem and only files of the current type is searched for the identifier.

Further, while looking up definitions within a Git repository, this package invokes the git grep command to restrict searches to the repository directory.

Let us now look at a few examples of the actual search commands that are executed under the hood when we type M-..

If neither ag nor rg is installed and we only have grep on our system, typing M-. while the cursor is on an identifier named foo in a Python file leads to the execution of a command like this when BSD grep is found:

grep -REn --include '*.py' -e '\s*\bfoo\s*=[^=\n]+' -e 'def\s*foo\b\s*\(' -e 'class\s*foo\b\s*\(?' /Users/susam

If GNU grep is found instead, then all files (not just *.py files) are searched with a command like this:

grep -rEn -e '[[:space:]]*\bfoo[[:space:]]*=[^=\n]+' -e 'def[[:space:]]*foo\b[[:space:]]*\(' -e 'class[[:space:]]*foo\b[[:space:]]*\(?' /home/susam

If rg is the only additional search tool installed, then the following command is executed:

rg --color never --no-heading --line-number -U --pcre2 --type py '\s*\bfoo\s*=[^=\n]+|def\s*foo\b\s*\(|class\s*foo\b\s*\(?' /home/susam

If ag is installed, then the following command is executed:

ag --nocolor --nogroup --python '\s*\bfoo\s*=[^=\n]+|def\s*foo\b\s*\(|class\s*foo\b\s*\(?' /home/susam

When we type M-. in a file that belongs to a Git repository, only the repository directory is searched with a command like this:

git grep --color=never --line-number --untracked -E '\s*\bfoo\s*=[^=\n]+|def\s*foo\b\s*\(|class\s*foo\b\s*\(?' -- /home/susam/repo/*.py

The book makes a mention of rg and remarks about the impressive speed with which it searches the file system. I recommend it too. Since the M-. command may search the whole home directory, if the home directory is very large, having a fast search tool like rg or ag makes a significant difference. For example what could normally take 10 to 20 seconds to search using grep might only take a second or two with rg or ag. I use M-. with rg.

Four Most Common Xref Commands

The book mentions the following commands as the four most common commands we should know about:

When multiple cross-references are displayed in the Xref buffer, we can use the following key sequences to work with the Xref buffer.

Xref and Dired

Here are some key sequences that demonstrate how we can use Xref with Dired.

Working with Log Files

In this section of the book, it discusses a set of commands that are useful for working with log files. Note that some of these commands have been already introduced in the previous chapters. The following list presents the commands discussed in this section of the book:

Highlighting

Section Working with Log Files of Chapter 6 of the book also introduces highlighting commands that can be very useful for highlighting certain strings in the log file. The highlighting commands are demonstrated below with an example.

  1. First create a buffer with the following content.

    foo bar baz
    Foo Bar Baz
    FOO BAR BAZ
    foo  bar  baz
    Foo  Bar  Baz
    FOO  BAR  BAZ
    
  2. Now type M-s h p f.. SPC b.. RET RET to highlight the phrases matching the regular expression f.. b.. in a case-insensitive and whitespace-insensitive manner. A total of six matches will be highlighted because the first two words and the whitespace between them in all lines match this phrase pattern when we ignore the case of the words and the amount of whitespace. The second RET is meant to accept the default face offered to us for highlighting.

  3. Now type M-s h p b.z RET RET to highlight the phrases matching the regular expression b.z. Again we select the default face offered to us for highlighting. At this point, we should see two sets of highlighting in two different faces.

  4. Now move the cursor to one of the first set of highlights and type M-s h u RET. Those highlights will be unhighlighted. The RET key accepts the default unhighlighting pattern offered to us. It happens to be the pattern with which the highlight under the cursor was highlighted. That is why this key sequence ends up unhighlighting the highlight under the cursor.

    If the cursor were not over a highlgiht, then the default unhighlighting pattern offered to us would have been the pattern we used for the last highlight. In that case, we could type M-s h u f.. b.. RET to explicitly specify the unhighlighting pattern.

  5. Now type M-s h u RET again to remove the second set of highlights too.

  6. Type M-s h p F.. SPC B.. RET RET to perform a case-sensitive but whitespace-insensitive highlighting. When there is an uppercase letter in the pattern, the highlighting becomes case-sensitive.

  7. Type M-s h u RET to remove the previous highlighting.

  8. Type M-s h r f.. SPC b.. RET RET to perform a case-insensitive but whitespace-sensitive highlighting. This time, there are only three matches from the first three lines.

  9. Type M-s h u RET to remove the previous highlighting.

  10. Type M-s h r F.. SPC B.. RET RET to perform a case-sensitive and whitespace-sensitive highlighting. The matching strings are found in the second and third lines.

  11. Move the cursor to lowercase bar and type M-s h . to highlight symbol at point. All six occurrences of this symbol are highlighted in a case-insensitive manner because the symbol at point is written in all lowercase.

  12. Move the cursor to Baz and type M-s h . to highlight symbol at point. Only two occurrences of this symbol get highlighted. The highlighted symbols match the symbol Baz exactly (case-sensitive match). The highlighting is done in case-sensitive manner because the symbol at point has at least one uppercase letter.

Auto-Revert Mode

The following steps demonstrate how to use the revert-buffer command and then how to use auto-revert-mode.

  1. In a terminal, run the following command:

    : > /tmp/log.txt && while true; do date >> /tmp/log.txt; sleep 1; done

    You could use ansi-term within Emacs too as the terminal if you are familiar with it.

  2. Now within Emacs, type C-x C-f /tmp/log.txt RET.

  3. Wait for a few seconds and type M-x revert-buffer RET yes RET to update the buffer with the latest content of the file from the file system.

  4. Type M-x auto-revert-mode RET to enable automatic update of the buffer as the file changes on the file system. Note that this reloads the entire file whenever a change is detected, so this could be inefficient while working with very large files.

  5. Type M-> to go to the end of the buffer. This moves the cursor to the end of the buffer. Doing this ensures that as the buffer is automatically updated, the cursor automatically keeps moving to the end of the file.

  6. Terminate the command of step 1 and run this command in a terminal:

    echo hello > /tmp/log.txt

    The content of the buffer should now automatically truncate and update to just the text hello.

  7. Run the command in step 1 again and confirm that the content of the buffer in Emacs gets updated automatically.

  8. Type M-x auto-revert-mode RET to disable automatic update of the buffer.

Auto Revert Tail Mode

The mode named auto-revert-tail-mode is similar to auto-revert-mode. However, unlike auto-revert-mode which reloads the entire file on every update, the auto-revert-tail-mode only follows the tail of the buffer and appends any new text found to the buffer. The following steps demonstrate this:

  1. Like in the previous section, run the following command:

    : > /tmp/log.txt && while true; do date >> /tmp/log.txt; sleep 1; done
  2. Type M-x auto-revert-tail-mode RET. Note that this command follows the tail of the file only. It does not reload the entire file. This can be confirmed with the next step.

  3. Terminate the command of step 1 and run this command in a terminal:

    echo hello > /tmp/log.txt

    The buffer for this file in Emacs should automatically update to show the text hello at the bottom. But notice all the earlier text remains intact. The earlier text does not disappear from the buffer because Emacs does not reload the entire file when auto-revert-tail-mode is enabled.

  4. Run the command in step 1 again and confirm that the content of the buffer begins to get updated automatically again.

  5. As of Emacs 28.2, unfortunately running M-x auto-revert-tail-mode RET is not sufficient to disable automatic updates in the buffer. This command does disable the mode but the buffer continues to be updated everytime the file changes. This is very likely a bug in this mode.

    As a workaround, disabling auto-revert-mode ends up stopping the auto-update behaviour. There are two ways to do this. You could type M-x auto-revert-mode RET twice: once to enable it and a second time to disable it. Alternatively, just simply type C-0 M-x auto-revert-mode RET which invokes the mode with a prefix argument of zero which ends up disabling the mode.

Browsing Tarballs

The following steps demonstrate how we can not only browse a tarball but also edit files in it and save them back to the tarball.

  1. First, create a directory of text files with the following shell commands:

    mkdir -p foo/bar/baz/
    echo hello foo > foo/foo.txt
    echo hello bar > foo/bar/bar.txt
    echo hello baz > foo/bar/baz/baz.txt
    tar -caf /tmp/foo.tgz foo/
    
  2. Confirm that the tarball looks good with these shell commands:

    tar -tf /tmp/foo.tgz
    tar -xOf /tmp/foo.tgz
    
  3. Within Emacs, type C-x C-f /tmp/foo.tgz RET to open the tarball. A list of all entries in the tarball is displayed in a Tar buffer.

  4. Type n and p to navigate the Tar buffer down and up, respectively.

  5. With the cursor on the line containing foo/bar/baz/baz.txt, type RET. The content of this entry is now displayed in a new buffer.

  6. Now in the buffer that displays the content of baz.txt, edit its content. Say, type C-a ! to append an exclamation point to this buffer.

  7. Type C-x C-s to save this buffer. This updates the entry of foo/bar/baz/baz.txt within the buffer for foo.tgz. However, the updated tarball is not written to the file system yet.

  8. Type C-x b foo.tgz RET to go back to the buffer with the tarball entry listing.

  9. Finally, type C-x C-s to save the tarball to the file system.

  10. Now repeat step 2. The updated content of foo/bar/baz/baz.txt should now appear in the output.

Dired: Thumbnail Image Browser

Assuming there is a directory ~/foo/ that contains several image files as well as files of other types, the command M-x image-dired RET ~/foo/ RET creates a preview buffer of all images in the directory and displays it along with a normal dired buffer showing the directory listing. Both buffers are displayed in two separate windows.

When the preview buffer is first launched, all image files found in the directory are automatically marked. This can be seen in the Dired buffer. However the preview buffer does not reflect this immediately. Type m in the preview buffer to force it to pick the current list of marked images and highlight them.

As a best practice, remember to type m soon after launching image-dired so that the marked images are accurately displayed in the preview buffer.

Within the preview buffer, the following key sequences are supported:

Preview Buffer Quirks

The m, u, or d commands in the preview buffer are actually meant to mark, unmark, or flag the corresponding files in the Dired buffer. The highlighting or unhighlighting that occurs in the preview buffer is merely a convenience feature. The preview buffer may not always accurately reflect the most recent list of all marked and flagged files. Always keep an eye on the Dired buffer to check the most recent state of the files.

Especially, if we go back to the Dired buffer and mark, unmark, or flag files, the preview buffer does not reflect it automatically. We need to go to the preview buffer again and perform at least one similar operation (m, u, or d) in the preview buffer for it to be updated again. This is why it is important to keep an eye on the Dired buffer to get an accurate account of which files are marked or flagged.

Working on Marked Files

Say we have marked some image files using the key sequence m in the preview buffer. Now we can perform various operations on these marked files. For example, to copy the marked files to /tmp/ directory, in a Dired buffer, type C /tmp/ RET. To move them instead, type R /tmp/.

Deleting Images

When we flag thumbnails by typing d in the preview buffer, the corresponding files are flagged for deletion in the Dired buffer. The first column of the flagged file entries contain the letter D in the Dired buffer. Type x in the Dired buffer to permanently delete (expunge) the flagged files.

Tagging and Untagging

If there are marked images, then the tagging and untagging commands executed in the preview buffer work on those marked images. Otherwise, they work on image corresponding to the current thumbnail. We will refer to these images that the tagging or untagging commands work on as target images in the next few paragraphs..

The key sequence t t trip;oxford;uk RET tags the target images with the tags trip, oxford, and uk. The tags must be separated by semicolon as shown in the preceding example. The tags are saved in a path set in the image-dired-db-file variable. Type C-h v image-dired-db-file to read this path. Typically, it is something like ~/.emacs.d/image-dired/.image-dired_db. We will call this the DB file. This file may be manually inspected to see how this command and the next command affect the tags for each thumbnail. Alternatively, type C-t e in a Dired buffer to view and edit the tags of the target files.

The key sequence t r trip RET removes the tag trip from the target images. By virtue of how this functionality is implemented, a key sequence like t r t.*d removes the tags trip;oxford and trip;salford (if present) from the DB file but it does not remove a tag like trip;cambridge (if present).

Using Tags

Tagging thumbnails could be useful if we want to later mark files by tags. In a Dired buffer, the key sequence C-t f t.*d will mark all files whose thumbnails have tags (as they appear in the tags file) matching the regular expression t.*d. For example, images that have with tags trip;oxford;uk as well as trip;london;uk will be marked but images with tags trip;bath;uk and trip;liverpool;uk will not be marked.

Display Buffer

When we type RET in the preview buffer, the original image is displayed in a display buffer. The following key sequences are supported in the display buffer:

The book makes a note that when we open an image file directly from a Dired buffer, the image is opened in image-mode which is more powerful than the display buffer we get when we open an image from the thumbnail preview window.

DocView

When a PDF or another document of a supported format is opened in Emacs, they are converted to images on the fly and displayed in Emacs. In this section, we will discuss working with PDFs only. The converted images are cached at the directory set in the doc-view-cache-directory variable.

Type C-h v auto-mode-alist RET and search for doc-view in the help buffer to see the list of file formats that Emacs tries to open in DocView.

Ghostscript needs to be installed so that DocView can convert the PDF into images. Further, for some commands where we perform text-based operations on the PDF, we need the pdftotext command so that DocView can extract text from the PDF. Type C-h v doc-view-ghostscript-program RET and C-h v doc-view-pdftotext-program RET to see the external programs that DocView depends on. These programs can be installed with the following command on a Debian or Debian-based Linux distribution:

apt-get install ghostscript poppler-utils

On a macOS system, run the following command instead:

brew install ghostscript poppler

The following list presents some of the key bindings supported by DocView:

Although not mentioned in the book, here are some commands that show how to perform text-based operations on the PDF. These commands need pdftotext to be installed.

DocView Resolution

If the text in the document looks pixelated in Emacs, set the doc-view-resolution variable to 300 as follows:

(setq doc-view-resolution 300)

This sets the dots per inch resolution used to render the documents to 300. This offers a good trade-off between high quality rendering and fast rendering. After setting this variable, type the following key sequences:

Clearing the cache directory and reopening the document in this manner regenerates the images from the documents with the updated resolution.

TRAMP

TRAMP stands for Transparent Remote Access, Multiple Protocol. The general syntax of paths supported by TRAMP is:

/method:[user@][hostname[#port]]:[path]

Here are some complete key sequences that demonstrate various ways to open a remote file using TRAMP:

This chapter recommends looking up the info manual page (tramp) Internal methods but this is very likely an error. For example, evaluating (info "(tramp)Internal methods") leads to the following error:

user-error: No such node or anchor: Internal methods

Instead evaluate (info "(tramp)Inline methods") to reach the correct node that describes the various connection methods.

Default Directory

The variable default-directory is buffer local. Typically, this is automatically set to the directory where Emacs was launched or to the directory of the currently visited file. Commands like C-x C-f defaults to looking up files in this directory.

While editing a remote file via TRAMP, the value for this variable may look something like /ssh:alice@box:/home/alice/. The @ character is displayed in the mode line while editng a remote file.

The chapter presents the following examples of commands that work seamlessly on a remote machine:

With Eshell we can go directly into remote directories seamlessly. The following Eshell session illustrates this:

Welcome to the Emacs shell

~ $ uname
Darwin
~ $ cd /ssh:alice@box:~/foo/
/ssh:alice@box:/home/alice/foo $ hostname
debian
/ssh:alice@box:/home/alice/foo $

Multi-Hops

Here are some commands that illustrate how multi-hops work:

Bookmarks with key sequences like C-x r m (bookmark-set), C-x r l (bookmark-bmenu-list), and C-x r b (bookmark-jump) work seamlessly for remote files (including multi-hops).

Eshell works seamlessy too across multi-hops. Here is an Eshell session that illustrates it:

~ $ uname
Darwin
~ $ cd '/ssh:alice@box1|ssh:bob@box2|ssh:carol@box3:/home/carol/foo/bar/'
/ssh:alice@box1|ssh:bob@box2|ssh:carol@box3:/home/carol/foo/bar $ uname
Linux
/ssh:alice@box1|ssh:bob@box2|ssh:carol@box3:/home/carol/foo/bar $

EWW: Emacs Web Wowser

The following commands are useful to get started with EWW.

In the EWW buffer, the following navigation keys work:

Further, EWW supports a few semantic browsing methods. The pertaining commands are presented below. However note that whether these commands would work on a page or not depends on whether the page provides the relevant navigation aids required by these commands. Here are the key sequences for such commands:

Invoking External Browser

If we would rather open a URL using our desktop web browser, then we can use the browse-url command like this: M-x browse-url RET http://example.net/ RET.

This command very conveniently picks up the word or domain name at the point or just before the point and uses that as the default value for the URL input. Therefore if the cursor is already on a URL, then we can simply type M-x browse-url RET RET to visit it.

Dired

Dired: Getting Started

There are several ways to start Dired. Some examples are presented below:

Dired: Navigation

The following keys work in a Dired buffer:

Note that when we go from one Dired buffer to another (say, by typing RET to enter a subdirectory from a parent directory), then typing q or C-u q buries or kills (respectively) the current buffer and takes us back to the last Dired buffer.

Dired: Marking and Unmarking

The following list describes marking and unmarking commands of Dired:

The following key sequences describe the effects of prefix arguments with marking, unmarking, and flagging commands:

Additionally, the chapter mentions the following commands in a separate table but on Emacs 28.2, they seem to have the same effect as one of the commands discussed earlier:

However the following commands (also introduced briefly in the same table but illustrated with complete key sequences below) provide additional marking and unmarking facilities:

The chapter also mentions a key sequence * . to mark files by extension but this requires dired-x, so this is discussed in a later section of this page.

Dired: Operations

This section explains some operations we can perform in Dired. If there are one or more items marked in the Dired buffer, then the operations work on the marked items. Otherwise, the operations work on the item under the cursor.

Note the difference between D and x. The key D deletes marked files but the key x deletes flagged files. Therefore there are two ways of deleting files:

I normally prefer the second way of deleting files. Since deleting file is a destructive operation which is possibly risky, I like to flag them first with d before deleting them with x. In other words, I flag files for deletions and mark files for everything else. Since I do not use the D key, I can be confident that my marked files are always safe and there is no risk of inadvertently deleting them.

Dired: Copying or Renaming Between Buffers

The following steps explain how we can copy or move files from one Dired buffer to another. First we will see the default behaviour and then we will customise Dired to copy or move files to a particular Dired directory.

  1. Type C-x d /usr/ RET.
  2. Type C-x 2.
  3. Type C-x d /tmp/ RET.
  4. Type C-x 2 agian.
  5. Type C-x d /etc/ RET.
  6. Move the cursor on some file in /etc/ and then type C or R and we will see that the default directory to copy/move the file to is /etc/.
  7. Now type (setq dired-dwim-target t).
  8. Now type C or R again while the cursor is on some file in /etc/. We will see that the default directory to copy/move the file to is /tmp/ now. Since dired-dwim-target is set to non-nil, Dired picks the directory from the next window with a Dired buffer and uses that as the target buffer.

Dired: More Keys

Here are some examples of Dired keys that do not act on marked files but does other interesting work:

To understand the usefulness of g, while Dired is open create a new file in the current directory with, say, C-x C-f foo.txt RET and save it with C-x s. Then kill the buffer for the file with C-x k and return to the Dired buffer. The Dired buffer does not show the new file foo.txt. Now type g to refresh the Dired buffer. As soon as g is typed, the buffer gets updated to display the new file.

To understand the difference between ! and & mark five files and then type the key sequence ! sleep 1; echo. Emacs blocks (i.e., does not react to our keystrokes) for 5 seconds while it runs the given command for each file. When the echo output for all files is obtained after 5 seconds, the output appears and Emacs unblocks again. Now type & sleep 1; echo. Now Emacs remains unblocked while the output of each echo command appears at one second intervals in the output buffer.

Dired-X

Dired-X provides extra Dired functionality. It is not enabled by default. To enable it, add the following line to the Emacs initialisation file:

(require 'dired-x)

The following key sequences are supported by Dired-X:

Dired: Working Across Directories

The following key sequences offer some support for working across multiple directories in the same Dired buffer:

Using i to insert the directory listing of a subdirectory into the current Dired buffer could feel tedious if we want to recursively work on multiple directories. The commands (illustrated with complete key sequences below) may be more suitable for such operations:

Shell Commands

The following complete key sequences demonstrate how we can invoke shell commands from Emacs.

The book also mentions that C-u M-& is supposed to work like C-u M-! but asynchronously but I did not find this to be true. For example, C-u M-& uname RET led to the following error Wrong type argument: stringp, (4). This may be a bug in Emacs 28.2.

Compiling in Emacs

The following complete key sequences demonstrate this feature:

The compile commands display the output in the *compilation* buffer where the following key sequences work:

Shells in Emacs

M-x shell

The key sequence M-x shell RET starts a shell with input/output done via a buffer. Some important points to keep in mind while using this:

Here are some key bindings that work in the shell buffer:

M-x ansi-term

The key sequence M-x ansi-term RET RET launches an ANSI-capable terminal emulator. It can run sophisticated programs like top, man, etc. that require terminal capabilities fine. The following key sequences are useful in this terminal emulator:

M-x eshell

The key sequence M-x eshell RET creates an interactive Eshell buffer if none exists, or switches to an existing one. Eshell is implemented in Elisp. It provides Elisp implementation of Unix commands like ls, cp, etc.

The list below provides examples of some commands we can enter directly into Eshell:

In the last point we see that for programs like top which need terminal capabilities to show output in a visual fashion (as opposed to just printing output to standard output or standard error), Eshell automatically runs the program in a term-mode buffer, so that the output of the visual program can be handled and displayed correctly. Eshell looks at the list in the variable eshell-visual-commands to determine if a command needs terminal support or not. By default, commands like vi, screen, tmux, top, etc. belong to this list.