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