Underling plain text in Emacs

emacs
code
A quick method for underlining text in Emacs
Author

Nicholas Van Horn

Published

December 12, 2013

Modified

June 15, 2015

Note

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 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.

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:

    (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

My section heading

into

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

My section heading
++++++++++++++++++

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

My section heading
== ======= =======

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

# My code comment
# ===============

The above also works for right-aligned text.