7

In Verilog/C/C++, comments can begin with //.

Here's an example comment, //This is a comment

I like to use the find-file-at-point feature. If my cursor is on the file name in `include "some_file.v".

But if my cursor is on the above example comment and if I hit C-x C-f, emacs tries to open a tentative path //This!

How do I selectively prevent find-file-at-point from activating? In this case, when the major mode is verilog-mode, how do I NOT do find-file-at-point when my cursor is on a line where the first 2 non-space characters are //?

Realraptor
  • 1,253
  • 6
  • 17
Kaushal Modi
  • 25,203
  • 3
  • 74
  • 179
  • I don't quite understand your usecase... Did you remap `C-x C-f` to `ffap` or to a wrapper around `ffap`? – T. Verron Sep 24 '14 at 05:18
  • Check the function mapped for your `C-x C-f` (by `C-h k` RET `C-x C-f`). It should say it "runs the command" `find-file`. – caisah Sep 24 '14 at 06:01
  • I can't reproduce this on my 24.3.1 GNU Emacs. Maybe this bug (as @Sigma notes) has already been solved? – remvee Sep 24 '14 at 07:44
  • 2
    @remvee The `find-file-at-point` feature is disabled by default. I have it enabled via `ido`. I have `(setq ido-use-filename-at-point 'guess)` in my config. – Kaushal Modi Sep 24 '14 at 16:12

4 Answers4

9

This is a bit disppointing, because ffap.el has some code that should do just that:

 ;; Immediate rejects (/ and // and /* are too common in C/C++):
     ((member name '("" "/" "//" "/*" ".")) nil)

But unfortunately, it relies on there being a space after the comment separator.

It's also very disappointing, because comment markers should never be part of any string-at-point. So here's a patched version of ffap-string-at-point that tries to systematically ignore those markers

(require 'ffap)
(defun ffap-string-at-point (&optional mode)
  (let* ((args
      (cdr
       (or (assq (or mode major-mode) ffap-string-at-point-mode-alist)
           (assq 'file ffap-string-at-point-mode-alist))))
         next-comment
     (pt (point))
     (beg (if (use-region-p)
          (region-beginning)
        (save-excursion
          (skip-chars-backward (car args))
          (skip-chars-forward (nth 1 args) pt)
                  (save-excursion
                    (setq next-comment
                          (progn (comment-search-forward (line-end-position) t)
                                 (point))))
          (point))))
     (end (if (use-region-p)
          (region-end)
        (save-excursion
          (skip-chars-forward (car args))
          (skip-chars-backward (nth 2 args) pt)
          (point)))))
  (when (> end next-comment)
    (setq beg next-comment))
  (setq ffap-string-at-point
      (buffer-substring-no-properties
       (setcar ffap-string-at-point-region beg)
       (setcar (cdr ffap-string-at-point-region) end)))))

By side effect it fixes your problem, but it's much more general. I'm wondering if such a fix should be integrated upstream.

Kaushal Modi
  • 25,203
  • 3
  • 74
  • 179
Sigma
  • 4,510
  • 21
  • 27
4

Thanks to the solution posted by @Sigma. I had that solution in my config for more than 2 years, and finally sent that as a patch to emacs upstream.

Commit in emacs master: e472cfe8


Here is what the patch effectively does:

(defun modi/ffap-string-at-point (&optional mode)
  "Return a string of characters from around point.

MODE (defaults to value of `major-mode') is a symbol used to look up
string syntax parameters in `ffap-string-at-point-mode-alist'.

If MODE is not found, we use `file' instead of MODE.

If the region is active,return a string from the region.

If the point is in a comment, ensure that the returned string does not contain
the comment start characters (especially for major modes that have '//' as
comment start characters). https://debbugs.gnu.org/cgi/bugreport.cgi?bug=24057

|-----------------------------------+---------------------------------|
| Example string in `c-mode' buffer | Returned `ffap-string-at-point' |
|-----------------------------------+---------------------------------|
| ▮//tmp                            | tmp                             |
| //▮tmp                            | tmp                             |
| ▮///tmp                           | /tmp                            |
| //▮/tmp                           | /tmp                            |
| ▮////tmp                          | //tmp                           |
| ////▮tmp                          | //tmp                           |
| ▮// //tmp                         | (empty string) \"\"             |
| // ▮/tmp                          | /tmp                            |
| // ▮//tmp                         | //tmp                           |
|-----------------------------------+---------------------------------|

Set the variables `ffap-string-at-point' and `ffap-string-at-point-region'.

When the region is active and larger than `ffap-max-region-length',
return an empty string, and set `ffap-string-at-point-region' to '(1 1)."
  (let* ((args
          (cdr
           (or (assq (or mode major-mode) ffap-string-at-point-mode-alist)
               (assq 'file ffap-string-at-point-mode-alist))))
         (region-selected (use-region-p))
         (pt (point))
         (beg (if region-selected
                  (region-beginning)
                (save-excursion
                  (skip-chars-backward (car args))
                  (skip-chars-forward (nth 1 args) pt)
                  (point))))
         (end (if region-selected
                  (region-end)
                (save-excursion
                  (skip-chars-forward (car args))
                  (skip-chars-backward (nth 2 args) pt)
                  (point))))
         (region-len (- (max beg end) (min beg end))))

    ;; If the initial characters of the to-be-returned string are the
    ;; current major mode's comment starter characters, *and* are
    ;; not part of a comment, remove those from the returned string
    ;; (Bug#24057).
    ;; Example comments in `c-mode' (which considers lines beginning
    ;; with "//" as comments):
    ;;  //tmp - This is a comment. It does not contain any path reference.
    ;;  ///tmp - This is a comment. The "/tmp" portion in that is a path.
    ;;  ////tmp - This is a comment. The "//tmp" portion in that is a path.
    (when (and
           ;; Proceed if no region is selected by the user.
           (null region-selected)
           ;; Check if END character is part of a comment.
           (save-excursion
             (nth 4 (syntax-ppss end))))
      ;; Move BEG to beginning of comment (after the comment start
      ;; characters), or END, whichever comes first.
      (save-excursion
        (let ((state (syntax-ppss beg)))
          ;; (nth 4 (syntax-ppss)) will be nil for comment start chars
          (unless (nth 4 state)
            (parse-partial-sexp beg end nil nil state :commentstop)
            (setq beg (point))))))

    (if (and (natnump ffap-max-region-length)
             (< region-len ffap-max-region-length)) ; Bug#25243.
        (setf ffap-string-at-point-region (list beg end)
              ffap-string-at-point
              (buffer-substring-no-properties beg end))
      (setf ffap-string-at-point-region (list 1 1)
            ffap-string-at-point ""))))
(advice-add 'ffap-string-at-point :override #'modi/ffap-string-at-point)
Kaushal Modi
  • 25,203
  • 3
  • 74
  • 179
2

I think hacking find-file-at-point is easy, you can use defadvice on find-file-at-point.

The key point is detecting whether the cursor is in a comment. I had a similar issue when developing evil-nerd-commenter. Here is the function you can re-use. The trick is to detect the current font face.

(defun evilnc--in-comment-p (pos)
  (interactive)
  (let ((fontfaces (get-text-property pos 'face)))
    (when (not (listp fontfaces))
      (setf fontfaces (list fontfaces)))
    (delq nil
      (mapcar #'(lambda (f)
              ;; learn this trick from flyspell
              (or (eq f 'font-lock-comment-face)
              (eq f 'font-lock-comment-delimiter-face)))
          fontfaces))))
chen bin
  • 4,781
  • 18
  • 36
0

I know that this is not exactly addressing what the OP asked, but a simple way to get ffap to do what you want is to give it just a little advice.

(defun delp--ffap-string-at-point-filter (s)
  "Remove long stretches of /////'s from `ffap-string-at-point' return value."
  (interactive "sTest string: ")
  (if (string-match-p "^//" s)
      ""
    s))

(advice-add 'ffap-string-at-point :filter-return 'delp--ffap-string-at-point-filter)

Edit: fixed incorrect lambda quote (#' => just ') I understand that modern emacsen prefer '# but the ones that do not prefer it, don't understand it.

For me, this worked. I really appreciated the insights of Kaushal Modi, Sigma, Chen bin, and Giles.

I use extended strings of ////'s to break up the page, and I am often in the header when trying to find either the current directory or a file therein. I know that this advice will not serve for all; I put it here because a search on ffap brought me here. Others might have different personal advice to provide the function. Based on what I read here, I wrote the code above.

I have been using Emacs since 1984, and some of the new features do not get on my radar until I see some code. I recommend the Info section on advice. Or in emacs (Info-goto-node "(elisp)Advising Functions").

ElderDelp
  • 101
  • 4