From Fill Prefix to TRAMP

By Susam Pal on 30 Dec 2023

Our tiny book club that used to meet during the weekends and holidays and discuss the book Mastering Emacs, 2022 edition concluded today. In our final meeting today, we first discussed how to work across multiple directories in the same Dired buffer. Then we did several demos of the various shells and terminal modes available in Emacs out of the box. That completed our discussion on Chapter 6. Then we moved on to Chapter 7 (the final chapter) that first reiterates the importance of using the describe-system to ask Emacs questions about itself and then offers some recommendations about third-party packages and online Emacs communities. Completing this chapter brought our book club discussions to an end.

A big thanks to Mickey Petersen for writing the book and also very generously granting me the permission to share his book on screen while discussing it.

This book club began on 16 Dec 2022 when we had our first meeting over Jitsi. About 3½ months after beginning these meetings, I posted an update about this book club in another blog post titled From Lunar Phases to Yank-Pop. If you have not read that post yet, I suggest you read it before returning to this post. Especially if you have recently begun learning Emacs, I think you will find that post useful.

Back then, when I posted that last update, we had spent about 26 hours together across 36 meetings and we were reading Chapter 5 of the book. It took another 36 meetings to complete that chapter and the remaining two chapters. After a total of 72 meetings, we completed discussing Chapter 7 of the book today which concluded this series of book club meetings. In total, we have spent a little over 52 hours together to discuss this book, trying out every concept and command introduced in the book, and sharing our insights about the material with each other.

In this post, I will share some highlights from our meetings since the last update. These highlights share some concepts and commands we learnt that most members of our book club were not familiar with earlier but were found to be very useful after having learnt them.

Contents

Fill Prefix

Most of us in the book discussion group knew about filling paragraphs with M-q. Consider the following badly formatted paragraphs with very long and very short lines:

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore
magna aliqua.  Arcu dui vivamus arcu felis bibendum ut tristique et egestas.
Bibendum arcu vitae
elementum curabitur vitae.

Now put the point (cursor) anywhere on the paragraph and type M-q. The paragraph gets neatly formatted to something like this:

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua.  Arcu dui
vivamus arcu felis bibendum ut tristique et egestas.  Bibendum arcu
vitae elementum curabitur vitae.

The key sequence M-q invokes the fill-paragraph command that reformats the paragraph such that each line is as long as possible without exceeding the fill width (70 columns by default). Most of us already used this command very often while writing and editing text. However what some of us did not know was that there is such a thing as fill prefix which is taken into account while filling paragraphs. To illustrate this concept, we will first consider this badly formatted paragraph:

:::: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
:::: incididunt ut labore et dolore
:::: magna aliqua.  Arcu dui vivamus arcu felis bibendum ut tristique et egestas.
:::: Bibendum arcu vitae
:::: elementum curabitur vitae.

Each line has a prefix consisting of four colons and a space. After we reformat this paragraph with M-q, we get something like this:

:::: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor :::: incididunt ut labore et dolore :::: magna aliqua.
Arcu dui vivamus arcu felis bibendum ut tristique et egestas.  ::::
Bibendum arcu vitae :::: elementum curabitur vitae.

This is not what we want. We want the paragraph to be formatted such that each line does not exceed 70 characters in length (which we have, in fact, accomplished above) and each line contains the four colons and a space as the prefix (this is broken above). Can we do this? Yes, by setting the fill prefix. Type C-/ or C-x u to undo the bad formatting we did just now and let us try again. This time move the point over to the L of Lorem and type C-x . to set the fill prefix to the current line up to the point. A confirmation is printed in the echo area that ":::: " has been set as the fill prefix. Then type M-q and the paragraph is now neatly formatted to look like this:

:::: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
:::: eiusmod tempor incididunt ut labore et dolore magna aliqua.  Arcu
:::: dui vivamus arcu felis bibendum ut tristique et egestas.
:::: Bibendum arcu vitae elementum curabitur vitae.

Note how every line is as long as possible without exceeding 70 characters in length and each line has the fill prefix. Emacs took care to remove the fill prefix from each line, subtract the length of the fill prefix from the maximum character budget it has for each line, reformat the lines, and then reinsert the fill prefix on each line of the result.

To turn off the fill prefix, simply set it to an empty prefix by typing C-x . at the beginning of the line. Thus C-a C-x . becomes an idiom for turning off the fill prefix.

Elisp Expressions in Replacement Strings

It was no surprise to anyone in the book discussion group that the key sequence C-M-% f.. RET bar RET starts a search-and-replace operation for strings that match the regular expression pattern f.. to be replaced with the text bar.

The concept of backreferences was also known to most. For example, C-M-% \(f..\)-\(b..\) RET \2-\1 RET searches for strings matching the given regular expression pattern and replaces it with a new string that swaps the positions of the first capturing group and the second capturing group. The backreference \1 refers to the string matched by the first capturing group \(f..\) and similarly \2 refers to the string matched by the second capturing group \(b..\). In this example, a string like foo-bar is replaced with bar-foo, or playful-banter with playban-fulter.

However what came as a surprise to some of us was that we could also use Elisp expressions in the replacement strings. The syntax to do so is to write \, followed by the Elisp expression in the replacement string. For example, consider the key sequence C-M-% f.. RET \,(upcase \&) RET. Note how we are using the backreference \& that refers to the whole match as the argument to the Elisp function upcase that converts its argument to upper-case. This example searches for strings that match the pattern f.. and replaces them with the upper-case form of the match. A string like foo-bar is replaced with FOO-bar.

Here is another slightly more sophisticated example: C-M-% port-\([0-9]+\) RET port-\,(+ 1000 \#1). The backreference \#1 refers to the string matched by the first capturing group \([0-9]+\) as number. The Elisp expression in the replacement pattern simply adds 1000 to that number and replaces the matched string with the result. A string like port-80 becomes port-1080.

Keep Lines and Flush Lines

A nifty set of commands that our group members enjoyed learning were the commands for keeping and flushing lines. These commands can be incredibly useful while filtering large log files. Here is a brief illustration of a couple of these commands:

Note how each point above mentions that the deleted lines are not copied to the kill ring. This can be an inconvenience if we want to quickly yank the deleted lines to another buffer. Emacs 28.1 introduces a couple of more commands that remedy this situation to an extent. Here they are:

Keyboard Macros

Most experienced Emacs users in our group were aware of keyboard macros. However, some people did learn this wonderful automation feature for the first time in our meetings, so I thought this deserves its own section in this post.

Keyboard macros is a large topic on its own which is best learnt from section Keyboard Macros of the manual. On Emacs, type M-: (info "(emacs) Keyboard Macros") RET to open this section using the Info documentation browser. In this blog post though, we will very briefly talk about keyboard macros that should be enough to get someone very new to it started with them quickly.

Say, we have a buffer that looks like this:

foo:bar:baz
bar:baz:qux
quux:corge:grault
garply:waldo:fred

Now suppose we want to swap the first two fields separated by colon in each line. Of course, we could do it using regular expressions, for example, with the key sequence C-M-% ^\(.+?:\)\(.+?:\) RET \2\1 RET. But we can also solve this problem in a "dumb" way by simply performing the edits necessary to do the swap on one line and then asking Emacs to repeat what we did on the other lines. Here are the steps:

  1. First move the point to somewhere on the first line.

  2. Then type C-x ( to start recording a keyboard macro.

  3. Then type C-a M-d C-d M-f : C-y C-n to swap the first and second fields on the first line and move the point to the next line. This is just one way to achieve the swap. You may use any editing commands you are comfortable with to make the swap happen and move the point to the next line.

  4. Now type C-x ) to stop macro recording.
  5. Now type C-x e to replay the macro in the second line. As soon as we type this key sequence, the swap occurs in the second line and the cursor moves to the third line. Keep repeating this key sequence to keep repeating the swap operation on subsequent lines.

To summarise, C-x ( starts recording a new keyboard macro, C-x ) stops recording the keyboard macro, and C-x e replays the last keyboard macro. Alternatively, we could also use the function keys F3 and F4. To start recording a keyboard macro, type F3. Type F4 to stop recording a keyboard macro. Then type F4 again to replay the last macro.

Pay close attention to step 3 above. We start the key sequence with C-a to move the point to the first column. This may feel redundant when the cursor is already at the first column. However in our meetings, I used to emphasise often about the importance of doing this. Typing C-a at the beginning ensures that we do not carry over any assumption about where the cursor is on the line into the rest of the keyboard macro definition we are going to record. By typing C-a, we ensure that no matter where the cursor is on the line, when we replay the macro, the cursor would first move to the beginning of the line. This guarantee allows us to confidently define the rest of the editing operations necessary to perform the swap.

Similarly, at the end we type C-n to move the point to the next line. I used to emphasise the importance of doing this too in our meetings. Moving the cursor to the next line ensures that the cursor is in a good place to allow repeating the keyboard macro again immediately. This is why we could type C-x e (or alternatively, F4) over and over again to replay the macro on subsequent lines. In fact, if we feel confident about the keyboard macro, we can repeat it several times automatically using the digit argument. For example, type C-3 C-x e (or alternatively C-3 F4) to repeat the keyboard macro 3 times. We could also type C-0 C-x e (or alternatively C-0 F4) to repeat the keyboard macro until there is an error (e.g., reaching the end of the buffer).

DAbbrev

DAbbrev stands for dynamic abbrevation. This is a pretty useful package that many of us learnt only from our book club meetings. We discussed two simple key sequences supported by this package:

Let us look at some examples. Suppose there is a buffer with the following one line of text:

abacus apple appliance application

Now if we type ap on the next line and type M-/, DAbbrev automatically expands the partially written word to application because that is the nearest word that has ap as the prefix.

But if we type ap and type C-M-/, the word expands to appl since that is the longest common prefix among all the matching words. If we type C-M-/ again, then apple, appliance, and application are presented as possible completions in a temporary buffer named *Completions*. If we type ic, so that the word before the cursor becomes applic, and type C-M-/, it is expanded to application because that is the only possible completion now.

These two commands are simpler than it sounds from the verbose descriptions of these commands presented in the above paragraphs. When we actually begin to use them, they become intuitive in no time. Roughly speaking, while M-/ expands the word before the point to the nearest preceding word, C-M-/ considers all matching words in the file for expansion and presents completion options to the user when it finds multiple of them.

TAB vs M-i

The behaviour of Emacs when we type TAB can be surprising to beginners. In most other mainstream editors, this key either inserts a tab character or it inserts enough number of spaces so that the cursor moves to the next tab stop. But in Emacs, TAB most often indents the current line according to the syntax rules implemented by the major mode enabled in the buffer.

What is a simple key on other editors happens to be a complex feature in Emacs. The exact behaviour of TAB is controlled by variables like tab-always-indent, indent-line-function, etc. Some major modes may refer to other such special variables to decide what TAB should do. However, as a user of Emacs this is not something we normally have to worry about. Most major modes set up all these variables appropriately, so that TAB almost always does what an experienced Emacs user expects, i.e., indent the current line of code correctly.

But what if we really do want to just insert a tab or enough number of spaces to move the point to the next tab stop column? That is done with M-i. If the variable indent-tabs-mode is set to t, then M-i inserts a literal tab character. If it is set to nil, then M-i inserts enough number of spaces to move the point to the next tab stop column.

To summarise, the behaviour of M-i is similar to the TAB behaviour we observe in other editors. In practice though, the key sequence M-i is rarely required. Most people just type TAB to automatically indent code. In fact, we can also select a region of code and type TAB to reindent the whole region.

Project Management

The project management commands that come out of the box (from the package named project.el) came as a surprise to some. In fact, some members of our group who never used the project management commands earlier happen to use them regularly now after learning about them in our meetings.

When we use a project command like C-x p f to visit a file in the current project, the command automatically detects the top-level directory of the project by checking parent directories for version control system artefacts (e.g., .git directory) and presents files within that top-level directory as autocomplete options.

There is a lot that can be written about the project management features that come out of the box in Emacs. The following list introduces only the very simple ones to get someone started with them:

There are several more project commands but we will end the above list here for the sake of brevity. Do pay attention to the second point that mentions that if the current file does not belong to any project, we are first prompted to enter the project name. This is a common theme for all project commands. Anytime we invoke a project command, it works on the current project. However if there is no current project, then it automatically prompts us to enter a project name before executing the command.

The key sequence C-x p p ... RET is very rarely required during day-to-day editing activities. Once a project has been discovered (say, due to having run a project command on that project earlier) and added to the list of known projects at ~/.emacs.d/projects, we never have to discover it again. We can use the other key sequences to switch to or work on a known project. Most day-to-day project activities involve working on known projects.

Further, even when we do want to discover a new project and add it to the list of known projects, a much more natural way to do it is to run a project command while we are visiting a file in the project directory. In most cases, we already have a file from some project open in the current buffer. Therefore it makes more sense to just go ahead with a project command, say, with C-x p f, C-x p g, etc. directly instead of explicitly discovering the project with C-x p p ... RET. Merely running a project command while we have a file from a project open ends up discovering the current project automatically. Explicitly discovering projects with C-x p p ... RET is almost never necessary.

Eshell with TRAMP

Many members of our group knew about Eshell and TRAMP separately. For example, M-x eshell RET starts Eshell. Eshell is implemented purely in Elisp and we can use it much like a regular shell. Here is an example session:

~ $ cd /tmp/
/tmp $ echo hello > hello.txt
/tmp $ cat hello.txt
hello
/tmp $ python3 --version
Python 3.11.5
/tmp $ which cd echo cat python3 which
eshell/cd is a byte-compiled Lisp function in ‘em-dirs.el’.
eshell/echo is a byte-compiled Lisp function in ‘em-basic.el’.
eshell/cat is a byte-compiled Lisp function in ‘em-unix.el’.
/usr/bin/python3
eshell/which is a byte-compiled Lisp function in ‘esh-cmd.el’.

We also knew about TRAMP. For example, when we type the key sequence C-x C-f /ssh:alice@box:/tmp/foo.txt RET, TRAMP notices that we intend to connect to a remote system named box as the user alice via SSH and edit a file named /tmp/foo.txt on the remote system. TRAMP then transparently establishes the SSH connection for us. If public key authentication is already set up, then the connection is successfully established immediately. Otherwise it prompts for a password. In the end, we get a buffer to edit the remote file /tmp/foo.txt. Once we have this buffer, we never have to do anything special to work on the remote file. All Emacs commands work seamlessly on this buffer for the remote file. For example, when we type C-x C-s TRAMP would go ahead and save the file to the remote system using the established SSH connection. If we type C-x d, TRAMP would create a Dired buffer for the remote directory /tmp/. All the Emacs commands for working with files and directories we know just work fine with the remote file or directory.

So we knew about Eshell and we knew about TRAMP. However what many of us found pleasantly surprising was how remarkably well Eshell and TRAMP work together. Here is an example Eshell session that illustrates this point:

~ $ cd /ssh:alice@box:/tmp/
/ssh:alice@box:/tmp $ echo foo > foo.txt
/ssh:alice@box:/tmp $ ls
foo.txt
/ssh:alice@box:/tmp $ cd /tmp/
/tmp $ echo bar > bar.txt
/tmp $ ls
bar.txt
/tmp $ cp /ssh:alice@box:/tmp/foo.txt .
/tmp $ ls
bar.txt  foo.txt
/tmp $

Look at how the command cd /ssh:alice@box:/tmp/ above has seamlessly and transparently set the current directory of the shell to the remote directory. When we create a file after that, it gets created on the remote directory! We can work across directories opened with multiple TRAMP methods too. For example first consider this session where the current local user does not have the permissions to write to the local /etc/ directory:

~ $ cp /ssh:alice@box:/etc/wgetrc /etc/
Opening output file Permission denied /etc/wgetrc

But if the current user has sudo privilege, we can do something like this:

~ $ cp /ssh:alice@box:/etc/wgetrc /sudo::/etc/
~ $ ls /etc/wgetrc
/etc/wgetrc

We copied a file from a remote system and wrote it to a protected directory on the local system by using the sudo privilege. We used the ssh method to read a remote file and the sudo method to write the file to a protected local directory. TRAMP really does live up to its name: Transparent Remote Access, Multiple Protocol!

Thanks

It has been a pleasure hosting these Emacs book club meetings throughout this year. I have really enjoyed discussing the book in great detail, examining each new concept introduced in the book carefully, and performing demos to illustrate the concepts. A big thank you to the Emacs communities on Libera and Matrix networks who showed interest in these meetings, joined these meetings, participated in the discussions, and helped make these meetings successful!

Comments | #emacs | #meetup | #technology