Fix typos and formatting.
[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))))))
7e1626fb
EZ
1188 ;; Try all parent directories by deleting the trailing directory
1189 ;; name until existing directory is found or name stops changing
1190 ((let ((dir name))
1191 (while (and dir
1192 (not (ffap-file-exists-string dir))
1193 (not (equal dir (setq dir (file-name-directory
1194 (directory-file-name dir)))))))
1195 (ffap-file-exists-string dir)))
213d9a4f 1196 )
3c2c6be2 1197 (set-match-data data))))
213d9a4f 1198\f
0948761d 1199;;; Prompting (`ffap-read-file-or-url'):
213d9a4f 1200;;
87e2d039
RS
1201;; We want to complete filenames as in read-file-name, but also url's
1202;; which read-file-name-internal would truncate at the "//" string.
1203;; The solution here is to replace read-file-name-internal with
1204;; `ffap-read-file-or-url-internal', which checks the minibuffer
1205;; contents before attempting to complete filenames.
213d9a4f
RS
1206
1207(defun ffap-read-file-or-url (prompt guess)
87e2d039 1208 "Read file or url from minibuffer, with PROMPT and initial GUESS."
213d9a4f 1209 (or guess (setq guess default-directory))
87e2d039 1210 (let (dir)
213d9a4f
RS
1211 ;; Tricky: guess may have or be a local directory, like "w3/w3.elc"
1212 ;; or "w3/" or "../el/ffap.el" or "../../../"
87e2d039 1213 (or (ffap-url-p guess)
213d9a4f
RS
1214 (progn
1215 (or (ffap-file-remote-p guess)
3788c735
KH
1216 (setq guess
1217 (abbreviate-file-name (expand-file-name guess))
1218 ))
213d9a4f 1219 (setq dir (file-name-directory guess))))
a22f0735
RS
1220 (let ((minibuffer-completing-file-name t))
1221 (setq guess
1222 (completing-read
1223 prompt
1224 'ffap-read-file-or-url-internal
1225 dir
1226 nil
7b86ef7d 1227 (if dir (cons guess (length dir)) guess)
a22f0735 1228 (list 'file-name-history))))
87e2d039
RS
1229 ;; Do file substitution like (interactive "F"), suggested by MCOOK.
1230 (or (ffap-url-p guess) (setq guess (substitute-in-file-name guess)))
1231 ;; Should not do it on url's, where $ is a common (VMS?) character.
1232 ;; Note: upcoming url.el package ought to handle this automatically.
1233 guess))
213d9a4f
RS
1234
1235(defun ffap-read-url-internal (string dir action)
87e2d039
RS
1236 "Complete url's from history, treating given string as valid."
1237 (let ((hist (ffap-soft-value "url-global-history-hash-table")))
213d9a4f
RS
1238 (cond
1239 ((not action)
1240 (or (try-completion string hist) string))
1241 ((eq action t)
1242 (or (all-completions string hist) (list string)))
87e2d039
RS
1243 ;; action == lambda, documented where? Tests whether string is a
1244 ;; valid "match". Let us always say yes.
1245 (t t))))
213d9a4f
RS
1246
1247(defun ffap-read-file-or-url-internal (string dir action)
d4021fd9
GM
1248 (unless dir
1249 (setq dir default-directory))
1250 (unless string
1251 (setq string default-directory))
213d9a4f
RS
1252 (if (ffap-url-p string)
1253 (ffap-read-url-internal string dir action)
1254 (read-file-name-internal string dir action)))
1255
87e2d039
RS
1256;; The rest of this page is just to work with package complete.el.
1257;; This code assumes that you load ffap.el after complete.el.
1258;;
1259;; We must inform complete about whether our completion function
8daa9f3d 1260;; will do filename style completion.
87e2d039
RS
1261
1262(defun ffap-complete-as-file-p nil
1263 ;; Will `minibuffer-completion-table' complete the minibuffer
1264 ;; contents as a filename? Assumes the minibuffer is current.
1265 ;; Note: t and non-nil mean somewhat different reasons.
1266 (if (eq minibuffer-completion-table 'ffap-read-file-or-url-internal)
1267 (not (ffap-url-p (buffer-string))) ; t
9925c419 1268 (and minibuffer-completing-file-name '(t)))) ;list
87e2d039 1269
213d9a4f
RS
1270(and
1271 (featurep 'complete)
87e2d039
RS
1272 (if (boundp 'PC-completion-as-file-name-predicate)
1273 ;; modern version of complete.el, just set the variable:
8daa9f3d 1274 (setq PC-completion-as-file-name-predicate 'ffap-complete-as-file-p)))
213d9a4f
RS
1275
1276\f
0948761d 1277;;; Highlighting (`ffap-highlight'):
213d9a4f
RS
1278;;
1279;; Based on overlay highlighting in Emacs 19.28 isearch.el.
1280
33514810 1281(defvar ffap-highlight t
213d9a4f
RS
1282 "If non-nil, ffap highlights the current buffer substring.")
1283
0948761d
KH
1284(defvar ffap-highlight-overlay nil
1285 "Overlay used by `ffap-highlight'.")
213d9a4f
RS
1286
1287(defun ffap-highlight (&optional remove)
87e2d039
RS
1288 "If `ffap-highlight' is set, highlight the guess in this buffer.
1289That is, the last buffer substring found by `ffap-string-at-point'.
213d9a4f 1290Optional argument REMOVE means to remove any such highlighting.
87e2d039 1291Uses the face `ffap' if it is defined, or else `highlight'."
213d9a4f 1292 (cond
0948761d
KH
1293 (remove
1294 (and ffap-highlight-overlay
3788c735
KH
1295 (delete-overlay ffap-highlight-overlay))
1296 )
213d9a4f 1297 ((not ffap-highlight) nil)
87e2d039 1298 (ffap-highlight-overlay
3788c735
KH
1299 (move-overlay
1300 ffap-highlight-overlay
1301 (car ffap-string-at-point-region)
1302 (nth 1 ffap-string-at-point-region)
1303 (current-buffer)))
213d9a4f 1304 (t
0948761d 1305 (setq ffap-highlight-overlay
3788c735
KH
1306 (apply 'make-overlay ffap-string-at-point-region))
1307 (overlay-put ffap-highlight-overlay 'face
e5eee690 1308 (if (facep 'ffap) 'ffap 'highlight)))))
87e2d039 1309
213d9a4f 1310\f
3788c735 1311;;; Main Entrance (`find-file-at-point' == `ffap'):
213d9a4f
RS
1312
1313(defun ffap-guesser nil
0948761d 1314 "Return file or URL or nil, guessed from text around point."
213d9a4f
RS
1315 (or (and ffap-url-regexp
1316 (ffap-fixup-url (or (ffap-url-at-point)
1317 (ffap-gopher-at-point))))
1318 (ffap-file-at-point) ; may yield url!
1319 (ffap-fixup-machine (ffap-machine-at-point))))
1320
1321(defun ffap-prompter (&optional guess)
1322 ;; Does guess and prompt step for find-file-at-point.
87e2d039 1323 ;; Extra complication for the temporary highlighting.
213d9a4f 1324 (unwind-protect
3788c735
KH
1325 ;; This catch will let ffap-alist entries do their own prompting
1326 ;; and then maybe skip over this prompt (ff-paths, for example).
1327 (catch 'ffap-prompter
1328 (ffap-read-file-or-url
1329 (if ffap-url-regexp "Find file or URL: " "Find file: ")
1330 (prog1
1331 (setq guess (or guess (ffap-guesser))) ; using ffap-alist here
1332 (and guess (ffap-highlight))
1333 )))
213d9a4f
RS
1334 (ffap-highlight t)))
1335
1336;;;###autoload
1337(defun find-file-at-point (&optional filename)
0948761d
KH
1338 "Find FILENAME, guessing a default from text around point.
1339If `ffap-url-regexp' is not nil, the FILENAME may also be an URL.
1340With a prefix, this command behaves exactly like `ffap-file-finder'.
213d9a4f 1341If `ffap-require-prefix' is set, the prefix meaning is reversed.
0948761d
KH
1342See also the variables `ffap-dired-wildcards', `ffap-newfile-prompt',
1343and the functions `ffap-file-at-point' and `ffap-url-at-point'.
213d9a4f 1344
87e2d039 1345See <ftp://ftp.mathcs.emory.edu/pub/mic/emacs/> for latest version."
213d9a4f
RS
1346 (interactive)
1347 (if (and (interactive-p)
1348 (if ffap-require-prefix (not current-prefix-arg)
1349 current-prefix-arg))
1350 ;; Do exactly the ffap-file-finder command, even the prompting:
87e2d039
RS
1351 (let (current-prefix-arg) ; we already interpreted it
1352 (call-interactively ffap-file-finder))
213d9a4f
RS
1353 (or filename (setq filename (ffap-prompter)))
1354 (cond
1355 ((ffap-url-p filename)
87e2d039
RS
1356 (let (current-prefix-arg) ; w3 2.3.25 bug, reported by KPC
1357 (funcall ffap-url-fetcher filename)))
213d9a4f 1358 ;; This junk more properly belongs in a modified ffap-file-finder:
87e2d039
RS
1359 ((and ffap-dired-wildcards
1360 (string-match ffap-dired-wildcards filename))
213d9a4f
RS
1361 (dired filename))
1362 ((or (not ffap-newfile-prompt)
1363 (file-exists-p filename)
1364 (y-or-n-p "File does not exist, create buffer? "))
1365 (funcall ffap-file-finder
1366 ;; expand-file-name fixes "~/~/.emacs" bug sent by CHUCKR.
1367 (expand-file-name filename)))
1368 ;; User does not want to find a non-existent file:
1369 ((signal 'file-error (list "Opening file buffer"
1370 "no such file or directory"
1371 filename))))))
1372
0948761d 1373;; Shortcut: allow {M-x ffap} rather than {M-x find-file-at-point}.
22ac7ca0
MR
1374;;;###autoload
1375(defalias 'ffap 'find-file-at-point)
1376
213d9a4f 1377\f
0948761d 1378;;; Menu support (`ffap-menu'):
213d9a4f
RS
1379
1380(defvar ffap-menu-regexp nil
87e2d039 1381 "*If non-nil, overrides `ffap-next-regexp' during `ffap-menu'.
213d9a4f 1382Make this more restrictive for faster menu building.
0948761d 1383For example, try \":/\" for URL (and some ftp) references.")
213d9a4f
RS
1384
1385(defvar ffap-menu-alist nil
87e2d039 1386 "Buffer local cache of menu presented by `ffap-menu'.")
213d9a4f
RS
1387(make-variable-buffer-local 'ffap-menu-alist)
1388
87e2d039 1389(defvar ffap-menu-text-plist
3788c735 1390 (cond
33514810
EZ
1391 ((display-mouse-p) '(face bold mouse-face highlight)) ; keymap <mousy-map>
1392 (t nil))
87e2d039
RS
1393 "Text properties applied to strings found by `ffap-menu-rescan'.
1394These properties may be used to fontify the menu references.")
1395
213d9a4f
RS
1396;;;###autoload
1397(defun ffap-menu (&optional rescan)
87e2d039
RS
1398 "Put up a menu of files and urls mentioned in this buffer.
1399Then set mark, jump to choice, and try to fetch it. The menu is
1400cached in `ffap-menu-alist', and rebuilt by `ffap-menu-rescan'.
1401The optional RESCAN argument \(a prefix, interactively\) forces
1402a rebuild. Searches with `ffap-menu-regexp'."
213d9a4f
RS
1403 (interactive "P")
1404 ;; (require 'imenu) -- no longer used, but roughly emulated
1405 (if (or (not ffap-menu-alist) rescan
1406 ;; or if the first entry is wrong:
1407 (and ffap-menu-alist
1408 (let ((first (car ffap-menu-alist)))
1409 (save-excursion
1410 (goto-char (cdr first))
1411 (not (equal (car first) (ffap-guesser)))))))
1412 (ffap-menu-rescan))
1413 ;; Tail recursive:
1414 (ffap-menu-ask
1415 (if ffap-url-regexp "Find file or URL" "Find file")
1416 (cons (cons "*Rescan Buffer*" -1) ffap-menu-alist)
1417 'ffap-menu-cont))
1418
1419(defun ffap-menu-cont (choice) ; continuation of ffap-menu
1420 (if (< (cdr choice) 0)
1421 (ffap-menu t) ; *Rescan*
1422 (push-mark)
1423 (goto-char (cdr choice))
1424 ;; Momentary highlight:
1425 (unwind-protect
1426 (progn
1427 (and ffap-highlight (ffap-guesser) (ffap-highlight))
1428 (sit-for 0) ; display
1429 (find-file-at-point (car choice)))
1430 (ffap-highlight t))))
1431
1432(defun ffap-menu-ask (title alist cont)
1433 "Prompt from a menu of choices, and then apply some action.
0948761d 1434Arguments are TITLE, ALIST, and CONT \(a continuation function\).
213d9a4f
RS
1435This uses either a menu or the minibuffer depending on invocation.
1436The TITLE string is used as either the prompt or menu title.
ee79ced8 1437Each ALIST entry looks like (STRING . DATA) and defines one choice.
0948761d
KH
1438Function CONT is applied to the entry chosen by the user."
1439 ;; Note: this function is used with a different continuation
1440 ;; by the ffap-url add-on package.
1441 ;; Could try rewriting to use easymenu.el or lmenu.el.
1442 (let (choice)
1443 (cond
1444 ;; Emacs mouse:
1445 ((and (fboundp 'x-popup-menu) (ffap-mouse-event))
1446 (setq choice
1447 (x-popup-menu
1448 t
1449 (list "" (cons title
1450 (mapcar (function (lambda (i) (cons (car i) i)))
1451 alist))))))
1452 ;; minibuffer with completion buffer:
1453 (t
1454 (let ((minibuffer-setup-hook 'minibuffer-completion-help))
1455 ;; Bug: prompting may assume unique strings, no "".
1456 (setq choice
1457 (completing-read
1458 (format "%s (default %s): " title (car (car alist)))
1459 alist nil t
1460 ;; (cons (car (car alist)) 0)
1461 nil)))
1462 (sit-for 0) ; redraw original screen
1463 ;; Convert string to its entry, or else the default:
1464 (setq choice (or (assoc choice alist) (car alist))))
1465 )
1466 (if choice
1467 (funcall cont choice)
1468 (message "No choice made!") ; possible with menus
1469 nil)))
213d9a4f
RS
1470
1471(defun ffap-menu-rescan nil
87e2d039
RS
1472 "Search buffer for `ffap-menu-regexp' to build `ffap-menu-alist'.
1473Applies `ffap-menu-text-plist' text properties at all matches."
213d9a4f
RS
1474 (interactive)
1475 (let ((ffap-next-regexp (or ffap-menu-regexp ffap-next-regexp))
0948761d
KH
1476 (range (- (point-max) (point-min)))
1477 (mod (buffer-modified-p)) ; was buffer modified?
87e2d039 1478 buffer-read-only ; to set text-properties
0948761d 1479 item
87e2d039
RS
1480 ;; Avoid repeated searches of the *mode-alist:
1481 (major-mode (if (assq major-mode ffap-string-at-point-mode-alist)
1482 major-mode
0948761d 1483 'file)))
213d9a4f 1484 (setq ffap-menu-alist nil)
0948761d
KH
1485 (unwind-protect
1486 (save-excursion
1487 (goto-char (point-min))
1488 (while (setq item (ffap-next-guess))
1489 (setq ffap-menu-alist (cons (cons item (point)) ffap-menu-alist))
1490 (add-text-properties (car ffap-string-at-point-region) (point)
1491 ffap-menu-text-plist)
1492 (message "Scanning...%2d%% <%s>"
1493 (/ (* 100 (- (point) (point-min))) range) item)))
1494 (or mod (set-buffer-modified-p nil))))
213d9a4f
RS
1495 (message "Scanning...done")
1496 ;; Remove duplicates.
1497 (setq ffap-menu-alist ; sort by item
1498 (sort ffap-menu-alist
1499 (function
1500 (lambda (a b) (string-lessp (car a) (car b))))))
0948761d 1501 (let ((ptr ffap-menu-alist)) ; remove duplicates
213d9a4f
RS
1502 (while (cdr ptr)
1503 (if (equal (car (car ptr)) (car (car (cdr ptr))))
1504 (setcdr ptr (cdr (cdr ptr)))
1505 (setq ptr (cdr ptr)))))
1506 (setq ffap-menu-alist ; sort by position
1507 (sort ffap-menu-alist
1508 (function
1509 (lambda (a b) (< (cdr a) (cdr b)))))))
1510
1511\f
0948761d 1512;;; Mouse Support (`ffap-at-mouse'):
213d9a4f 1513;;
87e2d039 1514;; See the suggested binding in ffap-bindings (near eof).
213d9a4f 1515
0948761d
KH
1516(defvar ffap-at-mouse-fallback nil ; ffap-menu? too time-consuming
1517 "Command invoked by `ffap-at-mouse' if nothing found at click, or nil.
1518Ignored when `ffap-at-mouse' is called programmatically.")
213d9a4f
RS
1519(put 'ffap-at-mouse-fallback 'risky-local-variable t)
1520
0948761d 1521;;;###autoload
213d9a4f 1522(defun ffap-at-mouse (e)
0948761d 1523 "Find file or url guessed from text around mouse click.
3788c735
KH
1524Interactively, calls `ffap-at-mouse-fallback' if no guess is found.
1525Return value:
1526 * if a guess string is found, return it (after finding it)
1527 * if the fallback is called, return whatever it returns
1528 * otherwise, nil"
213d9a4f
RS
1529 (interactive "e")
1530 (let ((guess
1531 ;; Maybe less surprising without the save-excursion?
1532 (save-excursion
1533 (mouse-set-point e)
0948761d
KH
1534 ;; Would prefer to do nothing unless click was *on* text. How
1535 ;; to tell that the click was beyond the end of current line?
213d9a4f
RS
1536 (ffap-guesser))))
1537 (cond
1538 (guess
0948761d 1539 (set-buffer (ffap-event-buffer e))
213d9a4f
RS
1540 (ffap-highlight)
1541 (unwind-protect
1542 (progn
1543 (sit-for 0) ; display
0948761d
KH
1544 (message "Finding `%s'" guess)
1545 (find-file-at-point guess)
3788c735 1546 guess) ; success: return non-nil
213d9a4f 1547 (ffap-highlight t)))
0948761d
KH
1548 ((interactive-p)
1549 (if ffap-at-mouse-fallback
1550 (call-interactively ffap-at-mouse-fallback)
3788c735
KH
1551 (message "No file or url found at mouse click.")
1552 nil)) ; no fallback, return nil
0948761d
KH
1553 ;; failure: return nil
1554 )))
213d9a4f
RS
1555
1556\f
0948761d
KH
1557;;; ffap-other-* commands:
1558;;
1559;; Requested by KPC.
1560
1561;; There could be a real `ffap-noselect' function, but we would need
1562;; at least two new user variables, and there is no w3-fetch-noselect.
1563;; So instead, we just fake it with a slow save-window-excursion.
213d9a4f
RS
1564
1565(defun ffap-other-window nil
0948761d
KH
1566 "Like `ffap', but put buffer in another window.
1567Only intended for interactive use."
213d9a4f
RS
1568 (interactive)
1569 (switch-to-buffer-other-window
1570 (save-window-excursion (call-interactively 'ffap) (current-buffer))))
1571
1572(defun ffap-other-frame nil
0948761d
KH
1573 "Like `ffap', but put buffer in another frame.
1574Only intended for interactive use."
213d9a4f 1575 (interactive)
0948761d
KH
1576 ;; Extra code works around dedicated windows (noted by JENS, 7/96):
1577 (let* ((win (selected-window)) (wdp (window-dedicated-p win)))
1578 (unwind-protect
1579 (progn
1580 (set-window-dedicated-p win nil)
1581 (switch-to-buffer-other-frame
1582 (save-window-excursion
1583 (call-interactively 'ffap)
1584 (current-buffer))))
1585 (set-window-dedicated-p win wdp))))
213d9a4f
RS
1586
1587\f
87e2d039
RS
1588;;; Bug Reporter:
1589
213d9a4f 1590(defun ffap-bug nil
87e2d039
RS
1591 "Submit a bug report for the ffap package."
1592 ;; Important: keep the version string here in synch with that at top
1593 ;; of file! Could use lisp-mnt from Emacs 19, but that would depend
1594 ;; on being able to find the ffap.el source file.
213d9a4f
RS
1595 (interactive)
1596 (require 'reporter)
1597 (let ((reporter-prompt-for-summary-p t))
1598 (reporter-submit-bug-report
87e2d039 1599 "Michelangelo Grigni <mic@mathcs.emory.edu>"
3788c735 1600 "ffap"
87e2d039
RS
1601 (mapcar 'intern (all-completions "ffap-" obarray 'boundp)))))
1602
213d9a4f
RS
1603(fset 'ffap-submit-bug 'ffap-bug) ; another likely name
1604
1605\f
87e2d039 1606;;; Hooks for Gnus, VM, Rmail:
213d9a4f 1607;;
87e2d039
RS
1608;; If you do not like these bindings, write versions with whatever
1609;; bindings you would prefer.
213d9a4f 1610
87e2d039
RS
1611(defun ffap-ro-mode-hook nil
1612 "Bind `ffap-next' and `ffap-menu' to M-l and M-m, resp."
1613 (local-set-key "\M-l" 'ffap-next)
1614 (local-set-key "\M-m" 'ffap-menu)
1615 )
213d9a4f 1616
87e2d039
RS
1617(defun ffap-gnus-hook nil
1618 "Bind `ffap-gnus-next' and `ffap-gnus-menu' to M-l and M-m, resp."
1619 (set (make-local-variable 'ffap-foo-at-bar-prefix) "news") ; message-id's
1620 ;; Note "l", "L", "m", "M" are taken:
1621 (local-set-key "\M-l" 'ffap-gnus-next)
1622 (local-set-key "\M-m" 'ffap-gnus-menu))
213d9a4f 1623
87e2d039
RS
1624(defun ffap-gnus-wrapper (form) ; used by both commands below
1625 (and (eq (current-buffer) (get-buffer gnus-summary-buffer))
1626 (gnus-summary-select-article)) ; get article of current line
1627 ;; Preserve selected buffer, but do not do save-window-excursion,
1628 ;; since we want to see any window created by the form. Temporarily
1629 ;; select the article buffer, so we can see any point movement.
1630 (let ((sb (window-buffer (selected-window))))
1631 (gnus-configure-windows 'article)
1632 (pop-to-buffer gnus-article-buffer)
1633 (widen)
1634 ;; Skip headers for ffap-gnus-next (which will wrap around)
1635 (if (eq (point) (point-min)) (search-forward "\n\n" nil t))
1636 (unwind-protect
1637 (eval form)
1638 (pop-to-buffer sb))))
1639
1640(defun ffap-gnus-next nil
1641 "Run `ffap-next' in the gnus article buffer."
1642 (interactive) (ffap-gnus-wrapper '(ffap-next nil t)))
1643
1644(defun ffap-gnus-menu nil
1645 "Run `ffap-menu' in the gnus article buffer."
1646 (interactive) (ffap-gnus-wrapper '(ffap-menu)))
1647
1648\f
61154252
RS
1649(defcustom dired-at-point-require-prefix nil
1650 "*If set, reverses the prefix argument to `dired-at-point'.
1651This is nil so neophytes notice ffap. Experts may prefer to disable
1652ffap most of the time."
1653 :type 'boolean
1654 :group 'ffap
1655 :version "20.3")
1656
1657;;;###autoload
1658(defun dired-at-point (&optional filename)
1659 "Start Dired, defaulting to file at point. See `ffap'."
1660 (interactive)
1661 (if (and (interactive-p)
1662 (if dired-at-point-require-prefix
1663 (not current-prefix-arg)
1664 current-prefix-arg))
1665 (let (current-prefix-arg) ; already interpreted
b8e2ae05 1666 (call-interactively 'dired))
61154252
RS
1667 (or filename (setq filename (dired-at-point-prompter)))
1668 (cond
1669 ((ffap-url-p filename)
1670 (funcall ffap-url-fetcher filename))
1671 ((and ffap-dired-wildcards
1672 (string-match ffap-dired-wildcards filename))
1673 (dired filename))
1674 ((file-exists-p filename)
1675 (if (file-directory-p filename)
1676 (dired (expand-file-name filename))
1677 (dired (concat (expand-file-name filename) "*"))))
3fa86f26
JB
1678 ((and (file-writable-p (file-name-directory filename))
1679 (y-or-n-p "Directory does not exist, create it? "))
61154252
RS
1680 (make-directory filename)
1681 (dired filename))
1682 ((error "No such file or directory `%s'" filename)))))
1683
1684(defun dired-at-point-prompter (&optional guess)
1685 ;; Does guess and prompt step for find-file-at-point.
1686 ;; Extra complication for the temporary highlighting.
1687 (unwind-protect
1688 (ffap-read-file-or-url
1689 (if ffap-url-regexp "Dired file or URL: " "Dired file: ")
1690 (prog1
1691 (setq guess (or guess (ffap-guesser)))
1692 (and guess (ffap-highlight))
1693 ))
1694 (ffap-highlight t)))
1695\f
0948761d 1696;;; Offer default global bindings (`ffap-bindings'):
87e2d039
RS
1697
1698(defvar ffap-bindings
3788c735
KH
1699 '(
1700 (global-set-key [S-mouse-3] 'ffap-at-mouse)
1701 (global-set-key [C-S-mouse-3] 'ffap-menu)
1702 (global-set-key "\C-x\C-f" 'find-file-at-point)
1703 (global-set-key "\C-x4f" 'ffap-other-window)
1704 (global-set-key "\C-x5f" 'ffap-other-frame)
3d729a9a 1705 (global-set-key "\C-xd" 'dired-at-point)
3788c735
KH
1706 (add-hook 'gnus-summary-mode-hook 'ffap-gnus-hook)
1707 (add-hook 'gnus-article-mode-hook 'ffap-gnus-hook)
1708 (add-hook 'vm-mode-hook 'ffap-ro-mode-hook)
1709 (add-hook 'rmail-mode-hook 'ffap-ro-mode-hook)
1710 ;; (setq dired-x-hands-off-my-keys t) ; the default
1711 )
1712 "List of binding forms evaluated by function `ffap-bindings'.
148b5960 1713A reasonable ffap installation needs just this one line:
87e2d039 1714 (ffap-bindings)
0948761d 1715Of course if you do not like these bindings, just roll your own!")
87e2d039 1716
25050dab 1717;;;###autoload
87e2d039
RS
1718(defun ffap-bindings nil
1719 "Evaluate the forms in variable `ffap-bindings'."
25050dab 1720 (interactive)
87e2d039
RS
1721 (eval (cons 'progn ffap-bindings)))
1722
87e2d039 1723\f
ab5796a9
MB
1724
1725;;; arch-tag: 9dd3e88a-5dec-4607-bd57-60ae9ede8ebc
213d9a4f 1726;;; ffap.el ends here