6

I want to adjust the behavior of comment-dwim when editing c++ files: When a region is selected that either starts or ends in the middle of a line I would prefer /* ... */ comments. For the other cases // per line is fine.

Example:

int foo(int arg) {
    // int a = 0;
    int b = 3;
    return arg /*+ a*/ + b;
}

Can this be done?

As another reference point, QtCreator behaves the way I'm trying to describe.

B_old
  • 717
  • 5
  • 14

2 Answers2

2

The reason that c++-mode does not use the /* ... */ syntax is because the variables comment-start and comment-end are set to "// " and "" by default in C++-mode. Changing them to "/* " and " */" doesn't quite achieve the effect you asked for, because then it always uses the /* ... */ syntax instead of "//".

To use /* ... */ only for regions in c++-mode that contain multiple lines, you can do something like the following:

(defun my-with-c-sytle-comments (orig-fun beg end &optional arg)
  (if (and (member major-mode '(c-mode c++-mode java-mode))
           (save-excursion
             (goto-char end)
             (not (looking-at-p "[ \t]*$"))))
      (let (;; Set up comment style to use "/* ... */" instead of "// ..."
            (comment-start "/* ")
            (comment-end " */")
            ;; Use a single pair of "/*" and "*/" for the entire region rather a
            ;; separate one for each line.
            (comment-style 'multi-line)
            ;; Don't insert a "*" at the beginning of each line.  This can't be
            ;; blank because then Emacs will just ignore it and revert to the
            ;; default behavior of inserting the "*"'s.
            (comment-continue "XXX")
            ;; Remember the first and last lines of the region commented.
            (first-line (line-number-at-pos beg))
            (last-line (line-number-at-pos end)))
        (funcall orig-fun beg end arg)
        (when (< first-line last-line)
          ;; Delete the comment-continue string at the start of each line after
          ;; the first one.
          (save-excursion
            (goto-char (point-min))
            (forward-line (1- first-line))
            (while (< (line-number-at-pos) last-line)
              (forward-line)
              (beginning-of-line)
              ;; Make sure that the line starts with comment-continue plus a
              ;; space before we delete the prefix.
              (when (looking-at-p (regexp-quote (concat comment-continue " ")))
                (delete-region (point)
                               (+ (point)
                                  (1+ (length comment-continue)))))))))
    (funcall orig-fun beg end arg)))

(advice-add 'comment-region :around 'my-with-c-sytle-comments)
Qudit
  • 828
  • 8
  • 16
  • That's a good explanation. The proposed solution is still not quite what I'm looking for. But maybe I did not explain it good enough. For example if I select one ore many lines *entirely* I would prefer the // comments per line. Basically every time // would require a newline to be added I would rather take /**/. QtCreator behaves in exactly this way. I will add this to the original question. – B_old Mar 15 '18 at 21:10
  • 1
    @B_old Try the updated version – Qudit Mar 15 '18 at 21:36
  • I think I better understand the problem now. I would need a query that tells me whether there is text (before beg and on the same line as beg) or (after end and on the same line as end). Single- or multi-line is not important, but rather whether a comment would chop up any of the affected lines. – B_old Mar 16 '18 at 12:14
  • 1
    @B_old Updated again! – Qudit Mar 17 '18 at 03:04
  • I think it fails if the marked region spans multiple lines and ends in the middle of a line. This last line will then be split to accommodate the // comments, instead of employing /**/. – B_old Mar 18 '18 at 17:32
  • 1
    @B_old In that case, you can just remove the code that checks if the region spans only one line (as in the latest update). Note that I have not tested it as I do not have access to my computer atm but I think it should work. – Qudit Mar 18 '18 at 22:33
  • Thank you! This is really useful to me now. I wonder whether it is possible to make it so, that only a single /**/ pair is ever used, even when spanning multiple lines? (That's how qtcreator behaves. On the other hand it would not really further improve usability of your solution.) – B_old Mar 20 '18 at 13:49
  • @B_old Glad it's useful. Binding `comment-style` to 'multi-line (as in the latest update) sort of does it, but it inserts extra "*" on lines that follow (try it). As far as I can tell, there no default variable that controls that behavior, so it would be necessary to basically write a different variant of `comment-region`, which would be more involved. – Qudit Mar 20 '18 at 19:52
  • 1
    @B_old I Happened to encounter the variable that controls adding the "*'"'s while working on some other stuff. It's comment-continue and the latest update shouldn't add the extra "*"'s. – Qudit Mar 22 '18 at 06:29
  • That's quite cool, but there is a problem with this. It will insert those whitespaces, and later when you go back and uncomment the region again, it will not remove the whitespaces! I tried leaving comment-continue blank, but that will just fall back to single-line comments again. – B_old Mar 22 '18 at 14:05
  • @B_old Yes, apparently comments are not intended to start in the middle of a line. The docs say the comment-continue should be the same length as comment-start to preserve indentation. I think the only way of solving the spacing issue would be to add code that deletes the first three spaces on each line. – Qudit Mar 22 '18 at 17:09
  • Hm, I see the problem. I think I'm just going to live with your solution now. As I said, it solves the main issue I had with the default behavior. – B_old Mar 23 '18 at 07:58
  • @B_old I've started using this myself, so I made new version that removes the prefix from the start of each line. – Qudit Mar 23 '18 at 18:38
  • The new version confuses me. It inserts "XXX" at the start of each line and if you later come back and uncomment the same region the "XXX" will remain. Or did I mess up when adding it to my init? – B_old Mar 23 '18 at 22:35
  • @B_old The "XXX"'s should be automatically deleted and never seen by the user. It looks like I gave the function the wrong name when copying it from my config. Try it again. – Qudit Mar 23 '18 at 22:46
  • I think picked the right name the first time already. Anyway, I just tried the updated version and the "XXX" issue remains the same. Strange that it behaves differently for us. – B_old Mar 24 '18 at 10:07
  • @B_old That is odd. It's work for me both with my configuration and with vanilla Emacs 25.3.1. – Qudit Mar 24 '18 at 19:19
0

Try binding the my/comment-line-or-region function to M-; and see if works as you expect. It checks if a region is active, and, if so, runs the comment-region function; and if there's no region, it runs my/comment-slash, which inserts // in the beginning of the line.

(defun my/comment-slash()
  (interactive)
  (back-to-indentation)
  (insert "// "))

(defun my/comment-line-or-region()
  (interactive)
  (if (region-active-p)
      (comment-region (region-beginning) (region-end))
    (my/comment-slash)))
Jesse
  • 1,984
  • 11
  • 19
  • `comment-dwim` and `comment-region` do the same thing when there is an active region that is not commented out. – Qudit Mar 08 '18 at 21:32
  • @Qudit I might be missing something because I use evil and I'm not familiar with many emacs core commands, but for me `comment-dwim` puts a commentary at the end of the line, while `comment-region` says there's nothing to comment if no region is active. I think my answer covers what he's asked. – Jesse Mar 09 '18 at 00:32
  • There may be a setting you've changed somewhere. My comment refers to the behavior in vanilla Emacs (version 25.3). – Qudit Mar 09 '18 at 00:35
  • Did you try with `c-mode`? Emacs handles comments differently depending on current major mode. – Jesse Mar 09 '18 at 00:40
  • I checked for C++ mode since that is what the OP asked about. – Qudit Mar 09 '18 at 00:44
  • 1
    @Jesse, I tried the function, but it will still insert // everywhere. I think that is just how comment-region behaves in C++ mode. – B_old Mar 09 '18 at 09:17