2

I have the following problem:

I am in a comment or the empty lines after or within the comment. I want to get to the first line of the comment.

So no matter where I am in this commented block, I want to get to the beginning of line ;; blah blah.

M-x butterfly

;; blah blah 

;; spoon

;; blah blah blah


;; swordfish

I think I have solved it, albeit clumsily:

(defun beginning-of-comment ()
      (interactive)
      (while
          (progn
            (beginning-of-line)
            (looking-at (rx-to-string (or
              `(: line-start (zero-or-more whitespace) (one-or-more ,comment-start))
              `(: line-start (zero-or-more whitespace) line-end))))))
      (previous-line))

Problem is, I cannot use the solution above, because it makes my emacs crash (and would presumably make other's emacs' with the same OS/emacs version crash too if I were to PR it).

There must be a more idiomatic, less verbose way to get to the beginning of a comment from within it.

Ninja edit: I thought of one more, but this also crashes emacs (becomes totally unresponsive):

(defun testing ()
  (interactive)
  (while (comment-forward) (progn (previous-line) (previous-line)))

Ps. I want this to work in as many modes as possible, so please use comment-start, do not hardcode the comment variable.

3
  • Comments typically have face associated with them. It would be easier to use text face to identify the beginning of comment (it will be also faster, since looking-at can be very slow, especially if there are lookaheads.
    – wvxvw
    Commented Apr 24, 2016 at 12:17
  • In your first version, the point is not moved inside the loop.
    – JeanPierre
    Commented Apr 24, 2016 at 12:18
  • @wvxvw Thanks for the info, bit it does not sound like the most robust way. If you could an answer I'll UV it though. Thanks to you too JeanPierre. Commented Apr 24, 2016 at 12:40

3 Answers 3

2

You can use syntax-ppss to find out if the point is in a comment or not. When it is, it also provides information about where the comment started. The following code wraps this into a loop, skipping empty lines etc.

(defun my-comment-start-position ()
  "The position of the start of the current comment, or nil."
  (save-excursion
    (let ((res nil))
      (while (progn
               (skip-chars-backward " \t\n")
               (let ((state (syntax-ppss)))
                 (if (nth 4 state)
                     (let ((start (nth 8 state)))
                       (setq res start)
                       (goto-char start)
                       t)
                   nil))))
      res)))

The code is more robust than looking at regexp:s, since it handles difficult languages where the comment syntax must be defined programatically using a syntax-propertize-function.

Also, it is more robust than checking the face property:

  • Some users don't like syntax highlighting and disable font-lock
  • font-lock isn't active in batch mode
  • font-lock only highlights visible parts of the buffer
  • The face property doesn't have to contain a single symbol (see below), code reading it must handle all cases. (This happens, for example, when highlighing additional things inside comments.)
  • Badly written font-lock keywords sometimes replace the existing face rather than prepending or appending
  • Some comments may be highlighted using another face, e.g. if you have javadoc-style comments font-lock-doc-face is used.

From the elisp manual:

‘face’

The ‘face’ property controls the appearance of the character (*note
Faces::).  The value of the property can be the following:

   • A face name (a symbol or string).

   • An anonymous face: a property list of the form ‘(KEYWORD VALUE
     …)’, where each KEYWORD is a face attribute name and VALUE is
     a value for that attribute.

   • A list of faces.  Each list element should be either a face
     name or an anonymous face.  This specifies a face which is an
     aggregate of the attributes of each of the listed faces.
     Faces occurring earlier in the list have higher priority.

   • A cons cell of the form ‘(foreground-color . COLOR-NAME)’ or
     ‘(background-color . COLOR-NAME)’.  This specifies the
     foreground or background color, similar to ‘(:foreground
     COLOR-NAME)’ or ‘(:background COLOR-NAME)’.  This form is
     supported for backward compatibility only, and should be
     avoided.
12
  • This might just be me, but I think the part about how using face/font-locking is bad doesn't belong here but should be a comment to the "Use the face" answer (but you did that already) or even an answer to a new question on the site (so that you can give details, like you did here).
    – YoungFrog
    Commented Apr 25, 2016 at 9:33
  • @YoungFrog, yes, I see your point. However, in this case, the use of checking the face property in cases like this is widespread (even I used to it until quite recently) so I decided to take the opportunity to explain why it's not a good idea. Commented Apr 25, 2016 at 11:00
  • If you are interested in a slightly shorter version of your function, I've added one to my post. Feel free to copy / edit my post. PS. There are also advantages of relying on face's value. The mode can use the comment face for whatever reason it likes, not necessary related to the parsing. From the user's standpoint, whenever something looks like a comment it is a comment, so it makes sense to take that into account. In any case, in practice, I didn't encounter the corner cases you mention (though they are valid).
    – wvxvw
    Commented Apr 25, 2016 at 11:22
  • Just to give you an example when (syntax-ppss) will do the wrong thing: org-mode comments aren't comments from its perspective, but you would still want to navigate them presumably.
    – wvxvw
    Commented Apr 25, 2016 at 11:24
  • @wvxvw, unfortunately, it overshoots the goal. When the first comment line is preceded with empty lines, your function returns the position of the first empty line rather than the first comment (which is the reason behind my res variable). Also, your code will not work when the point is in a string, as element 8 if the state is also used to point to the beginning of a string -- hence, you need to check element 4. Commented Apr 25, 2016 at 11:27
0

Here's an example I could think of, not sure it will handle all corner cases (maybe some modes use different faces for eg.)

(defun my/move-beginning-of-comment ()
  (interactive)
  (while (member (get-char-property (point) 'face)
                 (list font-lock-comment-face
                       font-lock-comment-delimiter-face))
    (backward-char)
    (skip-chars-backward " \t\n")))

Below is a slightly shorter way to write the same function Lindydancer posted:

(defun my-comment-start-position ()
  "The position of the start of the current comment, or nil."
  (save-excursion
    (let (pos)
      (while (let* ((parse (syntax-ppss))
                    (move (and (nth 4 parse) (nth 8 parse))))
               (when move (goto-char (setq pos move))))
        (skip-chars-backward " \t\n"))
    (or pos (point)))))
3
  • Thanks. If others say this is a good way to do it, I might accept. Commented Apr 24, 2016 at 13:17
  • Take a look at syntax-ppss instead, it can tell you if you're in a comment or not, and where the comment started. Looking at the font-lock face is too fragile. To start, the face property can either be a symbol or a list of symbols, or even include a face plist. Another problem is that some font-lock rules replace the existing face, when that happens your code will not work. Commented Apr 24, 2016 at 18:04
  • Indeed too fragile. And some (parts of) the buffer could lack font-locking.
    – YoungFrog
    Commented Apr 25, 2016 at 9:36
-1

More robust, but also more verbose:

(defun beginning-of-comment ()
  "When within a comment block - including empty lines - iterates backwards until the beginning."
  (interactive)
  (progn
    (while
        (progn
          (beginning-of-line)
          (or
           ;; Not working to use ?\n in rx-to-string for some reason
           (looking-at (rx (zero-or-more whitespace) ?\n))
           (looking-at (rx-to-string
                        `(: line-start (zero-or-more whitespace) (one-or-more ,comment-start))))))
      (previous-line))
    ;; If we end up in a code line, move one line forward.
    (unless
        (looking-at (rx (zero-or-more whitespace) line-end))
      (next-line)
      (beginning-of-line))))
4
  • Some modes have complicated commenting rules that comment-start does not cover (they use syntax properties), so this is not generic enough.
    – YoungFrog
    Commented Apr 25, 2016 at 9:38
  • next-line is for interactive use. You can use just forward-line to replace both next-line and beginning-of-line.
    – YoungFrog
    Commented Apr 25, 2016 at 9:40
  • Thanks. Should I delete this since the answer is suboptimal? Commented Apr 25, 2016 at 10:56
  • 1
    I don't think so. The voting system makes it easy enough to not even notice this answer for everyone not interested, and still possible to read it for the few interested people.
    – YoungFrog
    Commented Apr 25, 2016 at 11:09

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.