"Underlining" plain text in Emacs

Often when I’m writing emails, text documents, or code I like to create section headings (not unlike markdown headings) to provide visual segmentation to document structure. To quickly facilitate “underlining” text this way, I wrote the following convenience function in Emacs Lisp:

EDIT 2015-06-15: Boruch Baum improved upon my original code. The revised version included below has several additional desirable properties:

  • Aligns underlines properly under characters when line starts with whitespace.
  • Does not underline trailing whitespace.
  • Works if you already typed <enter> after typing line you want underlined.
  • Optionally does not underline any whitespace (with C-u C-u prefix).
  • Does not allow underlining with control characters or whitespace.
  • When underlining commented lines, inserts leading comment char.

The revised code snippet follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
(defun underline-text (arg)
  "Inserts a line under the current line, filled with a default
underline character `='. If point had been at the end of the
line, moves point to the beginning of the line directly following
the underlining. It does not underline the line's leading
whitespace, trailing whitespace, or comment symbols. With prefix `C-u'
prompts user for a custom underline character. With prefix `C-u
C-u', does not underline whitespace embedded in the line." ; Copyright 2015 Boruch Baum <boruch_baum@gmx.com>, GPL3+ license (interactive "p") (let* ((original-point (point)) (underline-char (replace-regexp-in-string "[[:cntrl:][:space:]]" "=" (if (= arg 1) "=" (char-to-string (read-char "What character to underline with?"))))) (original-point-is-eol (when (looking-at "$") t)) (original-point-is-eob (= original-point (point-max)))) (beginning-of-line) (unless (when (looking-at "[[:space:]]*$") (beginning-of-line 0) (when (looking-at "[[:space:]]*$") (goto-char original-point) (message "nothing to do"))) (insert (buffer-substring (line-beginning-position) (line-end-position)) "\n") (save-restriction (narrow-to-region (progn (goto-char (1- (re-search-forward "[^[:space:]]" nil t))) (cond ((looking-at ";+") (match-end 0)) ((looking-at "#+") (match-end 0)) ((looking-at "//+") (match-end 0)) ((looking-at "/\\*+") (match-end 0)) (t (point)))) (1+ (progn (goto-char (line-end-position)) (re-search-backward "[^[:space:]]" nil t)))) (untabify (point-min) (point-max)) (goto-char (point-min)) (if (= arg 16) (while (re-search-forward "[^[:space:]]" nil t) (replace-match underline-char nil)) (re-search-forward "[^[:space:]]" nil t) (goto-char (1- (point))) (while (re-search-forward "." nil t) (replace-match underline-char nil))) (widen)) (if original-point-is-eob (goto-char (point-max)) (if original-point-is-eol (goto-char (re-search-forward "^")) (goto-char original-point))))))

Once the above code is evaluated, entering *C-c u* will turn

1
My section heading

into

1
2
My section heading
==================

By preceding with the universal argument, you will be prompted for the underlining character. So entering *C-u C-c u +* will produce

1
2
My section heading
++++++++++++++++++

A double prefix followed by a underlining character will skip whitespace. For example, *C-u C-u C-c u =* produces

1
2
My section heading
== ======= =======

If the line to be underlined is a code comment, the necessary comment character is prepended to the underline

1
2
# My code comment
# ===============

The above also works for right-aligned text.