Emacs Lisp: How to Write a Command for Comment Handling

By Xah Lee. Date: . Last updated: .

This page shows you how to write a command to insert/delete comment syntax chars of a programing language.

First, you should know about emacs's builtin comment package newcomment.el. See: Emacs Lisp: How to Write Comment/Uncomment Command in Major Mode.

Problem

You want to write a emacs command to comment or uncomment code for your own language. You want to write it from scratch.

Technically, this lesson shows you how to write a command that either insert some string or delete some string in particular positions based on the text in current line or text selection.

Detail

Emacs has a standard command to insert or delete comment characters in a smart way, named comment-dwim. It already handles most languages. For your own language, you can implement a comment handling command based on it, with less than 10 lines of code. (For how, see: Emacs Lisp: How to Write Comment/Uncomment Command in Major Mode.)

However, you may not want to implement your comment command based on it. For example, you may not like its behavior, or can't have your code depend on it. For example, when there's no active region and current line is a comment, it doesn't do anything except moving cursor. Also, newcomment.el is about 1300 lines of lisp code, designed to handle comments in general. You may find this to be overkill, and don't want your package to depend on it.

So, your task now is to write your own comment command from scratch.

Solution

The following code handles C++-style comment // …. The code can be easily modified to handle any comment syntax that starts with a comment char and ends in a newline. (for example, Perl's # …, lisp's ; …, TeX's % …, Visual Basics's ' …)

It has 3 user-level functions:

“my-comment-dwim” is the general command. If there are no text selection, then it will comment or uncomment the current line, depending on whether the current line is a comment (If the comment start in the middle of the line, the line is not considered a comment). If there is a text selection, then it will comment or uncomment the whole region. Which action it does depends on whether the first line in selection is a comment line.

(defun my-comment-dwim ()
  "Comment or uncomment the current line or text selection."
  (interactive)

  ;; If there's no text selection, comment or uncomment the line
  ;; depending whether the WHOLE line is a comment. If there is a text
  ;; selection, using the first line to determine whether to
  ;; comment/uncomment.
  (let (p1 p2)
    (if (use-region-p)
        (save-excursion
          (setq p1 (region-beginning) p2 (region-end))
          (goto-char p1)
          (if (wholeLineIsCmt-p)
              (my-uncomment-region p1 p2)
            (my-comment-region p1 p2)
            ))
      (progn
        (if (wholeLineIsCmt-p)
            (my-uncomment-current-line)
          (my-comment-current-line)
          )) )))

(defun wholeLineIsCmt-p ()
  (save-excursion
    (beginning-of-line 1)
    (looking-at "[ \t]*//")
    ))

(defun my-comment-current-line ()
  (interactive)
  (beginning-of-line 1)
  (insert "//")
  )

(defun my-uncomment-current-line ()
  "Remove “//” (if any) in the beginning of current line."
  (interactive)
  (when (wholeLineIsCmt-p)
    (beginning-of-line 1)
    (search-forward "//")
    (delete-backward-char 2)
    ))

(defun my-comment-region (p1 p2)
  "Add “//” to the beginning of each line of selected text."
  (interactive "r")
  (let ((deactivate-mark nil))
    (save-excursion
      (goto-char p2)
      (while (>= (point) p1)
        (my-comment-current-line)
        (previous-line)
        ))))

(defun my-uncomment-region (p1 p2)
  "Remove “//” (if any) in the beginning of each line of selected text."
  (interactive "r")
  (let ((deactivate-mark nil))
    (save-excursion
      (goto-char p2)
      (while (>= (point) p1)
        (my-uncomment-current-line)
        (previous-line) )) ))

If your code is for the public, you may want to change the “my-” prefix to something more unique, such as your name's initials. “my-” is not a good prefix because otherwise lots of packages will have “my-this”, “my-that”, which contributes name conflict. Elisp does not have namespaces. 〔➤see Emacs Lisp's Library System: What's require, load, load-file, autoload, feature?

Like it? Buy Xah Emacs Tutorial. Thanks.