(occur-read-primary-args): Pass default to read-from-minibuffer.
[bpt/emacs.git] / lisp / find-file.el
CommitLineData
2be55c9c
RS
1;;; find-file.el --- find a file corresponding to this one given a pattern
2
b4686a1c 3;; Author: Henry Guillaume <henri@tibco.com, henry@c032.aone.net.au>
5027054f 4;; Maintainer: FSF
2be55c9c
RS
5;; Keywords: c, matching, tools
6
e641d300 7;; Copyright (C) 1994, 1995, 2002, 2003 Free Software Foundation, Inc.
2be55c9c 8
b578f267
EN
9;; This file is part of GNU Emacs.
10
11;; GNU Emacs is free software; you can redistribute it and/or modify
12;; it under the terms of the GNU General Public License as published by
13;; the Free Software Foundation; either version 2, or (at your option)
14;; any later version.
15
16;; GNU Emacs is distributed in the hope that it will be useful,
17;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19;; GNU General Public License for more details.
20
21;; You should have received a copy of the GNU General Public License
22;; along with GNU Emacs; see the file COPYING. If not, write to the
23;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24;; Boston, MA 02111-1307, USA.
2be55c9c
RS
25
26;;; Commentary:
27
28;; PURPOSE:
98d775e5
DL
29;; This package features a function called ff-find-other-file, which performs
30;; the following function:
2be55c9c
RS
31;;
32;; When in a .c file, find the first corresponding .h file in a set
33;; of directories and display it, and vice-versa from the .h file.
34;;
35;; Many people maintain their include file in a directory separate to their
36;; src directory, and very often you may be editing a file and have a need to
37;; visit the "other file". This package searches through a set of directories
38;; to find that file.
39;;
40;; THE "OTHER FILE", or "corresponding file", generally has the same basename,
98d775e5 41;; and just has a different extension as described by the ff-other-file-alist
2be55c9c
RS
42;; variable:
43;;
44;; '(("\\.cc$" (".hh" ".h"))
45;; ("\\.hh$" (".cc" ".C" ".CC" ".cxx" ".cpp")))
46;;
47;; If the current file has a .cc extension, ff-find-other-file will attempt
48;; to look for a .hh file, and then a .h file in some directory as described
49;; below. The mechanism here is to replace the matched part of the original
50;; filename with each of the corresponding extensions in turn.
51;;
52;; Alternatively, there are situations where the filename of the other file
53;; cannot be determined easily with regexps. For example, a .c file may
54;; have two corresponding .h files, for its public and private parts, or
55;; the filename for the .c file contains part of the pathname of the .h
56;; file, as between src/fooZap.cc and include/FOO/zap.hh. In that case, the
57;; format above can be changed to include a function to be called when the
58;; current file matches the regexp:
59;;
60;; '(("\\.cc$" cc-function)
61;; ("\\.hh$" hh-function))
62;;
98d775e5
DL
63;; These functions must return a list consisting of the possible names of the
64;; corresponding file, with or without path. There is no real need for more
2be55c9c
RS
65;; than one function, and one could imagine the following value for cc-other-
66;; file-alist:
67;;
68;; (setq cc-other-file-alist
69;; '(("\\.cc$" ff-cc-hh-converter)
70;; ("\\.hh$" ff-cc-hh-converter)
71;; ("\\.c$" (".h"))
72;; ("\\.h$" (".c" ".cc" ".C" ".CC" ".cxx" ".cpp"))))
71296446 73;;
2be55c9c 74;; ff-cc-hh-converter is included at the end of this file as a reference.
71296446 75;;
2be55c9c
RS
76;; SEARCHING is carried out in a set of directories specified by the
77;; ff-search-directories variable:
78;;
79;; ("." "../../src" "../include/*" "/usr/local/*/src/*" "$PROJECT/src")
80;;
81;; This means that the corresponding file will be searched for first in
82;; the current directory, then in ../../src, then in one of the directories
83;; under ../include, and so on. The star is _not_ a general wildcard
84;; character: it just indicates that the subdirectories of this directory
85;; must each be searched in turn. Environment variables will be expanded in
86;; the ff-search-directories variable.
87;;
88;; If the point is on a #include line, the file to be #included is searched
89;; for in the same manner. This can be disabled with the ff-ignore-include
90;; variable, or by calling ff-get-other-file instead of ff-find-other-file.
91;;
92;; If the file was not found, ff-find-other-file will prompt you for where
93;; to create the new "corresponding file" (defaults to the current directory),
98d775e5 94;; unless the variable ff-always-try-to-create is set to nil.
2be55c9c 95;;
98d775e5
DL
96;; GIVEN AN ARGUMENT (with the ^U prefix), ff-find-other-file will get the
97;; other file in another (the other?) window (see find-file-other-window and
98;; switch-to-buffer-other-window). This can be set on a more permanent basis
99;; by setting ff-always-in-other-window to t in which case the ^U prefix will
2be55c9c
RS
100;; do the opposite of what was described above.
101;;
102;; THERE ARE FIVE AVAILABLE HOOKS, called in this order if non-nil:
103;;
03741cc5
SM
104;; - ff-pre-find-hook - called before the search for the other file starts
105;; - ff-not-found-hook - called when the other file could not be found
106;; - ff-pre-load-hook - called just before the other file is 'loaded'
107;; - ff-file-created-hook - called when the other file is created
108;; - ff-post-load-hook - called just after the other file is 'loaded'
2be55c9c 109;;
03741cc5 110;; The *load-hook allow you to place point where you want it in the other
98d775e5 111;; file.
2be55c9c
RS
112
113;; CREDITS:
114;; Many thanks go to TUSC Computer Systems Pty Ltd for providing an environ-
115;; ment that made the development of this package possible.
116;;
117;; Many thanks also go to all those who provided valuable feedback throughout
118;; the development of this package:
119;; Rolf Ebert in particular, Fritz Knabe, Heddy Boubaker, Sebastian Kremer,
98d775e5 120;; Vasco Lopes Paulo, Mark A. Plaksin, Robert Lang, Trevor West, Kevin
17feeeb2 121;; Pereira, Benedict Lofstedt & Justin Vallon.
2be55c9c 122
17feeeb2 123;;; Code:
2be55c9c
RS
124;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
125;; User definable variables:
126
14d4446b
SE
127(defgroup ff nil
128 "Find a file corresponding to this one given a pattern."
129 :prefix "ff-"
98d775e5 130 :link '(emacs-commentary-link "find-file")
14d4446b
SE
131 :group 'find-file)
132
03741cc5 133(defcustom ff-pre-find-hook nil
14d4446b
SE
134 "*List of functions to be called before the search for the file starts."
135 :type 'hook
136 :group 'ff)
137
03741cc5 138(defcustom ff-pre-load-hook nil
14d4446b
SE
139 "*List of functions to be called before the other file is loaded."
140 :type 'hook
141 :group 'ff)
142
03741cc5 143(defcustom ff-post-load-hook nil
14d4446b
SE
144 "*List of functions to be called after the other file is loaded."
145 :type 'hook
146 :group 'ff)
147
03741cc5 148(defcustom ff-not-found-hook nil
14d4446b
SE
149 "*List of functions to be called if the other file could not be found."
150 :type 'hook
151 :group 'ff)
152
03741cc5 153(defcustom ff-file-created-hook nil
14d4446b
SE
154 "*List of functions to be called if the other file needs to be created."
155 :type 'hook
156 :group 'ff)
157
158(defcustom ff-case-fold-search nil
8c17509e 159 "*Non-nil means ignore cases in matches (see `case-fold-search').
14d4446b
SE
160If you have extensions in different cases, you will want this to be nil."
161 :type 'boolean
162 :group 'ff)
2be55c9c 163
14d4446b 164(defcustom ff-always-in-other-window nil
8c17509e 165 "*If non-nil, find the corresponding file in another window by default.
14d4446b
SE
166To override this, give an argument to `ff-find-other-file'."
167 :type 'boolean
168 :group 'ff)
2be55c9c 169
14d4446b
SE
170(defcustom ff-ignore-include nil
171 "*If non-nil, ignore `#include' lines."
172 :type 'boolean
173 :group 'ff)
2be55c9c 174
14d4446b
SE
175(defcustom ff-always-try-to-create t
176 "*If non-nil, always attempt to create the other file if it was not found."
177 :type 'boolean
178 :group 'ff)
2be55c9c 179
14d4446b
SE
180(defcustom ff-quiet-mode nil
181 "*If non-nil, trace which directories are being searched."
182 :type 'boolean
183 :group 'ff)
2be55c9c 184
e641d300 185;;;###autoload
98d775e5 186(defvar ff-special-constructs
2be55c9c
RS
187 '(
188 ;; C/C++ include, for NeXTSTEP too
189 ("^\#\\s *\\(include\\|import\\)\\s +[<\"]\\(.*\\)[>\"]" .
190 (lambda ()
191 (setq fname (buffer-substring (match-beginning 2) (match-end 2)))))
2be55c9c 192 )
98d775e5
DL
193 "*A list of regular expressions for `ff-find-file'.
194Specifies how to recognise special constructs such as include files
195etc. and an associated method for extracting the filename from that
196construct.")
2be55c9c 197
f7583fb6 198(defvaralias 'ff-related-file-alist 'ff-other-file-alist)
14d4446b 199(defcustom ff-other-file-alist 'cc-other-file-alist
2be55c9c
RS
200 "*Alist of extensions to find given the current file's extension.
201
202This list should contain the most used extensions before the others,
203since the search algorithm searches sequentially through each
8c17509e
RS
204directory specified in `ff-search-directories'. If a file is not found,
205a new one is created with the first matching extension (`.cc' yields `.hh').
14d4446b
SE
206This alist should be set by the major mode."
207 :type '(choice (repeat (list regexp (choice (repeat string) function)))
208 symbol)
209 :group 'ff)
2be55c9c 210
14d4446b 211(defcustom ff-search-directories 'cc-search-directories
2be55c9c
RS
212 "*List of directories to search for a specific file.
213
8c17509e 214Set by default to `cc-search-directories', expanded at run-time.
2be55c9c
RS
215
216This list is searched through with each extension specified in
8c17509e 217`ff-other-file-alist' that matches this file's extension. So the
2be55c9c
RS
218longer the list, the longer it'll take to realise that a file
219may not exist.
220
98d775e5 221A typical format is
2be55c9c 222
17feeeb2 223 '(\".\" \"/usr/include\" \"$PROJECT/*/include\")
2be55c9c 224
8c17509e 225Environment variables can be inserted between slashes (`/').
2be55c9c 226They will be replaced by their definition. If a variable does
8c17509e 227not exist, it is replaced (silently) with an empty string.
2be55c9c 228
8c17509e
RS
229The stars are *not* wildcards: they are searched for together with
230the preceding slash. The star represents all the subdirectories except
14d4446b
SE
231`..', and each of these subdirectories will be searched in turn."
232 :type '(choice (repeat directory) symbol)
233 :group 'ff)
2be55c9c 234
14d4446b 235(defcustom cc-search-directories
17feeeb2 236 '("." "/usr/include" "/usr/local/include/*")
14d4446b
SE
237 "*See the description of the `ff-search-directories' variable."
238 :type '(repeat directory)
239 :group 'ff)
2be55c9c 240
14d4446b 241(defcustom cc-other-file-alist
2be55c9c
RS
242 '(
243 ("\\.cc$" (".hh" ".h"))
244 ("\\.hh$" (".cc" ".C"))
245
246 ("\\.c$" (".h"))
247 ("\\.h$" (".c" ".cc" ".C" ".CC" ".cxx" ".cpp"))
248
249 ("\\.C$" (".H" ".hh" ".h"))
250 ("\\.H$" (".C" ".CC"))
251
252 ("\\.CC$" (".HH" ".H" ".hh" ".h"))
253 ("\\.HH$" (".CC"))
254
255 ("\\.cxx$" (".hh" ".h"))
256 ("\\.cpp$" (".hh" ".h"))
257 )
258 "*Alist of extensions to find given the current file's extension.
259
260This list should contain the most used extensions before the others,
261since the search algorithm searches sequentially through each directory
8c17509e 262specified in `ff-search-directories'. If a file is not found, a new one
14d4446b
SE
263is created with the first matching extension (`.cc' yields `.hh')."
264 :type '(repeat (list regexp (choice (repeat string) function)))
265 :group 'ff)
2be55c9c 266
14d4446b 267(defcustom modula2-other-file-alist
2be55c9c
RS
268 '(
269 ("\\.mi$" (".md")) ;; Modula-2 module definition
270 ("\\.md$" (".mi")) ;; and implementation.
271 )
14d4446b
SE
272 "*See the description for the `ff-search-directories' variable."
273 :type '(repeat (list regexp (choice (repeat string) function)))
274 :group 'ff)
275
2be55c9c
RS
276
277;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
278;; No user definable variables beyond this point!
279;; ==============================================
280
03741cc5
SM
281(make-variable-buffer-local 'ff-pre-find-hook)
282(make-variable-buffer-local 'ff-pre-load-hook)
283(make-variable-buffer-local 'ff-post-load-hook)
284(make-variable-buffer-local 'ff-not-found-hook)
285(make-variable-buffer-local 'ff-file-created-hook)
2be55c9c
RS
286(make-variable-buffer-local 'ff-case-fold-search)
287(make-variable-buffer-local 'ff-always-in-other-window)
288(make-variable-buffer-local 'ff-ignore-include)
289(make-variable-buffer-local 'ff-quiet-mode)
290(make-variable-buffer-local 'ff-other-file-alist)
291(make-variable-buffer-local 'ff-search-directories)
292
293;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
294;; User entry points
295
296;;;###autoload
297(defun ff-get-other-file (&optional in-other-window)
8c17509e 298 "Find the header or source file corresponding to this file.
98d775e5 299See also the documentation for `ff-find-other-file'.
2be55c9c 300
8c17509e 301If optional IN-OTHER-WINDOW is non-nil, find the file in another window."
2be55c9c
RS
302 (interactive "P")
303 (let ((ignore ff-ignore-include))
304 (setq ff-ignore-include t)
305 (ff-find-the-other-file in-other-window)
306 (setq ff-ignore-include ignore)))
307
0af8ad28 308;;;###autoload
f7583fb6
RS
309(defalias 'ff-find-related-file 'ff-find-other-file)
310
2be55c9c
RS
311;;;###autoload
312(defun ff-find-other-file (&optional in-other-window ignore-include)
8c17509e
RS
313 "Find the header or source file corresponding to this file.
314Being on a `#include' line pulls in that file.
2be55c9c 315
8c17509e
RS
316If optional IN-OTHER-WINDOW is non-nil, find the file in the other window.
317If optional IGNORE-INCLUDE is non-nil, ignore being on `#include' lines.
2be55c9c
RS
318
319Variables of interest include:
320
98d775e5
DL
321 - `ff-case-fold-search'
322 Non-nil means ignore cases in matches (see `case-fold-search').
2be55c9c
RS
323 If you have extensions in different cases, you will want this to be nil.
324
98d775e5 325 - `ff-always-in-other-window'
2be55c9c 326 If non-nil, always open the other file in another window, unless an
98d775e5 327 argument is given to `ff-find-other-file'.
2be55c9c 328
98d775e5 329 - `ff-ignore-include'
2be55c9c
RS
330 If non-nil, ignores #include lines.
331
98d775e5 332 - `ff-always-try-to-create'
2be55c9c
RS
333 If non-nil, always attempt to create the other file if it was not found.
334
98d775e5 335 - `ff-quiet-mode'
2be55c9c
RS
336 If non-nil, traces which directories are being searched.
337
98d775e5
DL
338 - `ff-special-constructs'
339 A list of regular expressions specifying how to recognise special
340 constructs such as include files etc, and an associated method for
2be55c9c
RS
341 extracting the filename from that construct.
342
98d775e5 343 - `ff-other-file-alist'
2be55c9c
RS
344 Alist of extensions to find given the current file's extension.
345
98d775e5 346 - `ff-search-directories'
2be55c9c 347 List of directories searched through with each extension specified in
98d775e5 348 `ff-other-file-alist' that matches this file's extension.
2be55c9c 349
03741cc5 350 - `ff-pre-find-hook'
2be55c9c
RS
351 List of functions to be called before the search for the file starts.
352
03741cc5 353 - `ff-pre-load-hook'
2be55c9c
RS
354 List of functions to be called before the other file is loaded.
355
03741cc5 356 - `ff-post-load-hook'
2be55c9c
RS
357 List of functions to be called after the other file is loaded.
358
03741cc5 359 - `ff-not-found-hook'
2be55c9c
RS
360 List of functions to be called if the other file could not be found.
361
03741cc5 362 - `ff-file-created-hook'
2be55c9c
RS
363 List of functions to be called if the other file has been created."
364 (interactive "P")
365 (let ((ignore ff-ignore-include))
366 (setq ff-ignore-include ignore-include)
367 (ff-find-the-other-file in-other-window)
368 (setq ff-ignore-include ignore)))
369
370;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
371;; Support functions
372
2be55c9c 373(defun ff-find-the-other-file (&optional in-other-window)
8c17509e
RS
374 "Find the header or source file corresponding to the current file.
375Being on a `#include' line pulls in that file, but see the help on
376the `ff-ignore-include' variable.
2be55c9c 377
8c17509e 378If optional IN-OTHER-WINDOW is non-nil, find the file in another window."
2be55c9c
RS
379
380 (let (match ;; matching regexp for this file
381 suffixes ;; set of replacing regexps for the matching regexp
382 action ;; function to generate the names of the other files
383 fname ;; basename of this file
384 pos ;; where we start matching filenames
385 stub ;; name of the file without extension
386 alist ;; working copy of the list of file extensions
387 pathname ;; the pathname of the file or the #include line
388 default-name ;; file we should create if none found
98d775e5
DL
389 format ;; what we have to match
390 found ;; name of the file or buffer found - nil if none
2be55c9c
RS
391 dirs ;; local value of ff-search-directories
392 no-match) ;; whether we know about this kind of file
393
03741cc5 394 (run-hooks 'ff-pre-find-hook 'ff-pre-find-hooks)
2be55c9c
RS
395
396 (message "Working...")
397
398 (setq dirs
399 (if (symbolp ff-search-directories)
400 (ff-list-replace-env-vars (symbol-value ff-search-directories))
401 (ff-list-replace-env-vars ff-search-directories)))
402
403 (save-excursion
404 (beginning-of-line 1)
405 (setq fname (ff-treat-as-special)))
406
407 (cond
408 ((and (not ff-ignore-include) fname)
409 (setq default-name fname)
410 (setq found (ff-get-file dirs fname nil in-other-window)))
411
412 ;; let's just get the corresponding file
413 (t
414 (setq alist (if (symbolp ff-other-file-alist)
415 (symbol-value ff-other-file-alist)
416 ff-other-file-alist)
417 pathname (if (buffer-file-name)
418 (buffer-file-name)
419 "/none.none"))
420
f4a97216 421 (setq fname (file-name-nondirectory pathname)
2be55c9c
RS
422 no-match nil
423 match (car alist))
424
425 ;; find the table entry corresponding to this file
426 (setq pos (ff-string-match (car match) fname))
427 (while (and match (if (and pos (>= pos 0)) nil (not pos)))
428 (setq alist (cdr alist))
429 (setq match (car alist))
430 (setq pos (ff-string-match (car match) fname)))
431
432 ;; no point going on if we haven't found anything
433 (if (not match)
434 (setq no-match t)
435
436 ;; otherwise, suffixes contains what we need
437 (setq suffixes (car (cdr match))
438 action (car (cdr match))
439 found nil)
440
98d775e5 441 ;; if we have a function to generate new names,
2be55c9c
RS
442 ;; invoke it with the name of the current file
443 (if (and (atom action) (fboundp action))
444 (progn
445 (setq suffixes (funcall action (buffer-file-name))
446 match (cons (car match) (list suffixes))
447 stub nil
448 default-name (car suffixes)))
449
450 ;; otherwise build our filename stub
98d775e5 451 (cond
2be55c9c
RS
452
453 ;; get around the problem that 0 and nil both mean false!
454 ((= pos 0)
455 (setq format "")
456 (setq stub "")
457 )
458
459 (t
460 (setq format (concat "\\(.+\\)" (car match)))
461 (string-match format fname)
462 (setq stub (substring fname (match-beginning 1) (match-end 1)))
463 ))
464
465 ;; if we find nothing, we should try to get a file like this one
466 (setq default-name
467 (concat stub (car (car (cdr match))))))
468
469 ;; do the real work - find the file
98d775e5 470 (setq found
2be55c9c
RS
471 (ff-get-file dirs
472 stub
98d775e5 473 suffixes
2be55c9c
RS
474 in-other-window)))))
475
476 (cond
477 (no-match ;; could not even determine the other file
478 (message ""))
479
98d775e5 480 (t
2be55c9c
RS
481 (cond
482
483 ((not found) ;; could not find the other file
484
03741cc5 485 (run-hooks 'ff-not-found-hook 'ff-not-found-hooks)
2be55c9c 486
98d775e5 487 (cond
2be55c9c
RS
488 (ff-always-try-to-create ;; try to create the file
489 (let (name pathname)
490
491 (setq name
492 (expand-file-name
493 (read-file-name
494 (format "Find or create %s in: " default-name)
495 default-directory default-name nil)))
71296446 496
2be55c9c
RS
497 (setq pathname
498 (if (file-directory-p name)
499 (concat (file-name-as-directory name) default-name)
500 (setq found name)))
71296446 501
2be55c9c
RS
502 (ff-find-file pathname in-other-window t)))
503
504 (t ;; don't create the file, just whinge
988021a7 505 (message "No file found for %s" fname))))
2be55c9c
RS
506
507 (t ;; matching file found
508 nil))))
509
510 found)) ;; return buffer-name or filename
511
274890d9
RS
512(defun ff-other-file-name ()
513 "Return name of the header or source file corresponding to the current file.
514Being on a `#include' line pulls in that file, but see the help on
515the `ff-ignore-include' variable."
516
517 (let (match ;; matching regexp for this file
518 suffixes ;; set of replacing regexps for the matching regexp
519 action ;; function to generate the names of the other files
520 fname ;; basename of this file
521 pos ;; where we start matching filenames
522 stub ;; name of the file without extension
523 alist ;; working copy of the list of file extensions
524 pathname ;; the pathname of the file or the #include line
525 default-name ;; file we should create if none found
526 format ;; what we have to match
527 found ;; name of the file or buffer found - nil if none
528 dirs ;; local value of ff-search-directories
529 no-match) ;; whether we know about this kind of file
530
531 (message "Working...")
532
533 (setq dirs
534 (if (symbolp ff-search-directories)
535 (ff-list-replace-env-vars (symbol-value ff-search-directories))
536 (ff-list-replace-env-vars ff-search-directories)))
537
538 (save-excursion
539 (beginning-of-line 1)
540 (setq fname (ff-treat-as-special)))
541
542 (cond
543 ((and (not ff-ignore-include) fname)
544 (setq default-name fname)
545 (setq found (ff-get-file-name dirs fname nil)))
546
547 ;; let's just get the corresponding file
548 (t
549 (setq alist (if (symbolp ff-other-file-alist)
550 (symbol-value ff-other-file-alist)
551 ff-other-file-alist)
552 pathname (if (buffer-file-name)
553 (buffer-file-name)
554 "/none.none"))
555
556 (setq fname (file-name-nondirectory pathname)
557 no-match nil
558 match (car alist))
559
560 ;; find the table entry corresponding to this file
561 (setq pos (ff-string-match (car match) fname))
562 (while (and match (if (and pos (>= pos 0)) nil (not pos)))
563 (setq alist (cdr alist))
564 (setq match (car alist))
565 (setq pos (ff-string-match (car match) fname)))
566
567 ;; no point going on if we haven't found anything
568 (if (not match)
569 (setq no-match t)
570
571 ;; otherwise, suffixes contains what we need
572 (setq suffixes (car (cdr match))
573 action (car (cdr match))
574 found nil)
575
576 ;; if we have a function to generate new names,
577 ;; invoke it with the name of the current file
578 (if (and (atom action) (fboundp action))
579 (progn
580 (setq suffixes (funcall action (buffer-file-name))
581 match (cons (car match) (list suffixes))
582 stub nil
583 default-name (car suffixes)))
584
585 ;; otherwise build our filename stub
586 (cond
587
588 ;; get around the problem that 0 and nil both mean false!
589 ((= pos 0)
590 (setq format "")
591 (setq stub "")
592 )
593
594 (t
595 (setq format (concat "\\(.+\\)" (car match)))
596 (string-match format fname)
597 (setq stub (substring fname (match-beginning 1) (match-end 1)))
598 ))
599
600 ;; if we find nothing, we should try to get a file like this one
601 (setq default-name
602 (concat stub (car (car (cdr match))))))
603
604 ;; do the real work - find the file
605 (setq found
606 (ff-get-file-name dirs stub suffixes)))))
607 found)) ;; return buffer-name or filename
608
98d775e5
DL
609(defun ff-get-file (search-dirs filename &optional suffix-list other-window)
610 "Find a file in the SEARCH-DIRS with the given FILENAME (or filename stub).
611If (optional) SUFFIX-LIST is nil, search for fname, otherwise search
612for fname with each of the given suffixes. Get the file or the buffer
613corresponding to the name of the first file found, or nil."
614 (let ((filename (ff-get-file-name search-dirs filename suffix-list)))
71296446 615
98d775e5 616 (cond
2be55c9c
RS
617 ((not filename)
618 nil)
619
91afecb3
RS
620 ((bufferp (get-file-buffer filename))
621 (ff-switch-to-buffer (get-file-buffer filename) other-window)
2be55c9c 622 filename)
71296446 623
2be55c9c
RS
624 ((file-exists-p filename)
625 (ff-find-file filename other-window nil)
626 filename)
627
628 (t
629 nil))))
630
631(defun ff-get-file-name (search-dirs fname-stub &optional suffix-list)
98d775e5
DL
632 "Find a file in SEARCH-DIRS with the given name (or stub) FNAME-STUB.
633If (optional) SUFFIX-LIST is nil, search for FNAME-STUB, otherwise
634search for FNAME-STUB with each of the given suffixes. Return the
635name of the first file found."
2be55c9c
RS
636 (let* (dirs ;; working copy of dirs to search
637 dir ;; the current dir considered
638 file ;; filename being looked for
639 rest ;; pathname after first /*
640 this-suffix ;; the suffix we are currently considering
641 suffixes ;; working copy of suffix-list
642 filename ;; built filename
643 blist ;; list of live buffers
644 buf ;; current buffer in blist
645 found) ;; whether we have found anything
646
647 (setq suffixes suffix-list)
648
649 ;; suffixes is nil => fname-stub is the file we are looking for
650 ;; otherwise fname-stub is a stub, and we append a suffix
651 (if suffixes
652 (setq this-suffix (car suffixes))
653 (setq this-suffix "")
654 (setq suffixes (list "")))
71296446 655
2be55c9c
RS
656 ;; find whether the file is in a buffer first
657 (while (and suffixes (not found))
658 (setq filename (concat fname-stub this-suffix))
659
660 (if (not ff-quiet-mode)
988021a7 661 (message "Finding buffer %s..." filename))
2be55c9c 662
988021a7
RS
663 (if (bufferp (get-file-buffer filename))
664 (setq found (buffer-file-name (get-file-buffer filename))))
2be55c9c
RS
665
666 (setq blist (buffer-list))
667 (setq buf (buffer-name (car blist)))
668 (while (and blist (not found))
669
670 (if (string-match (concat filename "<[0-9]+>") buf)
17feeeb2 671 (setq found (buffer-file-name (car blist))))
2be55c9c
RS
672
673 (setq blist (cdr blist))
674 (setq buf (buffer-name (car blist))))
675
676 (setq suffixes (cdr suffixes))
677 (setq this-suffix (car suffixes)))
678
679 ;; now look for the real file
680 (setq dirs search-dirs)
681 (setq dir (car dirs))
682 (while (and (not found) dirs)
683
684 (setq suffixes suffix-list)
685
686 ;; if dir does not contain '/*', look for the file
687 (if (and dir (not (string-match "\\([^*]*\\)/\\\*\\(/.*\\)*" dir)))
98d775e5 688 (progn
71296446 689
2be55c9c
RS
690 ;; suffixes is nil => fname-stub is the file we are looking for
691 ;; otherwise fname-stub is a stub, and we append a suffix
692 (if suffixes
693 (setq this-suffix (car suffixes))
694 (setq this-suffix "")
695 (setq suffixes (list "")))
71296446 696
2be55c9c
RS
697 (while (and suffixes (not found))
698
699 (setq filename (concat fname-stub this-suffix))
700 (setq file (concat dir "/" filename))
71296446 701
2be55c9c 702 (if (not ff-quiet-mode)
988021a7 703 (message "Finding %s..." file))
2be55c9c
RS
704
705 (if (file-exists-p file)
706 (setq found file))
71296446 707
2be55c9c
RS
708 (setq suffixes (cdr suffixes))
709 (setq this-suffix (car suffixes))))
710
711 ;; otherwise dir matches the '/*', so search each dir separately
712 (progn
713 (if (match-beginning 2)
714 (setq rest (substring dir (match-beginning 2) (match-end 2)))
715 (setq rest "")
716 )
717 (setq dir (substring dir (match-beginning 1) (match-end 1)))
718
719 (let ((dirlist (ff-all-dirs-under dir '("..")))
720 this-dir compl-dirs)
721
722 (setq this-dir (car dirlist))
723 (while dirlist
724 (setq compl-dirs
725 (append
726 compl-dirs
727 (list (concat this-dir rest))
728 ))
729 (setq dirlist (cdr dirlist))
730 (setq this-dir (car dirlist)))
731
732 (if compl-dirs
733 (setq found (ff-get-file-name compl-dirs
734 fname-stub
735 suffix-list))))))
736 (setq dirs (cdr dirs))
737 (setq dir (car dirs)))
738
739 (if found
740 (message "%s found" found))
741
742 found))
743
744(defun ff-string-match (regexp string &optional start)
91afecb3 745 "Like `string-match', but set `case-fold-search' temporarily.
8c17509e
RS
746The value used comes from `ff-case-fold-search'."
747 (let ((case-fold-search ff-case-fold-search))
2be55c9c 748 (if regexp
8c17509e 749 (string-match regexp string start))))
2be55c9c
RS
750
751(defun ff-list-replace-env-vars (search-list)
752 "Replace environment variables (of the form $VARIABLE) in SEARCH-LIST."
753 (let (list
754 (var (car search-list)))
755 (while search-list
756 (if (string-match "\\(.*\\)\\$[({]*\\([a-zA-Z0-9_]+\\)[)}]*\\(.*\\)" var)
757 (setq var
758 (concat
759 (substring var (match-beginning 1) (match-end 1))
760 (getenv (substring var (match-beginning 2) (match-end 2)))
761 (substring var (match-beginning 3) (match-end 3)))))
762 (setq search-list (cdr search-list))
763 (setq list (cons var list))
764 (setq var (car search-list)))
765 (setq search-list (reverse list))))
766
767(defun ff-treat-as-special ()
98d775e5 768 "Return the file to look for if the construct was special, else nil.
91afecb3 769The construct is defined in the variable `ff-special-constructs'."
2be55c9c
RS
770 (let* (fname
771 (list ff-special-constructs)
772 (elem (car list))
773 (regexp (car elem))
774 (match (cdr elem)))
775 (while (and list (not fname))
776 (if (and (looking-at regexp) match)
777 (setq fname (funcall match)))
778 (setq list (cdr list))
779 (setq elem (car list))
780 (setq regexp (car elem))
781 (setq match (cdr elem)))
782 fname))
783
784(defun ff-basename (string)
98d775e5 785 "Return the basename of pathname STRING."
2be55c9c
RS
786 (setq string (concat "/" string))
787 (string-match ".*/\\([^/]+\\)$" string)
788 (setq string (substring string (match-beginning 1) (match-end 1))))
789
790(defun ff-all-dirs-under (here &optional exclude)
8c17509e 791 "Get all the directory files under directory HERE.
2be55c9c
RS
792Exclude all files in the optional EXCLUDE list."
793 (if (file-directory-p here)
794 (condition-case nil
795 (progn
796 (let ((files (directory-files here t))
797 (dirlist (list))
798 file)
799 (while files
800 (setq file (car files))
801 (if (and
802 (file-directory-p file)
803 (not (member (ff-basename file) exclude)))
804 (setq dirlist (cons file dirlist)))
805 (setq files (cdr files)))
806 (setq dirlist (reverse dirlist))))
807 (error nil))
808 nil))
809
810(defun ff-switch-file (f1 f2 file &optional in-other-window new-file)
8c17509e
RS
811 "Call F1 or F2 on FILE, according to IN-OTHER-WINDOW.
812In addition, this runs various hooks.
2be55c9c 813
8c17509e
RS
814Either F1 or F2 receives FILE as the sole argument.
815The decision of which one to call is based on IN-OTHER-WINDOW
816and on the global variable `ff-always-in-other-window'.
2be55c9c 817
8c17509e
RS
818F1 and F2 are typically `find-file' / `find-file-other-window'
819or `switch-to-buffer' / `switch-to-buffer-other-window' function pairs.
820
03741cc5
SM
821If optional NEW-FILE is t, then a special hook (`ff-file-created-hook') is
822called before `ff-post-load-hook'."
823 (run-hooks 'ff-pre-load-hook 'ff-pre-load-hooks)
2be55c9c
RS
824 (if (or
825 (and in-other-window (not ff-always-in-other-window))
826 (and (not in-other-window) ff-always-in-other-window))
827 (funcall f2 file)
828 (funcall f1 file))
829 (if new-file
03741cc5
SM
830 (run-hooks 'ff-file-created-hook 'ff-file-created-hooks))
831 (run-hooks 'ff-post-load-hook 'ff-post-load-hooks))
2be55c9c
RS
832
833(defun ff-find-file (file &optional in-other-window new-file)
91afecb3 834 "Like `find-file', but may show the file in another window."
98d775e5
DL
835 (ff-switch-file 'find-file
836 'find-file-other-window
2be55c9c
RS
837 file in-other-window new-file))
838
91afecb3
RS
839(defun ff-switch-to-buffer (buffer-or-name &optional in-other-window)
840 "Like `switch-to-buffer', but may show the buffer in another window."
2be55c9c 841
98d775e5
DL
842 (ff-switch-file 'switch-to-buffer
843 'switch-to-buffer-other-window
91afecb3 844 buffer-or-name in-other-window nil))
2be55c9c 845
6e9aac74
RS
846;;;###autoload
847(defun ff-mouse-find-other-file (event)
848 "Visit the file you click on."
849 (interactive "e")
850 (save-excursion
98d775e5 851 (mouse-set-point event)
6e9aac74 852 (ff-find-other-file nil)))
2be55c9c 853
6e9aac74
RS
854;;;###autoload
855(defun ff-mouse-find-other-file-other-window (event)
98d775e5 856 "Visit the file you click on in another window."
6e9aac74
RS
857 (interactive "e")
858 (save-excursion
98d775e5 859 (mouse-set-point event)
6e9aac74 860 (ff-find-other-file t)))
2be55c9c 861
2be55c9c
RS
862;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
863;; This section offers an example of user defined function to select files
864
8c17509e 865(defun ff-upcase-p (string &optional start end)
98d775e5 866 "Return t if STRING is all uppercase.
8c17509e 867Given START and/or END, checks between these characters."
2be55c9c
RS
868 (let (match str)
869 (if (not start)
870 (setq start 0))
871 (if (not end)
872 (setq end (length string)))
873 (if (= start end)
874 (setq end (1+ end)))
875 (setq str (substring string start end))
98d775e5 876 (if (and
2be55c9c
RS
877 (ff-string-match "[A-Z]+" str)
878 (setq match (match-data))
879 (= (car match) 0)
880 (= (car (cdr match)) (length str)))
881 t
882 nil)))
883
884(defun ff-cc-hh-converter (arg)
8c17509e
RS
885 "Discriminate file extensions.
886Build up a new file list based possibly on part of the directory name
887and the name of the file passed in."
2be55c9c 888 (ff-string-match "\\(.*\\)/\\([^/]+\\)/\\([^.]+\\).\\([^/]+\\)$" arg)
98d775e5 889 (let ((path (if (match-beginning 1)
2be55c9c 890 (substring arg (match-beginning 1) (match-end 1)) nil))
98d775e5 891 (dire (if (match-beginning 2)
2be55c9c 892 (substring arg (match-beginning 2) (match-end 2)) nil))
98d775e5 893 (file (if (match-beginning 3)
2be55c9c 894 (substring arg (match-beginning 3) (match-end 3)) nil))
98d775e5 895 (extn (if (match-beginning 4)
2be55c9c
RS
896 (substring arg (match-beginning 4) (match-end 4)) nil))
897 return-list)
898 (cond
899 ;; fooZapJunk.cc => ZapJunk.{hh,h} or fooZapJunk.{hh,h}
98d775e5 900 ((and (string= extn "cc")
2be55c9c
RS
901 (ff-string-match "^\\([a-z]+\\)\\([A-Z].+\\)$" file))
902 (let ((stub (substring file (match-beginning 2) (match-end 2))))
903 (setq dire (upcase (substring file (match-beginning 1) (match-end 1))))
904 (setq return-list (list (concat stub ".hh")
905 (concat stub ".h")
906 (concat file ".hh")
907 (concat file ".h")))
908 ))
909 ;; FOO/ZapJunk.hh => fooZapJunk.{cc,C} or ZapJunk.{cc,C}
8c17509e 910 ((and (string= extn "hh") (ff-upcase-p dire) file)
2be55c9c 911 (let ((stub (concat (downcase dire) file)))
98d775e5 912 (setq return-list (list (concat stub ".cc")
2be55c9c
RS
913 (concat stub ".C")
914 (concat file ".cc")
915 (concat file ".C")))
916 ))
917 ;; zap.cc => zap.hh or zap.h
918 ((string= extn "cc")
919 (let ((stub file))
920 (setq return-list (list (concat stub ".hh")
921 (concat stub ".h")))
922 ))
923 ;; zap.hh => zap.cc or zap.C
924 ((string= extn "hh")
925 (let ((stub file))
926 (setq return-list (list (concat stub ".cc")
927 (concat stub ".C")))
928 ))
98d775e5 929 (t
2be55c9c 930 nil))
71296446 931
2be55c9c
RS
932 return-list))
933
934;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
935;; This section offers an example of user defined function to place point.
936;; The regexps are Ada specific.
937
938(defvar ff-function-name nil "Name of the function we are in.")
939
03741cc5 940;; bind with (setq ff-pre-load-hook 'ff-which-function-are-we-in)
2be55c9c
RS
941;;
942(defun ff-which-function-are-we-in ()
8c17509e
RS
943 "Return the name of the function whose definition/declaration point is in.
944Also remember that name in `ff-function-name'."
2be55c9c
RS
945
946 (setq ff-function-name nil)
947
948 (save-excursion
949 (if (re-search-backward ada-procedure-start-regexp nil t)
950 (setq ff-function-name (buffer-substring (match-beginning 0)
951 (match-end 0)))
952 ; we didn't find a procedure start, perhaps there is a package
953 (if (re-search-backward ada-package-start-regexp nil t)
954 (setq ff-function-name (buffer-substring (match-beginning 0)
955 (match-end 0)))
956 ))))
957
03741cc5 958;; bind with (setq ff-post-load-hook 'ff-set-point-accordingly)
2be55c9c
RS
959;;
960(defun ff-set-point-accordingly ()
8c17509e 961 "Find the function specified in `ff-function-name'.
2ab6bb14 962That name was previously determined by `ff-which-function-are-we-in'."
2be55c9c
RS
963 (if ff-function-name
964 (progn
965 (goto-char (point-min))
966 (search-forward ff-function-name nil t))))
967
98d775e5 968(provide 'find-file)
2be55c9c 969
ab5796a9 970;;; arch-tag: 5a2fc49e-3b0a-4708-9acf-fb14e471a97a
98d775e5 971;;; find-file.el ends here