On using text editors

Text editors I use:

ed(1)

The best text editor is {gnu ed}, a line editor released in 1969. It's still actively maintained; currently at version 1.17. It has a small set of commands that can be very effectively combined.

General usage

The general usage of ed is very terse and relies on regular expression operators, which are applied linewise.

For example, to change arbitrary text inside brackets on a given line, one can execute s/(.*)/(reg). Similarly, we can put a blank line after lines matching a pattern via g/reg/s/$/\, trim leading whitespace via s/^[ ]*/, or trailing whitespace via s/[ ]*$/.

ed has some special operators. For example, \( and \) can be used to capture regex. The command g/canon/s/\(canon\/\)/\1index.html, where \1 evaluates to canon/, substitutes canon with canon/index.html throughout the text. The operator % evaluates to the current file name. For example, in ed, typing !cc % compiles the current file.

ssh support

In ed, remote editing via ssh is possible:

r !ssh user@host "cat /path/to/local/file"
w !ssh user@host "cat > /path/to/remote/file"

External tools

ed can utilize other command line tools for different purposes. It can use fmt to break lines at 72 chars by using e !fmt -w 72 %, which is straight forward.

Piping a selection to fmt is more complicated. It can be achieved by combining sed with fmt as:

r !sed -n '[n],[m]p' % | fmt -w 72

where [n] and [m] are line numbers.

Scripting

The easiest way to script ed is using here scripts:

ed -s file <<< $'H\n\nw'

Here, H lets ed print out help messages.

Resources

{The manual} is a good place to start and to look things up. A soft introduction to ed(1) can be found in the excellent article {Actually using ed} by T. Ryder. Examples for scripting ed(1) are given in {Can you use ed as a replacement for vim, grep & sed?} by R. Elder.

emacs

General usage

Just use {ed}.

If you need to work with multiple files and don't want to use ed, {gnu emacs} is an editor with a graphical user interface.

When using emacs, it is sometimes advised to launch it as a daemon with server-start and keep it running during the entire session. This allows you to open other files from the command line through the emacsclient command. Files will open in the emacs window that is already open.

However, I prefer to start a single instance and use eshell (or, if you must, shell) inside it. The advantage of this is that files can now be opened directly with C-f and emacs keybindings are consistent inside the shell.

Manual

The {emacs manual} is very good. Within emacs, it can be evoked from everywhere by executing C-h r. If it doesn't work, it's probably not installed. In {debian}, it can be installed via apt install emacs-common-non-dfsg for emacs 26.

Hippie Expand

When activated, {Hippie Expand} provides low-overhead auto-completion, based on text in open buffers. Start typing a word, and then invoke auto-completion with M-/. Hippie Expand completes the word by cycling through different auto-completion mechanisms that can be specified in the configuration file—see below. If the correct expansion of the word has been passed by accident, use C-/ to cycle back.

Keyboard macros

emacs lets you record a series of commands, which are called {keyboard macros}. We use C-x ( to start and C-x ) to end the recording. Once recorded, the macro can be executed repeatedly through C-x e. This is useful if the same action has to be performed repeatedly, but there is no good pattern to capture the location. This allows more general structural editing than the regular expression-based workflow in {gnu ed}.

apply-macro-to-region-lines runs the keyboard macro on each line in the region. I also sometimes just supply a number value as C-[number] C-x e, which repeats the execution of the macro as many times as the value of [number].

gnus

{gnus} is a news and email reader that ships with emacs. It lets you "subscribe" to email inboxes and, for example, RSS feeds.

Basic usage

gnus hides emails that have been read. To view these older emails, use /o, which displays all email. This can be changed in the configuration file, but I’m OK with the defaults.

gnus stores login credentials in: ${HOME}/.authinfo

gnus can de- and encrypt emails using {GnuPG}. See {this section of the manual} for more details. If the key is setup with the corresponding email address, the encryption and decryption do not need further configuration.

Emails can be encrypted via C-c C-m c o. Decrypting is done automatically, if you receive an encrypted message and you have the corresponding key added to your keyring.

Spam filter

Some spam mail looks like it's coming from a legitimate sender and will not be detected by your email service. For example, the confidence artists from Apple keep sending me "important information" I never asked for and from which I can't unsubscribe from, because they forced me to create an Apple account on my work computer. Clearly, this means I consent to get two emails per day, informing me about products and other "fun activities."

gnus has the capability to filter out such spam mail. The {spam-package} provides functionality to sanitize your inbox. In order to use this package, run M-x spam-initialize, or alternatively (spam-initialize) in your configuration file. Then, move the cursor on the spam mail and mark it with M-d. The mail is now marked as spam (a $-sign should appear next to it in the gnus summary buffer. The mail is moved to the spam folder when you exit gnus.

Dired

dired is a file browser that is shipped with emacs.

Selecting multiple files

Navigate in the directory holding the files you want to search in. Using m, mark all files you want to modify.

If there are too many files to mark by hand, one can execute M-x find-dired and then provide suitable flags, such as -name "*.cpp". This opens another dired buffer with the files that match the regex. If you want to select all matches, press t, which toggles marks.

Replacing regular expression in several files

In dired, we can replace a regular expression across several files. Select multiple files as described above. Following {the manual entry on the subject}, execute dired-do-find-regexp-and-replace or, alternatively, press Q. Follow the instructions. The files need to be explicitly saved.

Saving multiple files

For a large number of files, one can execute M-x ibuffer. Mark all unsaved files with * u and save them via S, that is to say Shift+s.

pdf-tools

pdf-tools is a suite to view and modify pdf files. In my opinion, it works better than doc-view, which relies on converting pdf files into png files. It can be installed through the package-manager from melpa.

If you only occasionally view pdf documents with emacs you don't need this package.

Underrated functionalities

Configuration

init.el

Older versions of emacs used to be configured through a file called .emacs, placed in your ${HOME}. While modern versions still support this, it's recommended to write configuration into ${HOME}/.emacs.d/init.el instead. For example, my init.el reads:

;; dired

(put 'dired-find-alternate-file 'disabled nil)
(setq delete-by-moving-to-trash t)

;; hippie expand

(global-set-key (kbd "M-/") 'hippie-expand)
(setq hippie-expand-try-functions-list '(try-expand-dabbrev
try-expand-dabbrev-all-buffers
try-expand-dabbrev-from-kill
try-complete-file-name-partially
try-complete-file-name
try-expand-all-abbrevs
try-expand-list
try-expand-line
try-complete-lisp-symbol-partially
try-complete-lisp-symbol))

;; ido

(require 'ido)
(setq ido-enable-flex-matching t)
(setq ido-everywhere t)
(ido-mode t)

;; eww

(setq browse-url-browser-function 'eww-browse-url)
(setq browse-url-generic-program "dillo")
(setq eww-search-prefix "https://wiby.org/?q=")

;; calendar and diary

(appt-activate t)

;; org

(org-babel-do-load-languages
'org-babel-load-languages
'((calc . t)
(gnuplot . t)
(C . t)))

;;
;; -- custom functions
;;

(load "~/.emacs.d/elisp/custom-functions.el")

.gnus.el

It’s pretty easy to set up a basic email reader. At this moment, I don’t need any more advanced functionality. The configuration file .gnus.el goes in the ${HOME} directory.

(setq user-full-name "User Name" user-mail-address
"user@example.what")

(setq message-send-mail-function 'smtpmail-send-it
smtpmail-default-smtp-server "example.domain.what"
smtpmail-smtp-service 587
smtpmail-local-domain "name-of-pc")

(setq gnus-select-method '(nnnil ""))
(setq gnus-secondary-select-methods '((nnml "")
(nnimap "example.domain.what"
(nnimap-address "example.domain.what")
(nnimap-server-port 993)
(nnimap-stream ssl)
(nnmail-expiry-wait 90))))

(setq gnus-message-archive-method '(nnimap "example.domain.what")
gnus-message-archive-group "Sent")
(setq gnus-gcc-mark-as-read t)

Notes

Hack solutions

Some hack solutions for academic needs. These are placed in custom-functions.el, loaded in the init.el above. Elisp doesn't have namespaces, so it is recommended to use a custom prefix—here I use "il"—to avoid accidentally overwriting other functions.

Pomodoro

This allows you to call the set-timer function through M-x. You provide the function an interval of X minutes. After X minutes have passed, the function flash-mode-line is called. The mode line flashes, reminding you to take a break.

(defun il-flash-mode-line (str) (invert-face 'mode-line)
(run-with-timer 1.0 nil #'invert-face 'mode-line) (message str))
(defun il-set-timer (interval)
(interactive "s > set interval: ")
(run-at-time (format "%s min" interval) nil #'il-flash-mode-line " > time for a break.")
(message (format " > will alert in %s min." interval)))

Switch between dark and light themes

Tries to invert the color scheme. Works well with the default theme. I haven’t tested it with custom color themes.

(defun il-switch-colors ()
(interactive)
(invert-face 'default))

Get bibtex citation by DOI

This has two implementations, which may give different results. The first one is a pure elisp implementation, the second one calls the external curl program. The original pure elisp implementation is from {P. Iosad}.

(defun il-doi-to-bib-elisp (doi)
"modified from iosad (https://www.anghyflawn.net/blog/2014/emacs-give-a-doi-get-a-bibtex-entry/)"
(interactive "M > doi: ")
(get-buffer-create "*doi2bib-el*")
(switch-to-buffer "*doi2bib-el*")
(let ((url-mime-accept-string "text/bibliography;style=bibtex"))
(with-current-buffer
(url-retrieve-synchronously (format "http://dx.doi.org/%s" doi))
(switch-to-buffer (current-buffer))
(goto-char (point-max))
(setq bibtex-entry (buffer-substring (string-match "@" (buffer-string)) (point)))
(kill-buffer (current-buffer))))
(insert (decode-coding-string bibtex-entry 'utf-8))
(bibtex-fill-entry))

(defun il-doi-to-bib-bash (doi)
(interactive "M > doi: ")
(shell-command (format "curl -LH \"Accept: application/x-bibtex\" http://dx.doi.org/%s" doi)))

ed(1)-like shell escape

In ed(1), when executing a shell command, the character "%" expands to the current file name. This can be achieved in emacs as well. Original implementation by {R. Grau}.

(defun il-shell-execute ()
(interactive)
(let ((file-buffer (buffer-file-name))
(command (read-shell-command " > shell command: " nil nil
(let ((filename
(cond
(buffer-file-name)
((eq major-mode 'dired-mode)
(dired-get-filename nil t)))))
(and filename (file-relative-name filename))))))
(shell-command (replace-regexp-in-string "%" file-buffer command))))

Create a quick note

I keep a small collection of notes in my .emacs.d/ directory. The following function allows me to create a note in my default note directory (.emacs.d/org/notes):

(defun il-create-note (title tags context)
"create a note in fleeting notes directory"
(interactive "s > title: \ns > Tags: \ns > Context: ")

(setq date (current-time-string))
(setq prefix (replace-regexp-in-string ":" "" (format-time-string "%Y%m%d-%T")))
(setq prefix (concat "~/.emacs.d/org/notes/" prefix))
(message (format " > title received: %s" title))

(setq downcase-title (downcase title))

(setq text (concat "#+title: " title "\n#+language: en\n#+startup: showall\n\n
                    * Metadata\n\n- tags: /" tags "/\n- context: " context "\n"))

(setq filename (concat prefix "-" (replace-regexp-in-string " " "-" downcase-title) ".org"))
(write-region text nil filename)
(message (format " > created note: %s" filename)))

Resources

The best resource to look stuff up is {the official manual}. A good curated list of articles and news can be found at {Planet Emacslife}.

See also:

Last updated: Wed Oct 13 09:51:12 2021