Is it possible to configure multi-line comments to comment/uncomment selected text?

For example C/C++'s preprocessor could be toggled like this.



    // a comment
    Some(code); /* some comment */


#if 0
    // a comment
    Some(code); /* some comment */

Since C/C++ don't support nested comments, I find this preferable behavior to quickly disable chunks of code.

  • What do you mean "c/c++ don't support nested comments"? What would this buy you over Emacs's standard commenting logic, which is fairly intelligent? The example you give is possible out of the box.
    – InHarmsWay
    Aug 19, 2017 at 8:12
  • No, I meant using 'comment-line, you can disable a chunk of code even if those lines themselves contain a comment. Emacs will escape the embedded comment characters.
    – InHarmsWay
    Aug 19, 2017 at 8:19
  • 1
    Nice, I wasn't aware it would escape nested comments, thats handy. Even so, I find it makes for noisy diffs, so would prefer to use the preprocessor.
    – ideasman42
    Aug 19, 2017 at 8:22

1 Answer 1


This can be done by matching the beginning & end of the region, removing when found, adding when not found.

The following example is a generic function which takes arguments which can be set for each major-mode.

(defun my-comment-multi-line-toggle--impl (head tail head-regex tail-regex)
  "Utility for toggling multi-line comments.
Argument HEAD Start of multi-line comment.
Argument TAIL End of multi-line comment.
Argument HEAD-REGEX Match the start of a multi-line comment.
Argument TAIL-REGEX Match the end of a multi-line comment."
  (unless (use-region-p)
    (user-error "No active region"))
  (let ((beg (region-beginning))
        (end (region-end))
        (match-head nil)
        (match-tail nil)
        ;; Prevent local language settings
        ;; impacting basic space stepping.
        (syntax-table (make-syntax-table)))
      (with-syntax-table syntax-table
        ;; True when we have multi-line comments.
        (if (and (setq match-head
                         (goto-char beg)
                         (skip-chars-forward "[:blank:]")
                         (when (looking-at head-regex)
                 (setq match-tail
                         (goto-char end)
                         (skip-chars-backward "[:blank:]")
                         (when (looking-back tail-regex)
                 ;; Double check there is no overlap.
                 (<= (save-match-data
                       (set-match-data match-head)
                       (match-end 0))
                       (set-match-data match-tail)
                       (match-beginning 0))))

            ;; Remove multi-line comment.
            (dolist (match (list match-tail match-head))
                (set-match-data match)
                (goto-char (match-beginning 0))
                (replace-match "" t nil nil))
              (when (looking-at-p "[[:blank:]]*$")

          ;; Add multi-line comment.
          (goto-char end)
          (insert tail)
          (insert "\n")

          (goto-char beg)
          (insert head)
          (insert "\n"))))))

(defun my-comment-multi-line-toggle (&optional head tail head-regex tail-regex)
  "Toggle multi-line comments for various languages."
   ;; Support optionally passing in arguments.
   ((and head tail head-regex tail-regex)
     head tail head-regex tail-regex))
   ((member major-mode '(c-mode c++-mode glsl-mode))
     "#if 0"
   ((member major-mode '(lua-mode))
   ((member major-mode '(cmake-mode))
   ((member major-mode '(python-mode))
    (user-error "Multi-line comment unsupported major mode '%s'" major-mode))))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.