(rmail-get-new-mail): Remove extra format string
[bpt/emacs.git] / lisp / ffap.el
CommitLineData
3fa86f26 1;; ffap.el --- find file (or url) at point
0948761d 2;;
e5eee690 3;; Copyright (C) 1995, 96, 97, 2000 Free Software Foundation, Inc.
0948761d 4;;
b578f267 5;; Author: Michelangelo Grigni <mic@mathcs.emory.edu>
bea0003a 6;; Maintainer: Rajesh Vaidheeswarran <rv@gnu.org>
87e2d039 7;; Created: 29 Mar 1993
f5f727f8 8;; Keywords: files, hypermedia, matching, mouse, convenience
3788c735 9;; X-URL: ftp://ftp.mathcs.emory.edu/pub/mic/emacs/
213d9a4f
RS
10
11;; This file is part of GNU Emacs.
12
13;; GNU Emacs is free software; you can redistribute it and/or modify
14;; it under the terms of the GNU General Public License as published by
15;; the Free Software Foundation; either version 2, or (at your option)
16;; any later version.
17
18;; GNU Emacs is distributed in the hope that it will be useful,
19;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21;; GNU General Public License for more details.
22
23;; You should have received a copy of the GNU General Public License
b578f267
EN
24;; along with GNU Emacs; see the file COPYING. If not, write to the
25;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
26;; Boston, MA 02111-1307, USA.
213d9a4f 27
213d9a4f 28\f
87e2d039 29;;; Commentary:
213d9a4f 30;;
87e2d039
RS
31;; Command find-file-at-point replaces find-file. With a prefix, it
32;; behaves exactly like find-file. Without a prefix, it first tries
41d34bed 33;; to guess a default file or URL from the text around the point
87e2d039
RS
34;; (`ffap-require-prefix' swaps these behaviors). This is useful for
35;; following references in situations such as mail or news buffers,
36;; README's, MANIFEST's, and so on. Submit bugs or suggestions with
37;; M-x ffap-bug.
213d9a4f 38;;
25050dab 39;; For the default installation, add this line to your .emacs file:
213d9a4f 40;;
87e2d039 41;; (ffap-bindings) ; do default key bindings
213d9a4f 42;;
87e2d039 43;; ffap-bindings makes the following global key bindings:
213d9a4f 44;;
87e2d039 45;; C-x C-f find-file-at-point (abbreviated as ffap)
28ccdfff 46;; C-x d dired-at-point
87e2d039
RS
47;; C-x 4 f ffap-other-window
48;; C-x 5 f ffap-other-frame
49;; S-mouse-3 ffap-at-mouse
0948761d 50;; C-S-mouse-3 ffap-menu
213d9a4f 51;;
87e2d039
RS
52;; ffap-bindings also adds hooks to make the following local bindings
53;; in vm, gnus, and rmail:
213d9a4f 54;;
0948761d
KH
55;; M-l ffap-next, or ffap-gnus-next in gnus (l == "link")
56;; M-m ffap-menu, or ffap-gnus-menu in gnus (m == "menu")
213d9a4f 57;;
87e2d039
RS
58;; If you do not like these bindings, modify the variable
59;; `ffap-bindings', or write your own.
213d9a4f 60;;
87e2d039
RS
61;; If you use ange-ftp, browse-url, complete, efs, or w3, it is best
62;; to load or autoload them before ffap. If you use ff-paths, load it
63;; afterwards. Try apropos {C-h a ffap RET} to get a list of the many
64;; option variables. In particular, if ffap is slow, try these:
213d9a4f 65;;
87e2d039
RS
66;; (setq ffap-alist nil) ; faster, dumber prompting
67;; (setq ffap-machine-p-known 'accept) ; no pinging
41d34bed 68;; (setq ffap-url-regexp nil) ; disable URL features in ffap
c98ddbe5 69;; (setq ffap-shell-prompt-regexp nil) ; disable shell prompt stripping
213d9a4f 70;;
3788c735
KH
71;; ffap uses `browse-url' (if found, else `w3-fetch') to fetch URL's.
72;; For a hairier `ffap-url-fetcher', try ffap-url.el (same ftp site).
87e2d039 73;; Also, you can add `ffap-menu-rescan' to various hooks to fontify
71296446 74;; the file and URL references within a buffer.
87e2d039 75
0948761d
KH
76\f
77;;; Change Log:
78;;
79;; The History and Contributors moved to ffap.LOG (same ftp site),
80;; which also has some old examples and commentary from ffap 1.5.
81
82\f
87e2d039 83;;; Todo list:
0948761d 84;; * use kpsewhich
95a85681 85;; * let "/dir/file#key" jump to key (tag or regexp) in /dir/file
87e2d039 86;; * find file of symbol if TAGS is loaded (like above)
0948761d
KH
87;; * break long menus into multiple panes (like imenu?)
88;; * notice node in "(dired)Virtual Dired" (quotes, parentheses, whitespace)
95a85681 89;; * notice "machine.dom blah blah blah dir/file" (how?)
0948761d 90;; * as w3 becomes standard, rewrite to rely more on its functions
87e2d039
RS
91;; * regexp options for ffap-string-at-point, like font-lock (MCOOK)
92;; * v19: could replace `ffap-locate-file' with a quieter `locate-library'
0948761d
KH
93;; * handle "$(VAR)" in Makefiles
94;; * use the font-lock machinery
213d9a4f
RS
95
96\f
97;;; Code:
98
99(provide 'ffap)
100
3788c735
KH
101;; Please do not delete this variable, it is checked in bug reports.
102(defconst ffap-version "1.9-fsf <97/06/25 13:21:41 mic>"
103 "The version of ffap: \"Major.Minor-Build <Timestamp>\"")
87e2d039 104
213d9a4f 105
41d34bed
RS
106(defgroup ffap nil
107 "Find file or URL at point."
3788c735 108 :link '(url-link :tag "URL" "ftp://ftp.mathcs.emory.edu/pub/mic/emacs/")
f5f727f8
DN
109 :group 'matching
110 :group 'convenience)
41d34bed 111
3788c735
KH
112;; The code is organized in pages, separated by formfeed characters.
113;; See the next two pages for standard customization ideas.
114
115\f
116;;; User Variables:
41d34bed 117
0948761d 118(defun ffap-soft-value (name &optional default)
87e2d039
RS
119 "Return value of symbol with NAME, if it is interned.
120Otherwise return nil (or the optional DEFAULT value)."
121 ;; Bug: (ffap-soft-value "nil" 5) --> 5
213d9a4f 122 (let ((sym (intern-soft name)))
0948761d 123 (if (and sym (boundp sym)) (symbol-value sym) default)))
213d9a4f 124
c98ddbe5
RV
125(defcustom ffap-shell-prompt-regexp
126 ;; This used to test for some shell prompts that don't have a space
127 ;; after them. The common root shell prompt (#) is not listed since it
128 ;; also doubles up as a valid URL character.
129 "[$%><]*"
130 "Paths matching this regexp are stripped off the shell prompt
131If nil, ffap doesn't do shell prompt stripping."
132 :type '(choice (const :tag "Disable" nil)
133 (const :tag "Standard" "[$%><]*")
134 regexp)
135 :group 'ffap)
136
41d34bed 137(defcustom ffap-ftp-regexp
0948761d
KH
138 ;; This used to test for ange-ftp or efs being present, but it should be
139 ;; harmless (and simpler) to give it this value unconditionally.
140 "\\`/[^/:]+:"
95a85681
RS
141 "*File names matching this regexp are treated as remote ffap.
142If nil, ffap neither recognizes nor generates such names."
41d34bed 143 :type '(choice (const :tag "Disable" nil)
0948761d 144 (const :tag "Standard" "\\`/[^/:]+:")
41d34bed
RS
145 regexp)
146 :group 'ffap)
147
148(defcustom ffap-url-unwrap-local t
95a85681 149 "*If non-nil, convert `file:' URL to local file name before prompting."
41d34bed
RS
150 :type 'boolean
151 :group 'ffap)
152
153(defcustom ffap-url-unwrap-remote t
95a85681 154 "*If non-nil, convert `ftp:' URL to remote file name before prompting.
41d34bed
RS
155This is ignored if `ffap-ftp-regexp' is nil."
156 :type 'boolean
157 :group 'ffap)
158
0948761d 159(defcustom ffap-ftp-default-user "anonymous"
95a85681 160 "*User name in ftp file names generated by `ffap-host-to-path'.
0948761d
KH
161Note this name may be omitted if it equals the default
162\(either `efs-default-user' or `ange-ftp-default-user'\)."
163 :type 'string
41d34bed 164 :group 'ffap)
213d9a4f 165
41d34bed 166(defcustom ffap-rfs-regexp
213d9a4f
RS
167 ;; Remote file access built into file system? HP rfa or Andrew afs:
168 "\\`/\\(afs\\|net\\)/."
169 ;; afs only: (and (file-exists-p "/afs") "\\`/afs/.")
95a85681 170 "*Matching file names are treated as remote. Use nil to disable."
41d34bed
RS
171 :type 'regexp
172 :group 'ffap)
213d9a4f
RS
173
174(defvar ffap-url-regexp
175 ;; Could just use `url-nonrelative-link' of w3, if loaded.
176 ;; This regexp is not exhaustive, it just matches common cases.
177 (concat
178 "\\`\\("
179 "news\\(post\\)?:\\|mailto:\\|file:" ; no host ok
180 "\\|"
1d34daae 181 "\\(ftp\\|https?\\|telnet\\|gopher\\|www\\|wais\\)://" ; needs host
213d9a4f
RS
182 "\\)." ; require one more character
183 )
41d34bed 184 "Regexp matching URL's. nil to disable URL features in ffap.")
213d9a4f 185
41d34bed
RS
186(defcustom ffap-foo-at-bar-prefix "mailto"
187 "*Presumed URL prefix type of strings like \"<foo.9z@bar>\".
188Sensible values are nil, \"news\", or \"mailto\"."
0948761d
KH
189 :type '(choice (const "mailto")
190 (const "news")
191 (const :tag "Disable" nil)
192 ;; string -- possible, but not really useful
193 )
41d34bed 194 :group 'ffap)
213d9a4f
RS
195
196\f
0948761d 197;;; Peanut Gallery (More User Variables):
87e2d039 198;;
213d9a4f
RS
199;; Users of ffap occasionally suggest new features. If I consider
200;; those features interesting but not clear winners (a matter of
201;; personal taste) I try to leave options to enable them. Read
87e2d039
RS
202;; through this section for features that you like, put an appropriate
203;; enabler in your .emacs file.
213d9a4f 204
0948761d 205(defcustom ffap-dired-wildcards nil
87e2d039
RS
206 ;; Suggestion from RHOGEE, 07 Jul 1994. Disabled, dired is still
207 ;; available by "C-x C-d <pattern>", and valid filenames may
208 ;; sometimes contain wildcard characters.
213d9a4f 209 "*A regexp matching filename wildcard characters, or nil.
87e2d039 210If `find-file-at-point' gets a filename matching this pattern,
41d34bed 211it passes it on to `dired' instead of `find-file'."
0948761d
KH
212 :type '(choice (const :tag "Disable" nil)
213 (const :tag "Enable" "[*?][^/]*\\'")
214 ;; regexp -- probably not useful
215 )
41d34bed 216 :group 'ffap)
213d9a4f 217
0948761d 218(defcustom ffap-newfile-prompt nil
87e2d039
RS
219 ;; Suggestion from RHOGEE, 11 Jul 1994. Disabled, I think this is
220 ;; better handled by `find-file-not-found-hooks'.
41d34bed
RS
221 "*Whether `find-file-at-point' prompts about a nonexistent file."
222 :type 'boolean
223 :group 'ffap)
213d9a4f 224
41d34bed 225(defcustom ffap-require-prefix nil
87e2d039
RS
226 ;; Suggestion from RHOGEE, 20 Oct 1994.
227 "*If set, reverses the prefix argument to `find-file-at-point'.
228This is nil so neophytes notice ffap. Experts may prefer to disable
41d34bed
RS
229ffap most of the time."
230 :type 'boolean
231 :group 'ffap)
232
233(defcustom ffap-file-finder 'find-file
234 "*The command called by `find-file-at-point' to find a file."
235 :type 'function
236 :group 'ffap)
87e2d039 237(put 'ffap-file-finder 'risky-local-variable t)
213d9a4f 238
41d34bed 239(defcustom ffap-url-fetcher
3788c735
KH
240 (if (fboundp 'browse-url)
241 'browse-url ; rely on browse-url-browser-function
242 'w3-fetch)
87e2d039 243 ;; Remote control references:
213d9a4f
RS
244 ;; http://www.ncsa.uiuc.edu/SDG/Software/XMosaic/remote-control.html
245 ;; http://home.netscape.com/newsref/std/x-remote.html
87e2d039 246 "*A function of one argument, called by ffap to fetch an URL.
3788c735 247Reasonable choices are `w3-fetch' or a `browse-url-*' function.
ee79ced8 248For a fancy alternative, get `ffap-url.el'."
0948761d 249 :type '(choice (const w3-fetch)
3788c735 250 (const browse-url) ; in recent versions of browse-url
0948761d
KH
251 (const browse-url-netscape)
252 (const browse-url-mosaic)
253 function)
41d34bed 254 :group 'ffap)
213d9a4f
RS
255(put 'ffap-url-fetcher 'risky-local-variable t)
256
257\f
3788c735
KH
258;;; Compatibility:
259;;
260;; This version of ffap supports Emacs 20 only, see the ftp site
261;; for a more general version. The following functions are necessary
262;; "leftovers" from the more general version.
263
264(defun ffap-mouse-event nil ; current mouse event, or nil
265 (and (listp last-nonmenu-event) last-nonmenu-event))
266(defun ffap-event-buffer (event)
267 (window-buffer (car (event-start event))))
0948761d
KH
268
269\f
270;;; Find Next Thing in buffer (`ffap-next'):
213d9a4f 271;;
87e2d039
RS
272;; Original ffap-next-url (URL's only) from RPECK 30 Mar 1995. Since
273;; then, broke it up into ffap-next-guess (noninteractive) and
274;; ffap-next (a command). It now work on files as well as url's.
213d9a4f 275
41d34bed 276(defcustom ffap-next-regexp
213d9a4f
RS
277 ;; If you want ffap-next to find URL's only, try this:
278 ;; (and ffap-url-regexp (string-match "\\\\`" ffap-url-regexp)
279 ;; (concat "\\<" (substring ffap-url-regexp 2))))
280 ;;
281 ;; It pays to put a big fancy regexp here, since ffap-guesser is
282 ;; much more time-consuming than regexp searching:
283 "[/:.~a-zA-Z]/\\|@[a-zA-Z][-a-zA-Z0-9]*\\."
41d34bed
RS
284 "*Regular expression governing movements of `ffap-next'."
285 :type 'regexp
286 :group 'ffap)
213d9a4f 287
c9ae9869
RS
288(defvar ffap-next-guess nil
289 "Last value returned by `ffap-next-guess'.")
290
291(defvar ffap-string-at-point-region '(1 1)
292 "List (BEG END), last region returned by `ffap-string-at-point'.")
293
213d9a4f 294(defun ffap-next-guess (&optional back lim)
41d34bed 295 "Move point to next file or URL, and return it as a string.
87e2d039 296If nothing is found, leave point at limit and return nil.
213d9a4f
RS
297Optional BACK argument makes search backwards.
298Optional LIM argument limits the search.
299Only considers strings that match `ffap-next-regexp'."
300 (or lim (setq lim (if back (point-min) (point-max))))
301 (let (guess)
302 (while (not (or guess (eq (point) lim)))
303 (funcall (if back 're-search-backward 're-search-forward)
304 ffap-next-regexp lim 'move)
305 (setq guess (ffap-guesser)))
306 ;; Go to end, so we do not get same guess twice:
307 (goto-char (nth (if back 0 1) ffap-string-at-point-region))
308 (setq ffap-next-guess guess)))
309
310;;;###autoload
311(defun ffap-next (&optional back wrap)
41d34bed 312 "Search buffer for next file or URL, and run ffap.
213d9a4f
RS
313Optional argument BACK says to search backwards.
314Optional argument WRAP says to try wrapping around if necessary.
315Interactively: use a single prefix to search backwards,
316double prefix to wrap forward, triple to wrap backwards.
87e2d039 317Actual search is done by `ffap-next-guess'."
213d9a4f
RS
318 (interactive
319 (cdr (assq (prefix-numeric-value current-prefix-arg)
320 '((1) (4 t) (16 nil t) (64 t t)))))
321 (let ((pt (point))
322 (guess (ffap-next-guess back)))
323 ;; Try wraparound if necessary:
324 (and (not guess) wrap
325 (goto-char (if back (point-max) (point-min)))
326 (setq guess (ffap-next-guess back pt)))
327 (if guess
328 (progn
329 (sit-for 0) ; display point movement
330 (find-file-at-point (ffap-prompter guess)))
331 (goto-char pt) ; restore point
41d34bed 332 (message "No %sfiles or URL's found"
213d9a4f
RS
333 (if wrap "" "more ")))))
334
335(defun ffap-next-url (&optional back wrap)
87e2d039 336 "Like `ffap-next', but search with `ffap-url-regexp'."
213d9a4f
RS
337 (interactive)
338 (let ((ffap-next-regexp ffap-url-regexp))
339 (if (interactive-p)
340 (call-interactively 'ffap-next)
341 (ffap-next back wrap))))
342
343\f
0948761d 344;;; Machines (`ffap-machine-p'):
213d9a4f
RS
345
346;; I cannot decide a "best" strategy here, so these are variables. In
347;; particular, if `Pinging...' is broken or takes too long on your
348;; machine, try setting these all to accept or reject.
41d34bed 349(defcustom ffap-machine-p-local 'reject ; this happens often
ee79ced8 350 "*What `ffap-machine-p' does with hostnames that have no domain.
88b5c6b3 351Value should be a symbol, one of `ping', `accept', and `reject'."
41d34bed
RS
352 :type '(choice (const ping)
353 (const accept)
354 (const reject))
355 :group 'ffap)
ee79ced8
KH
356(defcustom ffap-machine-p-known 'ping ; `accept' for higher speed
357 "*What `ffap-machine-p' does with hostnames that have a known domain.
358Value should be a symbol, one of `ping', `accept', and `reject'.
359See `mail-extr.el' for the known domains."
41d34bed
RS
360 :type '(choice (const ping)
361 (const accept)
362 (const reject))
363 :group 'ffap)
364(defcustom ffap-machine-p-unknown 'reject
ee79ced8
KH
365 "*What `ffap-machine-p' does with hostnames that have an unknown domain.
366Value should be a symbol, one of `ping', `accept', and `reject'.
367See `mail-extr.el' for the known domains."
41d34bed
RS
368 :type '(choice (const ping)
369 (const accept)
370 (const reject))
371 :group 'ffap)
87e2d039
RS
372
373(defun ffap-what-domain (domain)
374 ;; Like what-domain in mail-extr.el, returns string or nil.
375 (require 'mail-extr)
3788c735
KH
376 (let ((ob (or (ffap-soft-value "mail-extr-all-top-level-domains")
377 (ffap-soft-value "all-top-level-domains")))) ; XEmacs
378 (and ob (get (intern-soft (downcase domain) ob) 'domain-name))))
87e2d039
RS
379
380(defun ffap-machine-p (host &optional service quiet strategy)
381 "Decide whether HOST is the name of a real, reachable machine.
382Depending on the domain (none, known, or unknown), follow the strategy
383named by the variable `ffap-machine-p-local', `ffap-machine-p-known',
384or `ffap-machine-p-unknown'. Pinging uses `open-network-stream'.
385Optional SERVICE specifies the port used \(default \"discard\"\).
213d9a4f 386Optional QUIET flag suppresses the \"Pinging...\" message.
87e2d039 387Optional STRATEGY overrides the three variables above.
213d9a4f 388Returned values:
87e2d039
RS
389 t means that HOST answered.
390'accept means the relevant variable told us to accept.
391\"mesg\" means HOST exists, but does not respond for some reason."
392 ;; Try some (Emory local):
393 ;; (ffap-machine-p "ftp" nil nil 'ping)
394 ;; (ffap-machine-p "nonesuch" nil nil 'ping)
395 ;; (ffap-machine-p "ftp.mathcs.emory.edu" nil nil 'ping)
396 ;; (ffap-machine-p "mathcs" 5678 nil 'ping)
397 ;; (ffap-machine-p "foo.bonk" nil nil 'ping)
398 ;; (ffap-machine-p "foo.bonk.com" nil nil 'ping)
213d9a4f 399 (if (or (string-match "[^-a-zA-Z0-9.]" host) ; Illegal chars (?)
87e2d039 400 (not (string-match "[^0-9]" host))) ; 1: a number? 2: quick reject
213d9a4f
RS
401 nil
402 (let* ((domain
403 (and (string-match "\\.[^.]*$" host)
404 (downcase (substring host (1+ (match-beginning 0))))))
87e2d039
RS
405 (what-domain (if domain (ffap-what-domain domain) "Local")))
406 (or strategy
407 (setq strategy
408 (cond ((not domain) ffap-machine-p-local)
409 ((not what-domain) ffap-machine-p-unknown)
410 (t ffap-machine-p-known))))
213d9a4f
RS
411 (cond
412 ((eq strategy 'accept) 'accept)
413 ((eq strategy 'reject) nil)
e75e894b 414 ((not (fboundp 'open-network-stream)) nil)
213d9a4f
RS
415 ;; assume (eq strategy 'ping)
416 (t
417 (or quiet
87e2d039
RS
418 (if (stringp what-domain)
419 (message "Pinging %s (%s)..." host what-domain)
213d9a4f
RS
420 (message "Pinging %s ..." host)))
421 (condition-case error
422 (progn
423 (delete-process
424 (open-network-stream
425 "ffap-machine-p" nil host (or service "discard")))
426 t)
427 (error
428 (let ((mesg (car (cdr error))))
429 (cond
430 ;; v18:
431 ((string-match "^Unknown host" mesg) nil)
432 ((string-match "not responding$" mesg) mesg)
433 ;; v19:
434 ;; (file-error "connection failed" "permission denied"
435 ;; "nonesuch" "ffap-machine-p")
436 ;; (file-error "connection failed" "host is unreachable"
437 ;; "gopher.house.gov" "ffap-machine-p")
438 ;; (file-error "connection failed" "address already in use"
439 ;; "ftp.uu.net" "ffap-machine-p")
440 ((equal mesg "connection failed")
441 (if (equal (nth 2 error) "permission denied")
442 nil ; host does not exist
87e2d039 443 ;; Other errors mean the host exists:
213d9a4f
RS
444 (nth 2 error)))
445 ;; Could be "Unknown service":
446 (t (signal (car error) (cdr error))))))))))))
447
0948761d
KH
448\f
449;;; Possibly Remote Resources:
450
95a85681 451(defun ffap-replace-file-component (fullname name)
0948761d
KH
452 "In remote FULLNAME, replace path with NAME. May return nil."
453 ;; Use ange-ftp or efs if loaded, but do not load them otherwise.
454 (let (found)
455 (mapcar
456 (function (lambda (sym) (and (fboundp sym) (setq found sym))))
457 '(
458 efs-replace-path-component
459 ange-ftp-replace-path-component
460 ange-ftp-replace-name-component
461 ))
462 (and found
95a85681 463 (fset 'ffap-replace-file-component found)
0948761d 464 (funcall found fullname name))))
95a85681 465;; (ffap-replace-file-component "/who@foo.com:/whatever" "/new")
0948761d 466
3788c735 467(defun ffap-file-suffix (file)
ee79ced8 468 "Return trailing `.foo' suffix of FILE, or nil if none."
3788c735
KH
469 (let ((pos (string-match "\\.[^./]*\\'" file)))
470 (and pos (substring file pos nil))))
471
472(defvar ffap-compression-suffixes '(".gz" ".Z") ; .z is mostly dead
473 "List of suffixes tried by `ffap-file-exists-string'.")
474
475(defun ffap-file-exists-string (file &optional nomodify)
476 ;; Early jka-compr versions modified file-exists-p to return the
477 ;; filename, maybe modified by adding a suffix like ".gz". That
478 ;; broke the interface of file-exists-p, so it was later dropped.
479 ;; Here we document and simulate the old behavior.
ee79ced8 480 "Return FILE (maybe modified) if the file exists, else nil.
3788c735
KH
481When using jka-compr (a.k.a. `auto-compression-mode'), the returned
482name may have a suffix added from `ffap-compression-suffixes'.
483The optional NOMODIFY argument suppresses the extra search."
484 (cond
485 ((not file) nil) ; quietly reject nil
486 ((file-exists-p file) file) ; try unmodified first
487 ;; three reasons to suppress search:
488 (nomodify nil)
489 ((not (rassq 'jka-compr-handler file-name-handler-alist)) nil)
490 ((member (ffap-file-suffix file) ffap-compression-suffixes) nil)
491 (t ; ok, do the search
492 (let ((list ffap-compression-suffixes) try ret)
493 (while list
494 (if (file-exists-p (setq try (concat file (car list))))
495 (setq ret try list nil)
496 (setq list (cdr list))))
497 ret))))
0948761d 498
213d9a4f 499(defun ffap-file-remote-p (filename)
ee79ced8 500 "If FILENAME looks remote, return it (maybe slightly improved)."
213d9a4f 501 ;; (ffap-file-remote-p "/user@foo.bar.com:/pub")
95a85681 502 ;; (ffap-file-remote-p "/cssun.mathcs.emory.edu://dir")
87e2d039 503 ;; (ffap-file-remote-p "/ffap.el:80")
213d9a4f
RS
504 (or (and ffap-ftp-regexp
505 (string-match ffap-ftp-regexp filename)
95a85681
RS
506 ;; Convert "/host.com://dir" to "/host:/dir", to handle a dieing
507 ;; practice of advertising ftp files as "host.dom://filename".
213d9a4f 508 (if (string-match "//" filename)
87e2d039
RS
509 ;; (replace-match "/" nil nil filename)
510 (concat (substring filename 0 (1+ (match-beginning 0)))
511 (substring filename (match-end 0)))
213d9a4f
RS
512 filename))
513 (and ffap-rfs-regexp
514 (string-match ffap-rfs-regexp filename)
515 filename)))
516
517(defun ffap-machine-at-point nil
87e2d039
RS
518 "Return machine name at point if it exists, or nil."
519 (let ((mach (ffap-string-at-point 'machine)))
213d9a4f
RS
520 (and (ffap-machine-p mach) mach)))
521
95a85681 522(defsubst ffap-host-to-filename (host)
0948761d 523 "Convert HOST to something like \"/USER@HOST:\" or \"/HOST:\".
87e2d039 524Looks at `ffap-ftp-default-user', returns \"\" for \"localhost\"."
0948761d
KH
525 (if (equal host "localhost")
526 ""
527 (let ((user ffap-ftp-default-user))
528 ;; Avoid including the user if it is same as default:
529 (if (or (equal user (ffap-soft-value "ange-ftp-default-user"))
530 (equal user (ffap-soft-value "efs-default-user")))
531 (setq user nil))
532 (concat "/" user (and user "@") host ":"))))
87e2d039 533
213d9a4f 534(defun ffap-fixup-machine (mach)
95a85681 535 ;; Convert a hostname into an url, an ftp file name, or nil.
213d9a4f
RS
536 (cond
537 ((not (and ffap-url-regexp (stringp mach))) nil)
87e2d039 538 ;; gopher.well.com
213d9a4f
RS
539 ((string-match "\\`gopher[-.]" mach) ; or "info"?
540 (concat "gopher://" mach "/"))
87e2d039 541 ;; www.ncsa.uiuc.edu
213d9a4f
RS
542 ((and (string-match "\\`w\\(ww\\|eb\\)[-.]" mach))
543 (concat "http://" mach "/"))
544 ;; More cases? Maybe "telnet:" for archie?
95a85681 545 (ffap-ftp-regexp (ffap-host-to-filename mach))
213d9a4f
RS
546 ))
547
c9ae9869
RS
548(defvar ffap-newsgroup-regexp "^[a-z]+\\.[-+a-z_0-9.]+$"
549 "Strings not matching this fail `ffap-newsgroup-p'.")
550(defvar ffap-newsgroup-heads ; entirely inadequate
551 '("alt" "comp" "gnu" "misc" "news" "sci" "soc" "talk")
552 "Used by `ffap-newsgroup-p' if gnus is not running.")
553
213d9a4f
RS
554(defun ffap-newsgroup-p (string)
555 "Return STRING if it looks like a newsgroup name, else nil."
556 (and
557 (string-match ffap-newsgroup-regexp string)
558 (let ((htbs '(gnus-active-hashtb gnus-newsrc-hashtb gnus-killed-hashtb))
559 (heads ffap-newsgroup-heads)
560 htb ret)
561 (while htbs
562 (setq htb (car htbs) htbs (cdr htbs))
563 (condition-case nil
564 (progn
565 ;; errs: htb symbol may be unbound, or not a hash-table.
566 ;; gnus-gethash is just a macro for intern-soft.
001f5583
RS
567 (and (symbol-value htb)
568 (intern-soft string (symbol-value htb))
213d9a4f 569 (setq ret string htbs nil))
87e2d039 570 ;; If we made it this far, gnus is running, so ignore "heads":
213d9a4f
RS
571 (setq heads nil))
572 (error nil)))
573 (or ret (not heads)
574 (let ((head (string-match "\\`\\([a-z]+\\)\\." string)))
575 (and head (setq head (substring string 0 (match-end 1)))
576 (member head heads)
577 (setq ret string))))
87e2d039 578 ;; Is there ever a need to modify string as a newsgroup name?
213d9a4f 579 ret)))
213d9a4f 580
87e2d039
RS
581(defsubst ffap-url-p (string)
582 "If STRING looks like an url, return it (maybe improved), else nil."
213d9a4f
RS
583 (let ((case-fold-search t))
584 (and ffap-url-regexp (string-match ffap-url-regexp string)
585 ;; I lied, no improvement:
586 string)))
587
87e2d039
RS
588;; Broke these out of ffap-fixup-url, for use of ffap-url package.
589(defsubst ffap-url-unwrap-local (url)
590 "Return URL as a local file, or nil. Ignores `ffap-url-regexp'."
213d9a4f
RS
591 (and (string-match "\\`\\(file\\|ftp\\):/?\\([^/]\\|\\'\\)" url)
592 (substring url (1+ (match-end 1)))))
87e2d039
RS
593(defsubst ffap-url-unwrap-remote (url)
594 "Return URL as a remote file, or nil. Ignores `ffap-url-regexp'."
213d9a4f
RS
595 (and (string-match "\\`\\(ftp\\|file\\)://\\([^:/]+\\):?\\(/.*\\)" url)
596 (concat
95a85681 597 (ffap-host-to-filename (substring url (match-beginning 2) (match-end 2)))
213d9a4f 598 (substring url (match-beginning 3) (match-end 3)))))
87e2d039 599;; Test: (ffap-url-unwrap-remote "ftp://foo.com/bar.boz")
213d9a4f
RS
600
601(defun ffap-fixup-url (url)
87e2d039 602 "Clean up URL and return it, maybe as a file name."
213d9a4f
RS
603 (cond
604 ((not (stringp url)) nil)
605 ((and ffap-url-unwrap-local (ffap-url-unwrap-local url)))
606 ((and ffap-url-unwrap-remote ffap-ftp-regexp
607 (ffap-url-unwrap-remote url)))
3788c735
KH
608 ((fboundp 'url-normalize-url) ; may autoload url (part of w3)
609 (url-normalize-url url))
610 (url)))
213d9a4f
RS
611
612\f
95a85681 613;;; File Name Handling:
213d9a4f 614;;
0948761d 615;; The upcoming ffap-alist actions need various utilities to prepare
95a85681 616;; and search directories. Too many features here.
0948761d
KH
617
618;; (defun ffap-last (l) (while (cdr l) (setq l (cdr l))) l)
619;; (defun ffap-splice (func inlist)
620;; "Equivalent to (apply 'nconc (mapcar FUNC INLIST)), but less consing."
621;; (let* ((head (cons 17 nil)) (last head))
622;; (while inlist
623;; (setcdr last (funcall func (car inlist)))
624;; (setq last (ffap-last last) inlist (cdr inlist)))
625;; (cdr head)))
213d9a4f
RS
626
627(defun ffap-list-env (env &optional empty)
0948761d
KH
628 "Return a list of strings parsed from environment variable ENV.
629Optional EMPTY is the default list if \(getenv ENV\) is undefined, and
630also is substituted for the first empty-string component, if there is one.
631Uses `path-separator' to separate the path into substrings."
632 ;; We cannot use parse-colon-path (files.el), since it kills
633 ;; "//" entries using file-name-as-directory.
634 ;; Similar: dired-split, TeX-split-string, and RHOGEE's psg-list-env
635 ;; in ff-paths and bib-cite. The EMPTY arg may help mimic kpathsea.
213d9a4f
RS
636 (if (or empty (getenv env)) ; should return something
637 (let ((start 0) match dir ret)
87e2d039 638 (setq env (concat (getenv env) path-separator))
8c1001f6 639 (while (setq match (string-match path-separator env start))
213d9a4f
RS
640 (setq dir (substring env start match) start (1+ match))
641 ;;(and (file-directory-p dir) (not (member dir ret)) ...)
642 (setq ret (cons dir ret)))
643 (setq ret (nreverse ret))
644 (and empty (setq match (member "" ret))
0948761d 645 (progn ; allow string or list here
213d9a4f
RS
646 (setcdr match (append (cdr-safe empty) (cdr match)))
647 (setcar match (or (car-safe empty) empty))))
648 ret)))
649
650(defun ffap-reduce-path (path)
87e2d039 651 "Remove duplicates and non-directories from PATH list."
213d9a4f
RS
652 (let (ret tem)
653 (while path
654 (setq tem path path (cdr path))
87e2d039 655 (if (equal (car tem) ".") (setcar tem ""))
213d9a4f
RS
656 (or (member (car tem) ret)
657 (not (file-directory-p (car tem)))
658 (progn (setcdr tem ret) (setq ret tem))))
659 (nreverse ret)))
660
0948761d
KH
661(defun ffap-all-subdirs (dir &optional depth)
662 "Return list all subdirectories under DIR, starting with itself.
663Directories beginning with \".\" are ignored, and directory symlinks
664are listed but never searched (to avoid loops).
665Optional DEPTH limits search depth."
666 (and (file-exists-p dir)
667 (ffap-all-subdirs-loop (expand-file-name dir) (or depth -1))))
668
669(defun ffap-all-subdirs-loop (dir depth) ; internal
670 (setq depth (1- depth))
671 (cons dir
672 (and (not (eq depth -1))
673 (apply 'nconc
674 (mapcar
675 (function
676 (lambda (d)
677 (cond
678 ((not (file-directory-p d)) nil)
679 ((file-symlink-p d) (list d))
680 (t (ffap-all-subdirs-loop d depth)))))
681 (directory-files dir t "\\`[^.]")
682 )))))
683
684(defvar ffap-kpathsea-depth 1
685 "Bound on depth of subdirectory search in `ffap-kpathsea-expand-path'.
686Set to 0 to avoid all searching, or nil for no limit.")
687
688(defun ffap-kpathsea-expand-path (path)
689 "Replace each \"//\"-suffixed dir in PATH by a list of its subdirs.
690The subdirs begin with the original directory, and the depth of the
691search is bounded by `ffap-kpathsea-depth'. This is intended to mimic
692kpathsea, a library used by some versions of TeX."
693 (apply 'nconc
694 (mapcar
695 (function
696 (lambda (dir)
697 (if (string-match "[^/]//\\'" dir)
698 (ffap-all-subdirs (substring dir 0 -2) ffap-kpathsea-depth)
699 (list dir))))
700 path)))
701
3788c735
KH
702(defun ffap-locate-file (file &optional nosuffix path dir-ok)
703 ;; The Emacs 20 version of locate-library could almost replace this,
704 ;; except it does not let us overrride the suffix list. The
705 ;; compression-suffixes search moved to ffap-file-exists-string.
0948761d
KH
706 "A generic path-searching function, mimics `load' by default.
707Returns path to file that \(load FILE\) would load, or nil.
708Optional NOSUFFIX, if nil or t, is like the fourth argument
709for load: whether to try the suffixes (\".elc\" \".el\" \"\").
710If a nonempty list, it is a list of suffixes to try instead.
3788c735
KH
711Optional PATH is a list of directories instead of `load-path'.
712Optional DIR-OK means that returning a directory is allowed,
713DIR-OK is already implicit if FILE looks like a directory.
714
715This uses ffap-file-exists-string, which may try adding suffixes from
716`ffap-compression-suffixes'."
0948761d 717 (or path (setq path load-path))
3788c735 718 (or dir-ok (setq dir-ok (equal "" (file-name-nondirectory file))))
0948761d
KH
719 (if (file-name-absolute-p file)
720 (setq path (list (file-name-directory file))
721 file (file-name-nondirectory file)))
722 (let ((suffixes-to-try
723 (cond
724 ((consp nosuffix) nosuffix)
725 (nosuffix '(""))
3788c735
KH
726 (t '(".elc" ".el" ""))))
727 suffixes try found)
728 (while path
729 (setq suffixes suffixes-to-try)
730 (while suffixes
731 (setq try (ffap-file-exists-string
732 (expand-file-name
733 (concat file (car suffixes)) (car path))))
734 (if (and try (or dir-ok (not (file-directory-p try))))
735 (setq found try suffixes nil path nil)
736 (setq suffixes (cdr suffixes))))
737 (setq path (cdr path)))
738 found))
0948761d
KH
739
740\f
741;;; Action List (`ffap-alist'):
742;;
743;; These search actions depend on the major-mode or regexps matching
744;; the current name. The little functions and their variables are
745;; deferred to the next section, at some loss of "code locality". A
746;; good example of featuritis. Trim this list for speed.
213d9a4f 747
213d9a4f 748(defvar ffap-alist
c9ae9869 749 '(
0948761d
KH
750 ("" . ffap-completable) ; completion, slow on some systems
751 ("\\.info\\'" . ffap-info) ; gzip.info
752 ("\\`info/" . ffap-info-2) ; info/emacs
753 ("\\`[-a-z]+\\'" . ffap-info-3) ; (emacs)Top [only in the parentheses]
754 ("\\.elc?\\'" . ffap-el) ; simple.el, simple.elc
755 (emacs-lisp-mode . ffap-el-mode) ; rmail, gnus, simple, custom
3788c735 756 ;; (lisp-interaction-mode . ffap-el-mode) ; maybe
0948761d
KH
757 (finder-mode . ffap-el-mode) ; type {C-h p} and try it
758 (help-mode . ffap-el-mode) ; maybe useful
759 (c++-mode . ffap-c-mode) ; search ffap-c-path
760 (cc-mode . ffap-c-mode) ; same
761 ("\\.\\([chCH]\\|cc\\|hh\\)\\'" . ffap-c-mode) ; stdio.h
762 (fortran-mode . ffap-fortran-mode) ; FORTRAN requested by MDB
763 ("\\.[fF]\\'" . ffap-fortran-mode)
764 (tex-mode . ffap-tex-mode) ; search ffap-tex-path
765 (latex-mode . ffap-latex-mode) ; similar
c9ae9869 766 ("\\.\\(tex\\|sty\\|doc\\|cls\\)\\'" . ffap-tex)
0948761d
KH
767 ("\\.bib\\'" . ffap-bib) ; search ffap-bib-path
768 ("\\`\\." . ffap-home) ; .emacs, .bashrc, .profile
769 ("\\`~/" . ffap-lcd) ; |~/misc/ffap.el.Z|
c9ae9869 770 ("^[Rr][Ff][Cc][- #]?\\([0-9]+\\)" ; no $
0948761d
KH
771 . ffap-rfc) ; "100% RFC2100 compliant"
772 (dired-mode . ffap-dired) ; maybe in a subdirectory
773 )
87e2d039
RS
774 "Alist of \(KEY . FUNCTION\) pairs parsed by `ffap-file-at-point'.
775If string NAME at point (maybe \"\") is not a file or url, these pairs
776specify actions to try creating such a string. A pair matches if either
777 KEY is a symbol, and it equals `major-mode', or
778 KEY is a string, it should matches NAME as a regexp.
779On a match, \(FUNCTION NAME\) is called and should return a file, an
780url, or nil. If nil, search the alist for further matches.")
781
213d9a4f 782(put 'ffap-alist 'risky-local-variable t)
0948761d 783
3788c735
KH
784;; Example `ffap-alist' modifications:
785;;
786;; (setq ffap-alist ; remove a feature in `ffap-alist'
787;; (delete (assoc 'c-mode ffap-alist) ffap-alist))
788;;
789;; (setq ffap-alist ; add something to `ffap-alist'
790;; (cons
791;; (cons "^YSN[0-9]+$"
792;; (defun ffap-ysn (name)
793;; (concat
794;; "http://www.physics.uiuc.edu/"
795;; "ysn/httpd/htdocs/ysnarchive/issuefiles/"
796;; (substring name 3) ".html")))
797;; ffap-alist))
798
c9ae9869 799\f
0948761d
KH
800;;; Action Definitions:
801;;
802;; Define various default members of `ffap-alist'.
803
804(defun ffap-completable (name)
805 (let* ((dir (or (file-name-directory name) default-directory))
806 (cmp (file-name-completion (file-name-nondirectory name) dir)))
807 (and cmp (concat dir cmp))))
808
809(defun ffap-home (name) (ffap-locate-file name t '("~")))
c9ae9869
RS
810
811(defun ffap-info (name)
0948761d 812 (ffap-locate-file
c9ae9869
RS
813 name '("" ".info")
814 (or (ffap-soft-value "Info-directory-list")
815 (ffap-soft-value "Info-default-directory-list")
0948761d 816 )))
c9ae9869
RS
817
818(defun ffap-info-2 (name) (ffap-info (substring name 5)))
819
c9ae9869 820(defun ffap-info-3 (name)
0948761d 821 ;; This ignores the node! "(emacs)Top" same as "(emacs)Intro"
c9ae9869
RS
822 (and (equal (ffap-string-around) "()") (ffap-info name)))
823
0948761d 824(defun ffap-el (name) (ffap-locate-file name t))
c9ae9869
RS
825
826(defun ffap-el-mode (name)
0948761d
KH
827 ;; If name == "foo.el" we will skip it, since ffap-el already
828 ;; searched for it once. (This assumes the default ffap-alist.)
c9ae9869 829 (and (not (string-match "\\.el\\'" name))
0948761d
KH
830 (ffap-locate-file name '(".el"))))
831
832(defvar ffap-c-path
833 ;; Need smarter defaults here! Suggestions welcome.
834 '("/usr/include" "/usr/local/include"))
835(defun ffap-c-mode (name)
836 (ffap-locate-file name t ffap-c-path))
837
838(defvar ffap-fortran-path '("../include" "/usr/include"))
839
840(defun ffap-fortran-mode (name)
841 (ffap-locate-file name t ffap-fortran-path))
c9ae9869 842
c9ae9869
RS
843(defvar ffap-tex-path
844 t ; delayed initialization
845 "Path where `ffap-tex-mode' looks for tex files.
846If t, `ffap-tex-init' will initialize this when needed.")
213d9a4f 847
c9ae9869
RS
848(defun ffap-tex-init nil
849 ;; Compute ffap-tex-path if it is now t.
850 (and (eq t ffap-tex-path)
0948761d 851 ;; this may be slow, so say something
c9ae9869
RS
852 (message "Initializing ffap-tex-path ...")
853 (setq ffap-tex-path
854 (ffap-reduce-path
0948761d
KH
855 (cons
856 "."
857 (ffap-kpathsea-expand-path
858 (append
859 (ffap-list-env "TEXINPUTS")
860 ;; (ffap-list-env "BIBINPUTS")
861 (ffap-soft-value
862 "TeX-macro-global" ; AUCTeX
863 '("/usr/local/lib/tex/macros"
864 "/usr/local/lib/tex/inputs")))))))))
c9ae9869
RS
865
866(defun ffap-tex-mode (name)
867 (ffap-tex-init)
0948761d 868 (ffap-locate-file name '(".tex" "") ffap-tex-path))
c9ae9869
RS
869
870(defun ffap-latex-mode (name)
871 (ffap-tex-init)
0948761d
KH
872 ;; only rare need for ""
873 (ffap-locate-file name '(".cls" ".sty" ".tex" "") ffap-tex-path))
c9ae9869
RS
874
875(defun ffap-tex (name)
876 (ffap-tex-init)
0948761d
KH
877 (ffap-locate-file name t ffap-tex-path))
878
879(defvar ffap-bib-path
880 (ffap-list-env "BIBINPUTS"
881 (ffap-reduce-path
882 '(
883 ;; a few wild guesses, need better
884 "/usr/local/lib/tex/macros/bib" ; Solaris?
885 "/usr/lib/texmf/bibtex/bib" ; Linux?
886 ))))
c9ae9869
RS
887
888(defun ffap-bib (name)
0948761d 889 (ffap-locate-file name t ffap-bib-path))
c9ae9869
RS
890
891(defun ffap-dired (name)
892 (let ((pt (point)) dir try)
893 (save-excursion
894 (and (progn
895 (beginning-of-line)
896 (looking-at " *[-d]r[-w][-x][-r][-w][-x][-r][-w][-x] "))
897 (re-search-backward "^ *$" nil t)
898 (re-search-forward "^ *\\([^ \t\n:]*\\):\n *total " pt t)
899 (file-exists-p
900 (setq try
901 (expand-file-name
902 name
903 (buffer-substring
904 (match-beginning 1) (match-end 1)))))
905 try))))
906
907;; Maybe a "Lisp Code Directory" reference:
908(defun ffap-lcd (name)
909 (and
910 (or
911 ;; lisp-dir-apropos output buffer:
912 (string-match "Lisp Code Dir" (buffer-name))
913 ;; Inside an LCD entry like |~/misc/ffap.el.Z|,
914 ;; or maybe the holy LCD-Datafile itself:
915 (member (ffap-string-around) '("||" "|\n")))
916 (concat
917 ;; lispdir.el may not be loaded yet:
95a85681 918 (ffap-host-to-filename
c9ae9869
RS
919 (ffap-soft-value "elisp-archive-host"
920 "archive.cis.ohio-state.edu"))
921 (file-name-as-directory
922 (ffap-soft-value "elisp-archive-directory"
923 "/pub/gnu/emacs/elisp-archive/"))
924 (substring name 2))))
925
926(defvar ffap-rfc-path
95a85681 927 (concat (ffap-host-to-filename "ds.internic.net") "/rfc/rfc%s.txt"))
c9ae9869
RS
928
929(defun ffap-rfc (name)
930 (format ffap-rfc-path
931 (substring name (match-beginning 1) (match-end 1))))
0948761d 932
213d9a4f
RS
933\f
934;;; At-Point Functions:
935
936(defvar ffap-string-at-point-mode-alist
937 '(
87e2d039 938 ;; The default, used when the `major-mode' is not found.
213d9a4f
RS
939 ;; Slightly controversial decisions:
940 ;; * strip trailing "@" and ":"
941 ;; * no commas (good for latex)
87e2d039
RS
942 (file "--:$+<>@-Z_a-z~" "<@" "@>;.,!?:")
943 ;; An url, or maybe a email/news message-id:
0948761d 944 (url "--:=&?$+@-Z_a-z~#,%" "^A-Za-z0-9" ":;.,!?")
87e2d039
RS
945 ;; Find a string that does *not* contain a colon:
946 (nocolon "--9$+<>@-Z_a-z~" "<@" "@>;.,!?")
947 ;; A machine:
948 (machine "-a-zA-Z0-9." "" ".")
949 ;; Mathematica paths: allow backquotes
950 (math-mode ",-:$+<>@-Z_a-z~`" "<" "@>;.,!?`:")
213d9a4f 951 )
87e2d039 952 "Alist of \(MODE CHARS BEG END\), where MODE is a symbol,
ee79ced8
KH
953possibly a major-mode name, or one of the symbol
954`file', `url', `machine', and `nocolon'.
87e2d039
RS
955`ffap-string-at-point' uses the data fields as follows:
9561. find a maximal string of CHARS around point,
9572. strip BEG chars before point from the beginning,
9583. Strip END chars after point from the end.")
213d9a4f 959
213d9a4f
RS
960(defvar ffap-string-at-point nil
961 ;; Added at suggestion of RHOGEE (for ff-paths), 7/24/95.
87e2d039
RS
962 "Last string returned by `ffap-string-at-point'.")
963
964(defun ffap-string-at-point (&optional mode)
965 "Return a string of characters from around point.
ee79ced8 966MODE (defaults to value of `major-mode') is a symbol used to look up string
87e2d039 967syntax parameters in `ffap-string-at-point-mode-alist'.
ee79ced8 968If MODE is not found, we use `file' instead of MODE.
87e2d039
RS
969Sets `ffap-string-at-point' and `ffap-string-at-point-region'."
970 (let* ((args
971 (cdr
972 (or (assq (or mode major-mode) ffap-string-at-point-mode-alist)
973 (assq 'file ffap-string-at-point-mode-alist))))
974 (pt (point))
975 (str
976 (buffer-substring
977 (save-excursion
978 (skip-chars-backward (car args))
979 (skip-chars-forward (nth 1 args) pt)
980 (setcar ffap-string-at-point-region (point)))
981 (save-excursion
982 (skip-chars-forward (car args))
983 (skip-chars-backward (nth 2 args) pt)
984 (setcar (cdr ffap-string-at-point-region) (point))))))
0948761d 985 (set-text-properties 0 (length str) nil str)
87e2d039 986 (setq ffap-string-at-point str)))
213d9a4f
RS
987
988(defun ffap-string-around nil
989 ;; Sometimes useful to decide how to treat a string.
87e2d039
RS
990 "Return string of two chars around last `ffap-string-at-point'.
991Assumes the buffer has not changed."
213d9a4f
RS
992 (save-excursion
993 (format "%c%c"
994 (progn
995 (goto-char (car ffap-string-at-point-region))
996 (preceding-char)) ; maybe 0
997 (progn
998 (goto-char (nth 1 ffap-string-at-point-region))
999 (following-char)) ; maybe 0
1000 )))
1001
87e2d039
RS
1002(defun ffap-copy-string-as-kill (&optional mode)
1003 ;; Requested by MCOOK. Useful?
1004 "Call `ffap-string-at-point', and copy result to `kill-ring'."
1005 (interactive)
1006 (let ((str (ffap-string-at-point mode)))
1007 (if (equal "" str)
1008 (message "No string found around point.")
1009 (kill-new str)
1010 ;; Older: (apply 'copy-region-as-kill ffap-string-at-point-region)
1011 (message "Copied to kill ring: %s" str))))
1012
213d9a4f 1013(defun ffap-url-at-point nil
87e2d039
RS
1014 "Return url from around point if it exists, or nil."
1015 ;; Could use w3's url-get-url-at-point instead. Both handle "URL:",
1016 ;; ignore non-relative links, trim punctuation. The other will
1017 ;; actually look back if point is in whitespace, but I would rather
0948761d 1018 ;; ffap be less aggressive in such situations.
213d9a4f
RS
1019 (and
1020 ffap-url-regexp
1021 (or
0948761d
KH
1022 ;; In a w3 buffer button?
1023 (and (eq major-mode 'w3-mode)
1024 ;; interface recommended by wmperry:
1025 (w3-view-this-url t))
213d9a4f 1026 ;; Is there a reason not to strip trailing colon?
87e2d039 1027 (let ((name (ffap-string-at-point 'url)))
213d9a4f
RS
1028 (cond
1029 ((string-match "^url:" name) (setq name (substring name 4)))
0948761d 1030 ((and (string-match "\\`[^:</>@]+@[^:</>@]+[a-zA-Z0-9]\\'" name)
213d9a4f 1031 ;; "foo@bar": could be "mailto" or "news" (a Message-ID).
0948761d
KH
1032 ;; Without "<>" it must be "mailto". Otherwise could be
1033 ;; either, so consult `ffap-foo-at-bar-prefix'.
213d9a4f 1034 (let ((prefix (if (and (equal (ffap-string-around) "<>")
0948761d 1035 ;; Expect some odd characters:
213d9a4f
RS
1036 (string-match "[$.0-9].*[$.0-9].*@" name))
1037 ;; Could be news:
87e2d039 1038 ffap-foo-at-bar-prefix
213d9a4f
RS
1039 "mailto")))
1040 (and prefix (setq name (concat prefix ":" name))))))
1041 ((ffap-newsgroup-p name) (setq name (concat "news:" name)))
1042 ((and (string-match "\\`[a-z0-9]+\\'" name) ; <mic> <root> <nobody>
1043 (equal (ffap-string-around) "<>")
1044 ;; (ffap-user-p name):
1045 (not (string-match "~" (expand-file-name (concat "~" name))))
1046 )
1047 (setq name (concat "mailto:" name)))
1048 )
1049 (and (ffap-url-p name) name)
1050 ))))
1051
1052(defvar ffap-gopher-regexp
1053 "^.*\\<\\(Type\\|Name\\|Path\\|Host\\|Port\\) *= *\\(.*\\) *$"
1054 "Regexp Matching a line in a gopher bookmark (maybe indented).
87e2d039 1055The two subexpressions are the KEY and VALUE.")
213d9a4f
RS
1056
1057(defun ffap-gopher-at-point nil
1058 "If point is inside a gopher bookmark block, return its url."
87e2d039 1059 ;; `gopher-parse-bookmark' from gopher.el is not so robust
213d9a4f
RS
1060 (save-excursion
1061 (beginning-of-line)
1062 (if (looking-at ffap-gopher-regexp)
1063 (progn
1064 (while (and (looking-at ffap-gopher-regexp) (not (bobp)))
1065 (forward-line -1))
1066 (or (looking-at ffap-gopher-regexp) (forward-line 1))
1067 (let ((type "1") name path host (port "70"))
1068 (while (looking-at ffap-gopher-regexp)
1069 (let ((var (intern
1070 (downcase
1071 (buffer-substring (match-beginning 1)
1072 (match-end 1)))))
1073 (val (buffer-substring (match-beginning 2)
1074 (match-end 2))))
1075 (set var val)
1076 (forward-line 1)))
1077 (if (and path (string-match "^ftp:.*@" path))
1078 (concat "ftp://"
1079 (substring path 4 (1- (match-end 0)))
1080 (substring path (match-end 0)))
1081 (and (= (length type) 1)
1082 host;; (ffap-machine-p host)
1083 (concat "gopher://" host
1084 (if (equal port "70") "" (concat ":" port))
1085 "/" type path))))))))
1086
1087(defvar ffap-ftp-sans-slash-regexp
1088 (and
1089 ffap-ftp-regexp
87e2d039 1090 ;; Note: by now, we know it is not an url.
213d9a4f
RS
1091 ;; Icky regexp avoids: default: 123: foo::bar cs:pub
1092 ;; It does match on: mic@cs: cs:/pub mathcs.emory.edu: (point at end)
213d9a4f 1093 "\\`\\([^:@]+@[^:@]+:\\|[^@.:]+\\.[^@:]+:\\|[^:]+:[~/]\\)\\([^:]\\|\\'\\)")
95a85681 1094 "Strings matching this are coerced to ftp file names by ffap.
213d9a4f
RS
1095That is, ffap just prepends \"/\". Set to nil to disable.")
1096
1097(defun ffap-file-at-point nil
1098 "Return filename from around point if it exists, or nil.
1099Existence test is skipped for names that look remote.
1100If the filename is not obvious, it also tries `ffap-alist',
87e2d039
RS
1101which may actually result in an url rather than a filename."
1102 ;; Note: this function does not need to look for url's, just
213d9a4f 1103 ;; filenames. On the other hand, it is responsible for converting
95a85681 1104 ;; a pseudo-url "site.com://dir" to an ftp file name
213d9a4f
RS
1105 (let* ((case-fold-search t) ; url prefixes are case-insensitive
1106 (data (match-data))
87e2d039 1107 (string (ffap-string-at-point)) ; uses mode alist
213d9a4f 1108 (name
87e2d039
RS
1109 (or (condition-case nil
1110 (and (not (string-match "//" string)) ; foo.com://bar
1111 (substitute-in-file-name string))
1112 (error nil))
1113 string))
213d9a4f
RS
1114 (abs (file-name-absolute-p name))
1115 (default-directory default-directory))
1116 (unwind-protect
1117 (cond
1118 ;; Immediate rejects (/ and // are too common in C++):
8005ea3f
RS
1119 ((member name '("" "/" "//" ".")) nil)
1120 ;; Immediately test local filenames. If default-directory is
1121 ;; remote, you probably already have a connection.
1122 ((and (not abs) (ffap-file-exists-string name)))
1123 ;; Try stripping off line numbers; good for compilation/grep output.
1124 ((and (not abs) (string-match ":[0-9]" name)
1125 (ffap-file-exists-string (substring name 0 (match-beginning 0)))))
c98ddbe5
RV
1126 ;; Try stripping off prominent (non-root - #) shell prompts
1127 ;; if the ffap-shell-prompt-regexp is non-nil.
1128 ((and ffap-shell-prompt-regexp
1129 (not abs) (string-match ffap-shell-prompt-regexp name)
1130 (ffap-file-exists-string (substring name (match-end 0)))))
213d9a4f
RS
1131 ;; Immediately test local filenames. If default-directory is
1132 ;; remote, you probably already have a connection.
1133 ((and (not abs) (ffap-file-exists-string name)))
1134 ;; Accept remote names without actual checking (too slow):
1135 ((if abs
1136 (ffap-file-remote-p name)
95a85681 1137 ;; Try adding a leading "/" (common omission in ftp file names):
213d9a4f
RS
1138 (and
1139 ffap-ftp-sans-slash-regexp
1140 (string-match ffap-ftp-sans-slash-regexp name)
1141 (ffap-file-remote-p (concat "/" name)))))
1142 ;; Ok, not remote, try the existence test even if it is absolute:
1143 ((and abs (ffap-file-exists-string name)))
87e2d039
RS
1144 ;; If it contains a colon, get rid of it (and return if exists)
1145 ((and (string-match path-separator name)
1146 (setq name (ffap-string-at-point 'nocolon))
1147 (ffap-file-exists-string name)))
213d9a4f
RS
1148 ;; File does not exist, try the alist:
1149 ((let ((alist ffap-alist) tem try case-fold-search)
1150 (while (and alist (not try))
1151 (setq tem (car alist) alist (cdr alist))
1152 (if (or (eq major-mode (car tem))
1153 (and (stringp (car tem))
1154 (string-match (car tem) name)))
0948761d
KH
1155 (and (setq try
1156 (condition-case nil
1157 (funcall (cdr tem) name)
1158 (error nil)))
213d9a4f
RS
1159 (setq try (or
1160 (ffap-url-p try) ; not a file!
1161 (ffap-file-remote-p try)
1162 (ffap-file-exists-string try))))))
1163 try))
1164 ;; Alist failed? Try to guess an active remote connection
1165 ;; from buffer variables, and try once more, both as an
95a85681 1166 ;; absolute and relative file name on that remote host.
213d9a4f
RS
1167 ((let* (ffap-rfs-regexp ; suppress
1168 (remote-dir
1169 (cond
1170 ((ffap-file-remote-p default-directory))
1171 ((and (eq major-mode 'internal-ange-ftp-mode)
1172 (string-match "^\\*ftp \\(.*\\)@\\(.*\\)\\*$"
1173 (buffer-name)))
1174 (concat "/" (substring (buffer-name) 5 -1) ":"))
1175 ;; This is too often a bad idea:
1176 ;;((and (eq major-mode 'w3-mode)
1177 ;; (stringp url-current-server))
1178 ;; (host-to-ange-path url-current-server))
1179 )))
1180 (and remote-dir
1181 (or
1182 (and (string-match "\\`\\(/?~?ftp\\)/" name)
1183 (ffap-file-exists-string
95a85681 1184 (ffap-replace-file-component
213d9a4f
RS
1185 remote-dir (substring name (match-end 1)))))
1186 (ffap-file-exists-string
95a85681 1187 (ffap-replace-file-component remote-dir name))))))
213d9a4f 1188 )
3c2c6be2 1189 (set-match-data data))))
213d9a4f 1190\f
0948761d 1191;;; Prompting (`ffap-read-file-or-url'):
213d9a4f 1192;;
87e2d039
RS
1193;; We want to complete filenames as in read-file-name, but also url's
1194;; which read-file-name-internal would truncate at the "//" string.
1195;; The solution here is to replace read-file-name-internal with
1196;; `ffap-read-file-or-url-internal', which checks the minibuffer
1197;; contents before attempting to complete filenames.
213d9a4f
RS
1198
1199(defun ffap-read-file-or-url (prompt guess)
87e2d039 1200 "Read file or url from minibuffer, with PROMPT and initial GUESS."
213d9a4f 1201 (or guess (setq guess default-directory))
87e2d039 1202 (let (dir)
213d9a4f
RS
1203 ;; Tricky: guess may have or be a local directory, like "w3/w3.elc"
1204 ;; or "w3/" or "../el/ffap.el" or "../../../"
87e2d039 1205 (or (ffap-url-p guess)
213d9a4f
RS
1206 (progn
1207 (or (ffap-file-remote-p guess)
3788c735
KH
1208 (setq guess
1209 (abbreviate-file-name (expand-file-name guess))
1210 ))
213d9a4f 1211 (setq dir (file-name-directory guess))))
a22f0735
RS
1212 (let ((minibuffer-completing-file-name t))
1213 (setq guess
1214 (completing-read
1215 prompt
1216 'ffap-read-file-or-url-internal
1217 dir
1218 nil
7b86ef7d 1219 (if dir (cons guess (length dir)) guess)
a22f0735 1220 (list 'file-name-history))))
87e2d039
RS
1221 ;; Do file substitution like (interactive "F"), suggested by MCOOK.
1222 (or (ffap-url-p guess) (setq guess (substitute-in-file-name guess)))
1223 ;; Should not do it on url's, where $ is a common (VMS?) character.
1224 ;; Note: upcoming url.el package ought to handle this automatically.
1225 guess))
213d9a4f
RS
1226
1227(defun ffap-read-url-internal (string dir action)
87e2d039
RS
1228 "Complete url's from history, treating given string as valid."
1229 (let ((hist (ffap-soft-value "url-global-history-hash-table")))
213d9a4f
RS
1230 (cond
1231 ((not action)
1232 (or (try-completion string hist) string))
1233 ((eq action t)
1234 (or (all-completions string hist) (list string)))
87e2d039
RS
1235 ;; action == lambda, documented where? Tests whether string is a
1236 ;; valid "match". Let us always say yes.
1237 (t t))))
213d9a4f
RS
1238
1239(defun ffap-read-file-or-url-internal (string dir action)
d4021fd9
GM
1240 (unless dir
1241 (setq dir default-directory))
1242 (unless string
1243 (setq string default-directory))
213d9a4f
RS
1244 (if (ffap-url-p string)
1245 (ffap-read-url-internal string dir action)
1246 (read-file-name-internal string dir action)))
1247
87e2d039
RS
1248;; The rest of this page is just to work with package complete.el.
1249;; This code assumes that you load ffap.el after complete.el.
1250;;
1251;; We must inform complete about whether our completion function
8daa9f3d 1252;; will do filename style completion.
87e2d039
RS
1253
1254(defun ffap-complete-as-file-p nil
1255 ;; Will `minibuffer-completion-table' complete the minibuffer
1256 ;; contents as a filename? Assumes the minibuffer is current.
1257 ;; Note: t and non-nil mean somewhat different reasons.
1258 (if (eq minibuffer-completion-table 'ffap-read-file-or-url-internal)
1259 (not (ffap-url-p (buffer-string))) ; t
9925c419 1260 (and minibuffer-completing-file-name '(t)))) ;list
87e2d039 1261
213d9a4f
RS
1262(and
1263 (featurep 'complete)
87e2d039
RS
1264 (if (boundp 'PC-completion-as-file-name-predicate)
1265 ;; modern version of complete.el, just set the variable:
8daa9f3d 1266 (setq PC-completion-as-file-name-predicate 'ffap-complete-as-file-p)))
213d9a4f
RS
1267
1268\f
0948761d 1269;;; Highlighting (`ffap-highlight'):
213d9a4f
RS
1270;;
1271;; Based on overlay highlighting in Emacs 19.28 isearch.el.
1272
33514810 1273(defvar ffap-highlight t
213d9a4f
RS
1274 "If non-nil, ffap highlights the current buffer substring.")
1275
0948761d
KH
1276(defvar ffap-highlight-overlay nil
1277 "Overlay used by `ffap-highlight'.")
213d9a4f
RS
1278
1279(defun ffap-highlight (&optional remove)
87e2d039
RS
1280 "If `ffap-highlight' is set, highlight the guess in this buffer.
1281That is, the last buffer substring found by `ffap-string-at-point'.
213d9a4f 1282Optional argument REMOVE means to remove any such highlighting.
87e2d039 1283Uses the face `ffap' if it is defined, or else `highlight'."
213d9a4f 1284 (cond
0948761d
KH
1285 (remove
1286 (and ffap-highlight-overlay
3788c735
KH
1287 (delete-overlay ffap-highlight-overlay))
1288 )
213d9a4f 1289 ((not ffap-highlight) nil)
87e2d039 1290 (ffap-highlight-overlay
3788c735
KH
1291 (move-overlay
1292 ffap-highlight-overlay
1293 (car ffap-string-at-point-region)
1294 (nth 1 ffap-string-at-point-region)
1295 (current-buffer)))
213d9a4f 1296 (t
0948761d 1297 (setq ffap-highlight-overlay
3788c735
KH
1298 (apply 'make-overlay ffap-string-at-point-region))
1299 (overlay-put ffap-highlight-overlay 'face
e5eee690 1300 (if (facep 'ffap) 'ffap 'highlight)))))
87e2d039 1301
213d9a4f 1302\f
3788c735 1303;;; Main Entrance (`find-file-at-point' == `ffap'):
213d9a4f
RS
1304
1305(defun ffap-guesser nil
0948761d 1306 "Return file or URL or nil, guessed from text around point."
213d9a4f
RS
1307 (or (and ffap-url-regexp
1308 (ffap-fixup-url (or (ffap-url-at-point)
1309 (ffap-gopher-at-point))))
1310 (ffap-file-at-point) ; may yield url!
1311 (ffap-fixup-machine (ffap-machine-at-point))))
1312
1313(defun ffap-prompter (&optional guess)
1314 ;; Does guess and prompt step for find-file-at-point.
87e2d039 1315 ;; Extra complication for the temporary highlighting.
213d9a4f 1316 (unwind-protect
3788c735
KH
1317 ;; This catch will let ffap-alist entries do their own prompting
1318 ;; and then maybe skip over this prompt (ff-paths, for example).
1319 (catch 'ffap-prompter
1320 (ffap-read-file-or-url
1321 (if ffap-url-regexp "Find file or URL: " "Find file: ")
1322 (prog1
1323 (setq guess (or guess (ffap-guesser))) ; using ffap-alist here
1324 (and guess (ffap-highlight))
1325 )))
213d9a4f
RS
1326 (ffap-highlight t)))
1327
1328;;;###autoload
1329(defun find-file-at-point (&optional filename)
0948761d
KH
1330 "Find FILENAME, guessing a default from text around point.
1331If `ffap-url-regexp' is not nil, the FILENAME may also be an URL.
1332With a prefix, this command behaves exactly like `ffap-file-finder'.
213d9a4f 1333If `ffap-require-prefix' is set, the prefix meaning is reversed.
0948761d
KH
1334See also the variables `ffap-dired-wildcards', `ffap-newfile-prompt',
1335and the functions `ffap-file-at-point' and `ffap-url-at-point'.
213d9a4f 1336
87e2d039 1337See <ftp://ftp.mathcs.emory.edu/pub/mic/emacs/> for latest version."
213d9a4f
RS
1338 (interactive)
1339 (if (and (interactive-p)
1340 (if ffap-require-prefix (not current-prefix-arg)
1341 current-prefix-arg))
1342 ;; Do exactly the ffap-file-finder command, even the prompting:
87e2d039
RS
1343 (let (current-prefix-arg) ; we already interpreted it
1344 (call-interactively ffap-file-finder))
213d9a4f
RS
1345 (or filename (setq filename (ffap-prompter)))
1346 (cond
1347 ((ffap-url-p filename)
87e2d039
RS
1348 (let (current-prefix-arg) ; w3 2.3.25 bug, reported by KPC
1349 (funcall ffap-url-fetcher filename)))
213d9a4f 1350 ;; This junk more properly belongs in a modified ffap-file-finder:
87e2d039
RS
1351 ((and ffap-dired-wildcards
1352 (string-match ffap-dired-wildcards filename))
213d9a4f
RS
1353 (dired filename))
1354 ((or (not ffap-newfile-prompt)
1355 (file-exists-p filename)
1356 (y-or-n-p "File does not exist, create buffer? "))
1357 (funcall ffap-file-finder
1358 ;; expand-file-name fixes "~/~/.emacs" bug sent by CHUCKR.
1359 (expand-file-name filename)))
1360 ;; User does not want to find a non-existent file:
1361 ((signal 'file-error (list "Opening file buffer"
1362 "no such file or directory"
1363 filename))))))
1364
0948761d 1365;; Shortcut: allow {M-x ffap} rather than {M-x find-file-at-point}.
22ac7ca0
MR
1366;;;###autoload
1367(defalias 'ffap 'find-file-at-point)
1368
213d9a4f 1369\f
0948761d 1370;;; Menu support (`ffap-menu'):
213d9a4f
RS
1371
1372(defvar ffap-menu-regexp nil
87e2d039 1373 "*If non-nil, overrides `ffap-next-regexp' during `ffap-menu'.
213d9a4f 1374Make this more restrictive for faster menu building.
0948761d 1375For example, try \":/\" for URL (and some ftp) references.")
213d9a4f
RS
1376
1377(defvar ffap-menu-alist nil
87e2d039 1378 "Buffer local cache of menu presented by `ffap-menu'.")
213d9a4f
RS
1379(make-variable-buffer-local 'ffap-menu-alist)
1380
87e2d039 1381(defvar ffap-menu-text-plist
3788c735 1382 (cond
33514810
EZ
1383 ((display-mouse-p) '(face bold mouse-face highlight)) ; keymap <mousy-map>
1384 (t nil))
87e2d039
RS
1385 "Text properties applied to strings found by `ffap-menu-rescan'.
1386These properties may be used to fontify the menu references.")
1387
213d9a4f
RS
1388;;;###autoload
1389(defun ffap-menu (&optional rescan)
87e2d039
RS
1390 "Put up a menu of files and urls mentioned in this buffer.
1391Then set mark, jump to choice, and try to fetch it. The menu is
1392cached in `ffap-menu-alist', and rebuilt by `ffap-menu-rescan'.
1393The optional RESCAN argument \(a prefix, interactively\) forces
1394a rebuild. Searches with `ffap-menu-regexp'."
213d9a4f
RS
1395 (interactive "P")
1396 ;; (require 'imenu) -- no longer used, but roughly emulated
1397 (if (or (not ffap-menu-alist) rescan
1398 ;; or if the first entry is wrong:
1399 (and ffap-menu-alist
1400 (let ((first (car ffap-menu-alist)))
1401 (save-excursion
1402 (goto-char (cdr first))
1403 (not (equal (car first) (ffap-guesser)))))))
1404 (ffap-menu-rescan))
1405 ;; Tail recursive:
1406 (ffap-menu-ask
1407 (if ffap-url-regexp "Find file or URL" "Find file")
1408 (cons (cons "*Rescan Buffer*" -1) ffap-menu-alist)
1409 'ffap-menu-cont))
1410
1411(defun ffap-menu-cont (choice) ; continuation of ffap-menu
1412 (if (< (cdr choice) 0)
1413 (ffap-menu t) ; *Rescan*
1414 (push-mark)
1415 (goto-char (cdr choice))
1416 ;; Momentary highlight:
1417 (unwind-protect
1418 (progn
1419 (and ffap-highlight (ffap-guesser) (ffap-highlight))
1420 (sit-for 0) ; display
1421 (find-file-at-point (car choice)))
1422 (ffap-highlight t))))
1423
1424(defun ffap-menu-ask (title alist cont)
1425 "Prompt from a menu of choices, and then apply some action.
0948761d 1426Arguments are TITLE, ALIST, and CONT \(a continuation function\).
213d9a4f
RS
1427This uses either a menu or the minibuffer depending on invocation.
1428The TITLE string is used as either the prompt or menu title.
ee79ced8 1429Each ALIST entry looks like (STRING . DATA) and defines one choice.
0948761d
KH
1430Function CONT is applied to the entry chosen by the user."
1431 ;; Note: this function is used with a different continuation
1432 ;; by the ffap-url add-on package.
1433 ;; Could try rewriting to use easymenu.el or lmenu.el.
1434 (let (choice)
1435 (cond
1436 ;; Emacs mouse:
1437 ((and (fboundp 'x-popup-menu) (ffap-mouse-event))
1438 (setq choice
1439 (x-popup-menu
1440 t
1441 (list "" (cons title
1442 (mapcar (function (lambda (i) (cons (car i) i)))
1443 alist))))))
1444 ;; minibuffer with completion buffer:
1445 (t
1446 (let ((minibuffer-setup-hook 'minibuffer-completion-help))
1447 ;; Bug: prompting may assume unique strings, no "".
1448 (setq choice
1449 (completing-read
1450 (format "%s (default %s): " title (car (car alist)))
1451 alist nil t
1452 ;; (cons (car (car alist)) 0)
1453 nil)))
1454 (sit-for 0) ; redraw original screen
1455 ;; Convert string to its entry, or else the default:
1456 (setq choice (or (assoc choice alist) (car alist))))
1457 )
1458 (if choice
1459 (funcall cont choice)
1460 (message "No choice made!") ; possible with menus
1461 nil)))
213d9a4f
RS
1462
1463(defun ffap-menu-rescan nil
87e2d039
RS
1464 "Search buffer for `ffap-menu-regexp' to build `ffap-menu-alist'.
1465Applies `ffap-menu-text-plist' text properties at all matches."
213d9a4f
RS
1466 (interactive)
1467 (let ((ffap-next-regexp (or ffap-menu-regexp ffap-next-regexp))
0948761d
KH
1468 (range (- (point-max) (point-min)))
1469 (mod (buffer-modified-p)) ; was buffer modified?
87e2d039 1470 buffer-read-only ; to set text-properties
0948761d 1471 item
87e2d039
RS
1472 ;; Avoid repeated searches of the *mode-alist:
1473 (major-mode (if (assq major-mode ffap-string-at-point-mode-alist)
1474 major-mode
0948761d 1475 'file)))
213d9a4f 1476 (setq ffap-menu-alist nil)
0948761d
KH
1477 (unwind-protect
1478 (save-excursion
1479 (goto-char (point-min))
1480 (while (setq item (ffap-next-guess))
1481 (setq ffap-menu-alist (cons (cons item (point)) ffap-menu-alist))
1482 (add-text-properties (car ffap-string-at-point-region) (point)
1483 ffap-menu-text-plist)
1484 (message "Scanning...%2d%% <%s>"
1485 (/ (* 100 (- (point) (point-min))) range) item)))
1486 (or mod (set-buffer-modified-p nil))))
213d9a4f
RS
1487 (message "Scanning...done")
1488 ;; Remove duplicates.
1489 (setq ffap-menu-alist ; sort by item
1490 (sort ffap-menu-alist
1491 (function
1492 (lambda (a b) (string-lessp (car a) (car b))))))
0948761d 1493 (let ((ptr ffap-menu-alist)) ; remove duplicates
213d9a4f
RS
1494 (while (cdr ptr)
1495 (if (equal (car (car ptr)) (car (car (cdr ptr))))
1496 (setcdr ptr (cdr (cdr ptr)))
1497 (setq ptr (cdr ptr)))))
1498 (setq ffap-menu-alist ; sort by position
1499 (sort ffap-menu-alist
1500 (function
1501 (lambda (a b) (< (cdr a) (cdr b)))))))
1502
1503\f
0948761d 1504;;; Mouse Support (`ffap-at-mouse'):
213d9a4f 1505;;
87e2d039 1506;; See the suggested binding in ffap-bindings (near eof).
213d9a4f 1507
0948761d
KH
1508(defvar ffap-at-mouse-fallback nil ; ffap-menu? too time-consuming
1509 "Command invoked by `ffap-at-mouse' if nothing found at click, or nil.
1510Ignored when `ffap-at-mouse' is called programmatically.")
213d9a4f
RS
1511(put 'ffap-at-mouse-fallback 'risky-local-variable t)
1512
0948761d 1513;;;###autoload
213d9a4f 1514(defun ffap-at-mouse (e)
0948761d 1515 "Find file or url guessed from text around mouse click.
3788c735
KH
1516Interactively, calls `ffap-at-mouse-fallback' if no guess is found.
1517Return value:
1518 * if a guess string is found, return it (after finding it)
1519 * if the fallback is called, return whatever it returns
1520 * otherwise, nil"
213d9a4f
RS
1521 (interactive "e")
1522 (let ((guess
1523 ;; Maybe less surprising without the save-excursion?
1524 (save-excursion
1525 (mouse-set-point e)
0948761d
KH
1526 ;; Would prefer to do nothing unless click was *on* text. How
1527 ;; to tell that the click was beyond the end of current line?
213d9a4f
RS
1528 (ffap-guesser))))
1529 (cond
1530 (guess
0948761d 1531 (set-buffer (ffap-event-buffer e))
213d9a4f
RS
1532 (ffap-highlight)
1533 (unwind-protect
1534 (progn
1535 (sit-for 0) ; display
0948761d
KH
1536 (message "Finding `%s'" guess)
1537 (find-file-at-point guess)
3788c735 1538 guess) ; success: return non-nil
213d9a4f 1539 (ffap-highlight t)))
0948761d
KH
1540 ((interactive-p)
1541 (if ffap-at-mouse-fallback
1542 (call-interactively ffap-at-mouse-fallback)
3788c735
KH
1543 (message "No file or url found at mouse click.")
1544 nil)) ; no fallback, return nil
0948761d
KH
1545 ;; failure: return nil
1546 )))
213d9a4f
RS
1547
1548\f
0948761d
KH
1549;;; ffap-other-* commands:
1550;;
1551;; Requested by KPC.
1552
1553;; There could be a real `ffap-noselect' function, but we would need
1554;; at least two new user variables, and there is no w3-fetch-noselect.
1555;; So instead, we just fake it with a slow save-window-excursion.
213d9a4f
RS
1556
1557(defun ffap-other-window nil
0948761d
KH
1558 "Like `ffap', but put buffer in another window.
1559Only intended for interactive use."
213d9a4f
RS
1560 (interactive)
1561 (switch-to-buffer-other-window
1562 (save-window-excursion (call-interactively 'ffap) (current-buffer))))
1563
1564(defun ffap-other-frame nil
0948761d
KH
1565 "Like `ffap', but put buffer in another frame.
1566Only intended for interactive use."
213d9a4f 1567 (interactive)
0948761d
KH
1568 ;; Extra code works around dedicated windows (noted by JENS, 7/96):
1569 (let* ((win (selected-window)) (wdp (window-dedicated-p win)))
1570 (unwind-protect
1571 (progn
1572 (set-window-dedicated-p win nil)
1573 (switch-to-buffer-other-frame
1574 (save-window-excursion
1575 (call-interactively 'ffap)
1576 (current-buffer))))
1577 (set-window-dedicated-p win wdp))))
213d9a4f
RS
1578
1579\f
87e2d039
RS
1580;;; Bug Reporter:
1581
213d9a4f 1582(defun ffap-bug nil
87e2d039
RS
1583 "Submit a bug report for the ffap package."
1584 ;; Important: keep the version string here in synch with that at top
1585 ;; of file! Could use lisp-mnt from Emacs 19, but that would depend
1586 ;; on being able to find the ffap.el source file.
213d9a4f
RS
1587 (interactive)
1588 (require 'reporter)
1589 (let ((reporter-prompt-for-summary-p t))
1590 (reporter-submit-bug-report
87e2d039 1591 "Michelangelo Grigni <mic@mathcs.emory.edu>"
3788c735 1592 "ffap"
87e2d039
RS
1593 (mapcar 'intern (all-completions "ffap-" obarray 'boundp)))))
1594
213d9a4f
RS
1595(fset 'ffap-submit-bug 'ffap-bug) ; another likely name
1596
1597\f
87e2d039 1598;;; Hooks for Gnus, VM, Rmail:
213d9a4f 1599;;
87e2d039
RS
1600;; If you do not like these bindings, write versions with whatever
1601;; bindings you would prefer.
213d9a4f 1602
87e2d039
RS
1603(defun ffap-ro-mode-hook nil
1604 "Bind `ffap-next' and `ffap-menu' to M-l and M-m, resp."
1605 (local-set-key "\M-l" 'ffap-next)
1606 (local-set-key "\M-m" 'ffap-menu)
1607 )
213d9a4f 1608
87e2d039
RS
1609(defun ffap-gnus-hook nil
1610 "Bind `ffap-gnus-next' and `ffap-gnus-menu' to M-l and M-m, resp."
1611 (set (make-local-variable 'ffap-foo-at-bar-prefix) "news") ; message-id's
1612 ;; Note "l", "L", "m", "M" are taken:
1613 (local-set-key "\M-l" 'ffap-gnus-next)
1614 (local-set-key "\M-m" 'ffap-gnus-menu))
213d9a4f 1615
87e2d039
RS
1616(defun ffap-gnus-wrapper (form) ; used by both commands below
1617 (and (eq (current-buffer) (get-buffer gnus-summary-buffer))
1618 (gnus-summary-select-article)) ; get article of current line
1619 ;; Preserve selected buffer, but do not do save-window-excursion,
1620 ;; since we want to see any window created by the form. Temporarily
1621 ;; select the article buffer, so we can see any point movement.
1622 (let ((sb (window-buffer (selected-window))))
1623 (gnus-configure-windows 'article)
1624 (pop-to-buffer gnus-article-buffer)
1625 (widen)
1626 ;; Skip headers for ffap-gnus-next (which will wrap around)
1627 (if (eq (point) (point-min)) (search-forward "\n\n" nil t))
1628 (unwind-protect
1629 (eval form)
1630 (pop-to-buffer sb))))
1631
1632(defun ffap-gnus-next nil
1633 "Run `ffap-next' in the gnus article buffer."
1634 (interactive) (ffap-gnus-wrapper '(ffap-next nil t)))
1635
1636(defun ffap-gnus-menu nil
1637 "Run `ffap-menu' in the gnus article buffer."
1638 (interactive) (ffap-gnus-wrapper '(ffap-menu)))
1639
1640\f
61154252
RS
1641(defcustom dired-at-point-require-prefix nil
1642 "*If set, reverses the prefix argument to `dired-at-point'.
1643This is nil so neophytes notice ffap. Experts may prefer to disable
1644ffap most of the time."
1645 :type 'boolean
1646 :group 'ffap
1647 :version "20.3")
1648
1649;;;###autoload
1650(defun dired-at-point (&optional filename)
1651 "Start Dired, defaulting to file at point. See `ffap'."
1652 (interactive)
1653 (if (and (interactive-p)
1654 (if dired-at-point-require-prefix
1655 (not current-prefix-arg)
1656 current-prefix-arg))
1657 (let (current-prefix-arg) ; already interpreted
b8e2ae05 1658 (call-interactively 'dired))
61154252
RS
1659 (or filename (setq filename (dired-at-point-prompter)))
1660 (cond
1661 ((ffap-url-p filename)
1662 (funcall ffap-url-fetcher filename))
1663 ((and ffap-dired-wildcards
1664 (string-match ffap-dired-wildcards filename))
1665 (dired filename))
1666 ((file-exists-p filename)
1667 (if (file-directory-p filename)
1668 (dired (expand-file-name filename))
1669 (dired (concat (expand-file-name filename) "*"))))
3fa86f26
JB
1670 ((and (file-writable-p (file-name-directory filename))
1671 (y-or-n-p "Directory does not exist, create it? "))
61154252
RS
1672 (make-directory filename)
1673 (dired filename))
1674 ((error "No such file or directory `%s'" filename)))))
1675
1676(defun dired-at-point-prompter (&optional guess)
1677 ;; Does guess and prompt step for find-file-at-point.
1678 ;; Extra complication for the temporary highlighting.
1679 (unwind-protect
1680 (ffap-read-file-or-url
1681 (if ffap-url-regexp "Dired file or URL: " "Dired file: ")
1682 (prog1
1683 (setq guess (or guess (ffap-guesser)))
1684 (and guess (ffap-highlight))
1685 ))
1686 (ffap-highlight t)))
1687\f
0948761d 1688;;; Offer default global bindings (`ffap-bindings'):
87e2d039
RS
1689
1690(defvar ffap-bindings
3788c735
KH
1691 '(
1692 (global-set-key [S-mouse-3] 'ffap-at-mouse)
1693 (global-set-key [C-S-mouse-3] 'ffap-menu)
1694 (global-set-key "\C-x\C-f" 'find-file-at-point)
1695 (global-set-key "\C-x4f" 'ffap-other-window)
1696 (global-set-key "\C-x5f" 'ffap-other-frame)
3d729a9a 1697 (global-set-key "\C-xd" 'dired-at-point)
3788c735
KH
1698 (add-hook 'gnus-summary-mode-hook 'ffap-gnus-hook)
1699 (add-hook 'gnus-article-mode-hook 'ffap-gnus-hook)
1700 (add-hook 'vm-mode-hook 'ffap-ro-mode-hook)
1701 (add-hook 'rmail-mode-hook 'ffap-ro-mode-hook)
1702 ;; (setq dired-x-hands-off-my-keys t) ; the default
1703 )
1704 "List of binding forms evaluated by function `ffap-bindings'.
148b5960 1705A reasonable ffap installation needs just this one line:
87e2d039 1706 (ffap-bindings)
0948761d 1707Of course if you do not like these bindings, just roll your own!")
87e2d039 1708
25050dab 1709;;;###autoload
87e2d039
RS
1710(defun ffap-bindings nil
1711 "Evaluate the forms in variable `ffap-bindings'."
25050dab 1712 (interactive)
87e2d039
RS
1713 (eval (cons 'progn ffap-bindings)))
1714
87e2d039 1715\f
ab5796a9
MB
1716
1717;;; arch-tag: 9dd3e88a-5dec-4607-bd57-60ae9ede8ebc
213d9a4f 1718;;; ffap.el ends here