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