Mercurial > hg > xemacs-beta
comparison lisp/hyperbole/wrolo.el @ 0:376386a54a3c r19-14
Import from CVS: tag r19-14
author | cvs |
---|---|
date | Mon, 13 Aug 2007 08:45:50 +0200 |
parents | |
children | 4103f0995bd7 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:376386a54a3c |
---|---|
1 ;;!emacs | |
2 ;; | |
3 ;; FILE: wrolo.el V2 (Renamed from rolo.el in earlier versions to avoid | |
4 ;; load path conflicts with the rolo.el written by | |
5 ;; another author.) | |
6 ;; SUMMARY: Hierarchical, multi-file, easy to use rolodex system | |
7 ;; USAGE: GNU Emacs Lisp Library | |
8 ;; KEYWORDS: hypermedia, matching | |
9 ;; | |
10 ;; AUTHOR: Bob Weiner | |
11 ;; ORG: Motorola Inc. | |
12 ;; | |
13 ;; ORIG-DATE: 7-Jun-89 at 22:08:29 | |
14 ;; LAST-MOD: 31-Oct-95 at 18:39:54 by Bob Weiner | |
15 ;; | |
16 ;; This file is part of Hyperbole. | |
17 ;; Available for use and distribution under the same terms as GNU Emacs. | |
18 ;; | |
19 ;; Copyright (C) 1989, '90, '91, '92, '95 Free Software Foundation, Inc. | |
20 ;; Developed with support from Motorola Inc. | |
21 ;; | |
22 ;; DESCRIPTION: | |
23 ;; | |
24 ;; The `put whatever you feel like into it' rolodex. | |
25 ;; | |
26 ;; FEATURES: | |
27 ;; | |
28 ;; 1. Multiple rolodex files with free text lookup. No structured | |
29 ;; fields are used. | |
30 ;; | |
31 ;; 2. Hierarchical rolodex entries as in: | |
32 ;; * Company | |
33 ;; ** Manager | |
34 ;; *** Underlings | |
35 ;; | |
36 ;; Searching for Manager turns up all Underlings. Searching for | |
37 ;; Company retrieves all listed employees. | |
38 ;; | |
39 ;; This hierarchical system has proved very effective for retrieving | |
40 ;; computer system administration problem reports by vendor name, | |
41 ;; problem number or by subject area, without having to resort to a | |
42 ;; database system, and also for extraction of relevant text | |
43 ;; sections from reports. | |
44 ;; | |
45 ;; 3. String and regular expression searching capabilities. Matches are | |
46 ;; found anywhere within entries, so entries may be of any format you | |
47 ;; like without the bother of fixed field restrictions. | |
48 ;; Ability to restrict number of matches or to report number of matches | |
49 ;; without displaying entries. | |
50 ;; | |
51 ;; 4. Smart addition, editing and sorting of entries by hierarchy level. | |
52 ;; | |
53 ;; 5. Support for Hyperbole buttons within rolodex entries. | |
54 ;; | |
55 ;; See "wrolo-logic.el" for logical search functions (and, or, not, xor). | |
56 ;; See "wrolo-menu.el" for menu handling functions. (If you received | |
57 ;; wrolo as part of Hyperbole, this file in unneeded and so not included.) | |
58 ;; | |
59 ;; | |
60 ;; SETUP: | |
61 ;; | |
62 ;; The variable 'rolo-file-list' is a list of files to search for | |
63 ;; matching rolodex entries. To add personal files to rolo-file-list, | |
64 ;; when you find these functions are useful for any sort of list lookup, | |
65 ;; add the following to your ~/.emacs file (substituting where you see | |
66 ;; <fileN>): | |
67 ;; | |
68 ;; (setq rolo-file-list (append rolo-file-list '("<file1>" "<file2>"))) | |
69 ;; | |
70 ;; We recommend that entries in 'rolo-file-list' have ".otl" suffixes | |
71 ;; so that they do not conflict with file names that other rolodex | |
72 ;; programs might use and so that they are edited in 'outline-mode' by | |
73 ;; default. If you want the latter behavior, uncomment and add something | |
74 ;; like the following to one of your GNU Emacs initialization files: | |
75 ;; | |
76 ;; ;; Add to the list of suffixes that causes automatic mode invocation | |
77 ;; (setq auto-mode-alist | |
78 ;; (append '(("\\.otl$" . outline-mode)) auto-mode-alist)) | |
79 ;; | |
80 ;; The buffers containing the rolodex files are not killed after a search | |
81 ;; on the assumption that another search is likely to follow within this | |
82 ;; Emacs session. You may wish to change this behavior with the following | |
83 ;; setting: | |
84 ;; | |
85 ;; (setq rolo-kill-buffers-after-use t) | |
86 ;; | |
87 ;; After an entry is killed, the modified rolodex file is automatically | |
88 ;; saved. If you would rather always save files yourself, use this | |
89 ;; setting: | |
90 ;; | |
91 ;; (setq rolo-save-buffers-after-use nil) | |
92 ;; | |
93 ;; When adding an entry from within a buffer containing a mail | |
94 ;; message, the rolodex add function will extract the sender's name | |
95 ;; and e-mail address and prompt you with the name as a default. If | |
96 ;; you accept it, it will enter the name and the email address using | |
97 ;; the format given by the 'rolo-email-format' variable. See its | |
98 ;; documentation if you want to change its value. | |
99 ;; | |
100 ;; | |
101 ;; If you use Hyperbole V2.3 or greater, then no other rolodex setup | |
102 ;; is necessary, simply select the "Rolo/" menu item from the top | |
103 ;; level Hyperbole menu. Otherwise, add the following to your | |
104 ;; "~/.emacs" file: | |
105 ;; | |
106 ;; (autoload 'rolo-menu "rolo-menu" "Load wrolo system." t) | |
107 ;; (global-set-key "\C-x4r" 'rolo-menu) | |
108 ;; | |
109 ;; And then simply invoke the rolodex menu with {C-x 4 r} after Emacs | |
110 ;; has read those lines in your init file. | |
111 ;; | |
112 ;; | |
113 ;; SUMMARY OF USE: | |
114 ;; | |
115 ;; The rolo menu provides access to the following commands: | |
116 ;; | |
117 ;; Menu Item Function Description | |
118 ;; ==================================================================== | |
119 ;; Add rolo-add Adds a rolodex entry | |
120 ;; Display rolo-display-matches Displays last matches again | |
121 ;; Edit rolo-edit Edits an existing rolodex entry | |
122 ;; Info Displays Rolodex manual entry | |
123 ;; Kill rolo-kill Removes an entry from the rolodex | |
124 ;; Order rolo-sort Sorts all levels in rolodex | |
125 ;; RegexFind rolo-grep Finds all entries containing | |
126 ;; a regular expression | |
127 ;; StringFind rolo-fgrep Finds all entries containing | |
128 ;; a string | |
129 ;; WordFind rolo-word Finds all entries containing | |
130 ;; a string of whole words | |
131 ;; Yank rolo-yank Inserts first matching rolodex | |
132 ;; entry at point | |
133 ;; | |
134 ;; For any of these commands that prompt you for a name, you may use the form | |
135 ;; parent/child to locate a child entry below a parent entry, e.g. | |
136 ;; from the example near the top, we could give Company/Manager/Underlings. | |
137 ;; | |
138 ;; Here is a snippet from our group rolodex file. The ';'s should be | |
139 ;; removed of course and the '*'s should begin at the start of the | |
140 ;; line. If a rolodex file begins with two separator lines whose | |
141 ;; first three characters are "===", then these lines and any text | |
142 ;; between them are prepended to the output buffer whenever any | |
143 ;; entries are retrieved from that file. | |
144 ;; | |
145 ;;============================================================================= | |
146 ;; GROUP ROLODEX | |
147 ;; <Last Name>, <First Name> <Co/Categ> W<Work #> H<Home #> P<Pager #> | |
148 ;; F<Fax #> M<Modem #> C<Cellular #> | |
149 ;; <Address> <Miscellaneous Info, Key Words> | |
150 ;;============================================================================= | |
151 ;;* EX594, Digital-Systems-Research | |
152 ;;** Weiner, Bob Motorola W2087 P7-7489 | |
153 ;; FL19, L-1035 | |
154 ;; | |
155 ;; | |
156 ;; FOR PROGRAMMERS: | |
157 ;; | |
158 ;; Entries in rolodex files are separated by patterns matching | |
159 ;; 'rolo-entry-regexp'. Each entry may have any number of sub-entries | |
160 ;; which represent the next level down in the entry hierarchy. | |
161 ;; Sub-entries' separator patterns are always longer than their parents'. | |
162 ;; For example, if an entry began with '*' then its sub-entries would begin | |
163 ;; with '**' and so on. Blank lines in rolodex files will not end up where | |
164 ;; you want them if you use the rolo-sort commands; therefore, blank lines | |
165 ;; are not recommended. If you change the value of | |
166 ;; 'rolo-entry-regexp', you will have to modify 'rolo-sort'. | |
167 ;; | |
168 ;; The following additional functions are provided: | |
169 ;; | |
170 ;; 'rolo-sort-level' sorts a specific level of entries in a rolodex file; | |
171 ;; 'rolo-map-level' runs a user specified function on a specific level of | |
172 ;; entries in a rolodex file; | |
173 ;; 'rolo-fgrep-file', same as 'rolo-fgrep' but operates on a single file; | |
174 ;; 'rolo-grep-file', same as 'rolo-grep' but operates on a single file; | |
175 ;; 'rolo-display-matches', display last set of rolodex matches, if any; | |
176 ;; 'rolo-toggle-narrow-to-entry' toggles between display of current entry | |
177 ;; and display of all matching entries. | |
178 ;; | |
179 ;; | |
180 ;; MOD HISTORY: | |
181 ;; | |
182 ;; 12/17/89 | |
183 ;; Added internal 'rolo-shrink-window' function for use in | |
184 ;; compressing/uncompressing the rolo view window to/from a size just | |
185 ;; large enough for the selected entry. This is useful when a search | |
186 ;; turns up more entries than desired. | |
187 ;; | |
188 ;; 02/21/90 | |
189 ;; Modified 'rolo-grep-file' and 'rolo-map-level' so they only set buffers | |
190 ;; read-only the first time they are read in. This way, if someone edits a | |
191 ;; rolodex file and then does a rolo-fgrep or other function, the buffer | |
192 ;; will not be back in read-only mode. | |
193 ;; | |
194 ;; 04/18/91 | |
195 ;; Modified 'rolo-grep-file' to expand any hidden entries in rolo file | |
196 ;; before doing a search. | |
197 ;; | |
198 ;; 12/24/91 | |
199 ;; Added Hyperbole button support. | |
200 ;; | |
201 ;; 12/30/91 | |
202 ;; Added convenient support for entry add, edit, kill and yank. | |
203 ;; | |
204 ;; 01/10/91 | |
205 ;; Fixed bug in rolo-to that ended search too early. | |
206 ;; | |
207 ;; DESCRIP-END. | |
208 | |
209 ;;; ************************************************************************ | |
210 ;;; Other required Elisp libraries | |
211 ;;; ************************************************************************ | |
212 | |
213 (require 'hversion) | |
214 (require 'hmail) | |
215 | |
216 ;;; ************************************************************************ | |
217 ;;; Public variables | |
218 ;;; ************************************************************************ | |
219 | |
220 (defvar rolo-email-format "%s\t\t<%s>" | |
221 "Format string to use when adding an entry with e-mail addr from a mail msg. | |
222 It must contain a %s indicating where to put the entry name and a second | |
223 %s indicating where to put the e-mail address.") | |
224 | |
225 (defvar rolo-file-list | |
226 (if (memq system-type '(ms-windows windows-nt ms-dos)) | |
227 '("c:/_rolodex.otl") '("~/.rolodex.otl")) | |
228 "*List of files containing rolodex entries. | |
229 The first file should be a user-specific rolodex file, typically in the home | |
230 directory. The second file is often a shared, group-specific rolodex file. | |
231 | |
232 A rolo-file consists of: | |
233 (1) an optional header beginning with and ending with a line which matches | |
234 rolo-hdr-regexp; | |
235 (2) one or more rolodex entries which each begin with | |
236 rolo-entry-regexp and may be nested.") | |
237 | |
238 (defvar rolo-highlight-face nil | |
239 "*Face used to highlight rolodex search matches.") | |
240 (if rolo-highlight-face | |
241 nil | |
242 (setq rolo-highlight-face | |
243 (cond (hyperb:emacs19-p | |
244 (if (fboundp 'make-face) | |
245 (progn (make-face 'rolo-highlight-face) | |
246 'rolo-highlight-face))) | |
247 (hyperb:epoch-p (make-style)) | |
248 (t (if (fboundp 'make-face) | |
249 (face-name (make-face 'rolo-highlight-face)))))) | |
250 (if (fboundp 'hproperty:set-item-highlight) | |
251 (hproperty:set-item-highlight))) | |
252 | |
253 (defvar rolo-kill-buffers-after-use nil | |
254 "*Non-nil means kill rolodex file buffers after searching them for entries. | |
255 Only unmodified buffers are killed.") | |
256 | |
257 (defvar rolo-save-buffers-after-use t | |
258 "*Non-nil means save rolodex file after an entry is killed.") | |
259 | |
260 (defvar wrolo-yank-reformat-function nil | |
261 "*Value is a function of two arguments, START and END, invoked after a rolo-yank. | |
262 It should reformat the region given by the arguments to some preferred style. | |
263 Default value is nil, meaning no reformmating is done.") | |
264 | |
265 ;;; ************************************************************************ | |
266 ;;; Commands | |
267 ;;; ************************************************************************ | |
268 | |
269 ;;;###autoload | |
270 (defun rolo-add (name &optional file) | |
271 "Adds a new entry in personal rolodex for NAME. | |
272 Last name first is best, e.g. \"Smith, John\". | |
273 With prefix argument, prompts for optional FILE to add entry within. | |
274 NAME may be of the form: parent/child to insert child below a parent | |
275 entry which begins with the parent string." | |
276 (interactive | |
277 (progn | |
278 (or (fboundp 'mail-fetch-field) (require 'mail-utils)) | |
279 (let* ((lst (rolo-name-and-email)) | |
280 (name (car lst)) | |
281 (email (car (cdr lst))) | |
282 (entry (read-string "Name to add to rolo: " | |
283 (or name email)))) | |
284 (list (if (and email name | |
285 (string-match (concat "\\`" (regexp-quote entry)) name)) | |
286 (format rolo-email-format entry email) entry) | |
287 current-prefix-arg)))) | |
288 (if (or (not (stringp name)) (string= name "")) | |
289 (error "(rolo-add): Invalid name: '%s'" name)) | |
290 (if (and (interactive-p) file) | |
291 (setq file (completing-read "File to add to: " | |
292 (mapcar 'list rolo-file-list)))) | |
293 (if (null file) (setq file (car rolo-file-list))) | |
294 (cond ((and file (or (not (stringp file)) (string= file ""))) | |
295 (error "(rolo-add): Invalid file: '%s'" file)) | |
296 ((and (file-exists-p file) (not (file-readable-p file))) | |
297 (error "(rolo-add): File not readable: '%s'" file)) | |
298 ((not (file-writable-p file)) | |
299 (error "(rolo-add): File not writable: '%s'" file))) | |
300 (set-buffer (or (get-file-buffer file) (find-file-noselect file))) | |
301 (if (interactive-p) (message "Locating insertion point for '%s'..." name)) | |
302 (let ((parent "") (level "") end) | |
303 (widen) (goto-char 1) | |
304 (while (setq end (string-match "/" name)) | |
305 (setq parent (substring name 0 end) | |
306 name (substring name (min (1+ end) (length name)))) | |
307 (if (re-search-forward | |
308 (concat "\\(" rolo-entry-regexp "\\)[ \t]*" | |
309 (regexp-quote parent)) nil t) | |
310 (setq level (buffer-substring (match-beginning 1) | |
311 (match-end 1))) | |
312 (error "(rolo-add): '%s' category not found in \"%s\"." | |
313 parent file))) | |
314 (narrow-to-region (point) | |
315 (progn (rolo-to-entry-end t level) (point))) | |
316 (goto-char (point-min)) | |
317 (let* ((len (length name)) | |
318 (name-level (concat level "*")) | |
319 (level-len (length name-level)) | |
320 (entry "") | |
321 (entry-spc "") | |
322 (entry-level) | |
323 (match) | |
324 (again t)) | |
325 (while (and again | |
326 (re-search-forward | |
327 (concat "\\(" rolo-entry-regexp "\\)\\([ \t]*\\)") | |
328 nil 'end)) | |
329 (setq entry-level (buffer-substring (match-beginning 1) | |
330 (match-end 1))) | |
331 (if (/= (length entry-level) level-len) | |
332 (rolo-to-entry-end t entry-level) | |
333 (setq entry (buffer-substring (point) (+ (point) len)) | |
334 entry-spc (buffer-substring (match-beginning 2) | |
335 (match-end 2))) | |
336 (cond ((string< entry name) | |
337 (rolo-to-entry-end t entry-level)) | |
338 ((string< name entry) | |
339 (setq again nil) (beginning-of-line)) | |
340 (t ;; found existing entry matching name | |
341 (setq again nil match t))))) | |
342 (setq buffer-read-only nil) | |
343 (if match | |
344 nil | |
345 (insert (or entry-level (concat level "*")) | |
346 (if (string= entry-spc "") " " entry-spc) | |
347 name "\n") | |
348 (backward-char 1)) | |
349 (widen) | |
350 (rolo-to-buffer (current-buffer)) | |
351 ;; Fixes non-display update bug when buf is on screen before | |
352 ;; interactive command invocation. | |
353 (goto-char (point)) | |
354 (if (interactive-p) | |
355 (message "Edit entry at point."))))) | |
356 | |
357 ;;;###autoload | |
358 (defun rolo-display-matches (&optional display-buf return-to-buffer) | |
359 "Display optional DISPLAY-BUF buffer of previously found rolodex matches. | |
360 If DISPLAY-BUF is nil, use the value in 'rolo-display-buffer'. | |
361 Second arg RETURN-TO-BUFFER is the buffer to leave point within after the display." | |
362 (interactive) | |
363 (or display-buf (setq display-buf (get-buffer rolo-display-buffer))) | |
364 (if display-buf nil | |
365 (error "(rolo-display-matches): Search the rolodex first.")) | |
366 ;; Save current window configuration if rolodex match buffer is not | |
367 ;; displayed in one of the windows already. | |
368 (or | |
369 ;; Handle both Emacs V18 and V19 versions of get-buffer-window. | |
370 (condition-case () | |
371 (get-buffer-window display-buf (selected-frame)) | |
372 (error (get-buffer-window display-buf))) | |
373 (setq *rolo-wconfig* (current-window-configuration))) | |
374 (rolo-to-buffer display-buf t) | |
375 (if (eq major-mode 'wrolo-mode) nil (wrolo-mode)) | |
376 (setq buffer-read-only nil) | |
377 (if (fboundp 'hproperty:but-create) (hproperty:but-create)) | |
378 (rolo-shrink-window) | |
379 (goto-char (point-min)) | |
380 (set-buffer-modified-p nil) | |
381 (setq buffer-read-only t) | |
382 (run-hooks 'wrolo-display-hook) | |
383 ;; Leave point in match buffer unless a specific RETURN-TO-BUFFER has | |
384 ;; been specified. Use {q} to quit and restore display. | |
385 (if return-to-buffer (rolo-to-buffer return-to-buffer t))) | |
386 | |
387 ;;;###autoload | |
388 (defun rolo-edit (&optional name file) | |
389 "Edits a rolodex entry given by optional NAME within 'rolo-file-list'. | |
390 With prefix argument, prompts for optional FILE to locate entry within. | |
391 With no NAME arg, simply displays FILE or first entry in 'rolo-file-list' in an | |
392 editable mode. NAME may be of the form: parent/child to edit child below a | |
393 parent entry which begins with the parent string." | |
394 (interactive "sName to edit in rolo: \nP") | |
395 (if (string-equal name "") (setq name nil)) | |
396 (and name (not (stringp name)) | |
397 (error "(rolo-edit): Invalid name: '%s'" name)) | |
398 (if (and (interactive-p) current-prefix-arg) | |
399 (if (= (length rolo-file-list) 1) | |
400 (setq file (car rolo-file-list)) | |
401 (setq file (completing-read "Entry's File: " | |
402 (mapcar 'list rolo-file-list))))) | |
403 (let ((found-point) (file-list (if file (list file) rolo-file-list))) | |
404 (or file (setq file (car file-list))) | |
405 (if (null name) | |
406 (progn (if (not (file-writable-p file)) | |
407 (error "(rolo-edit): File not writable: '%s'" file)) | |
408 (find-file-other-window file) (setq buffer-read-only nil)) | |
409 (if (setq found-point (rolo-to name file-list)) | |
410 (progn | |
411 (setq file buffer-file-name) | |
412 (if (file-writable-p file) | |
413 (setq buffer-read-only nil) | |
414 (message | |
415 "(rolo-edit): Entry found but file not writable: '%s'" file) | |
416 (beep)) | |
417 (rolo-to-buffer (current-buffer))) | |
418 (message "(rolo-edit): '%s' not found." name) | |
419 (beep) | |
420 (rolo-to-buffer (or (get-file-buffer (car file-list)) | |
421 (find-file-noselect (car file-list)))) | |
422 (setq buffer-read-only nil)) | |
423 (widen) | |
424 ;; Fixes display update bug in some Emacs versions. When buf is | |
425 ;; on screen before interactive command invocation, point is not | |
426 ;; moved to proper location. | |
427 (if found-point (goto-char found-point))))) | |
428 | |
429 (defun rolo-edit-entry () | |
430 "Edit the source entry of the rolodex match buffer entry at point. | |
431 Returns entry name if found, else nil." | |
432 (interactive) | |
433 (let ((name (rolo-name-at))) | |
434 (if name (progn (rolo-edit name (hbut:key-src)) | |
435 name)))) | |
436 | |
437 ;;;###autoload | |
438 (defun rolo-fgrep (string | |
439 &optional max-matches rolo-file count-only no-display) | |
440 "Display rolodex entries matching STRING. | |
441 To a maximum of optional prefix arg MAX-MATCHES, in file(s) from optional | |
442 ROLO-FILE or rolo-file-list. Default is to find all matching entries. Each | |
443 entry is displayed with all of its sub-entries. Optional COUNT-ONLY non-nil | |
444 means don't retrieve and don't display matching entries. Optional NO-DISPLAY | |
445 non-nil means retrieve entries but don't display. | |
446 | |
447 Nil value of MAX-MATCHES means find all matches, t value means find all matches | |
448 but omit file headers, negative values mean find up to the inverse of that | |
449 number of entries and omit file headers. | |
450 | |
451 Returns number of entries matched. See also documentation for the variable | |
452 rolo-file-list." | |
453 (interactive "sRolodex string to match: \nP") | |
454 (let ((total-matches (rolo-grep (regexp-quote string) max-matches | |
455 rolo-file count-only no-display))) | |
456 (if (interactive-p) | |
457 (message "%s matching entr%s found in rolodex." | |
458 (if (= total-matches 0) "No" total-matches) | |
459 (if (= total-matches 1) "y" "ies"))) | |
460 total-matches)) | |
461 | |
462 ;;;###autoload | |
463 (defun rolo-grep (regexp &optional max-matches rolo-bufs count-only no-display) | |
464 "Display rolodex entries matching REGEXP. | |
465 To a maximum of prefix arg MAX-MATCHES, in buffer(s) from optional ROLO-BUFS or | |
466 rolo-file-list. Default is to find all matching entries. Each entry is | |
467 displayed with all of its sub-entries. Optional COUNT-ONLY non-nil means don't | |
468 retrieve and don't display matching entries. Optional NO-DISPLAY non-nil | |
469 means retrieve entries but don't display. | |
470 | |
471 Nil value of MAX-MATCHES means find all matches, t value means find all matches | |
472 but omit file headers, negative values mean find up to the inverse of that | |
473 number of entries and omit file headers. | |
474 | |
475 Returns number of entries matched. See also documentation for the variable | |
476 rolo-file-list." | |
477 (interactive "sRolodex regular expression to match: \nP") | |
478 (let ((rolo-file-list | |
479 (cond ((null rolo-bufs) rolo-file-list) | |
480 ((listp rolo-bufs) rolo-bufs) | |
481 ((list rolo-bufs)))) | |
482 (display-buf (if count-only | |
483 nil | |
484 (set-buffer (get-buffer-create rolo-display-buffer)))) | |
485 (total-matches 0) | |
486 (num-matched 0) | |
487 (inserting (or (eq max-matches t) | |
488 (and (integerp max-matches) (< max-matches 0)))) | |
489 (file)) | |
490 (if count-only nil | |
491 (setq buffer-read-only nil) | |
492 (or inserting (erase-buffer))) | |
493 (while (and (setq file (car rolo-file-list)) | |
494 (or (not (integerp max-matches)) | |
495 (< total-matches (max max-matches (- max-matches))))) | |
496 (setq rolo-file-list (cdr rolo-file-list) | |
497 num-matched (rolo-grep-file file regexp max-matches count-only) | |
498 total-matches (+ total-matches num-matched)) | |
499 (if (integerp max-matches) | |
500 (setq max-matches | |
501 (if (>= max-matches 0) | |
502 (- max-matches num-matched) | |
503 (+ max-matches num-matched))))) | |
504 (if (or count-only no-display inserting (= total-matches 0)) | |
505 nil | |
506 (rolo-display-matches display-buf)) | |
507 (if (interactive-p) | |
508 (message "%s matching entr%s found in rolodex." | |
509 (if (= total-matches 0) "No" total-matches) | |
510 (if (= total-matches 1) "y" "ies") | |
511 )) | |
512 total-matches)) | |
513 | |
514 (defun rolo-isearch () | |
515 "Interactively search forward for next occurrence of current match regexp. | |
516 Use this to add characters to further narrow the search." | |
517 (interactive) | |
518 (if (equal (buffer-name) rolo-display-buffer) | |
519 (execute-kbd-macro (concat "\e\C-s" rolo-match-regexp)) | |
520 (error "(rolo-isearch): Use this command in the %s match buffer" | |
521 rolo-display-buffer))) | |
522 | |
523 ;;;###autoload | |
524 (defun rolo-kill (name &optional file) | |
525 "Kills a rolodex entry given by NAME within 'rolo-file-list'. | |
526 With prefix argument, prompts for optional FILE to locate entry within. | |
527 NAME may be of the form: parent/child to kill child below a parent entry | |
528 which begins with the parent string. | |
529 Returns t if entry is killed, nil otherwise." | |
530 (interactive "sName to kill in rolo: \nP") | |
531 (if (or (not (stringp name)) (string= name "")) | |
532 (error "(rolo-kill): Invalid name: '%s'" name)) | |
533 (if (and (interactive-p) current-prefix-arg) | |
534 (setq file (completing-read "Entry's File: " | |
535 (mapcar 'list rolo-file-list)))) | |
536 (let ((file-list (if file (list file) rolo-file-list)) | |
537 (killed)) | |
538 (or file (setq file (car file-list))) | |
539 (if (rolo-to name file-list) | |
540 (progn | |
541 (setq file buffer-file-name) | |
542 (if (file-writable-p file) | |
543 (let ((kill-op | |
544 (function (lambda (start level) | |
545 (kill-region | |
546 start (rolo-to-entry-end t level)) | |
547 (setq killed t) | |
548 (rolo-save-buffer) | |
549 (rolo-kill-buffer)))) | |
550 start end level) | |
551 (setq buffer-read-only nil) | |
552 (re-search-backward rolo-entry-regexp nil t) | |
553 (setq end (match-end 0)) | |
554 (beginning-of-line) | |
555 (setq start (point) | |
556 level (buffer-substring start end)) | |
557 (goto-char end) | |
558 (skip-chars-forward " \t") | |
559 (if (interactive-p) | |
560 (let ((entry-line (buffer-substring | |
561 (point) | |
562 (min (+ (point) 60) | |
563 (progn (end-of-line) (point)))))) | |
564 (if (y-or-n-p (format "Kill `%s...' " entry-line)) | |
565 (progn | |
566 (funcall kill-op start level) | |
567 (message "Killed")) | |
568 (message "Aborted"))) | |
569 (funcall kill-op start level))) | |
570 (message | |
571 "(rolo-kill): Entry found but file not writable: '%s'" file) | |
572 (beep))) | |
573 (message "(rolo-kill): '%s' not found." name) | |
574 (beep)) | |
575 killed)) | |
576 | |
577 (defun rolo-mail-to () | |
578 "Start composing mail addressed to the first e-mail address at or after point." | |
579 (interactive) | |
580 (let ((opoint (point)) button) | |
581 (skip-chars-backward "^ \t\n\r<>") | |
582 (if (and (re-search-forward mail-address-regexp nil t) | |
583 (goto-char (match-beginning 1)) | |
584 (setq button (ibut:at-p))) | |
585 (hui:hbut-act button) | |
586 (goto-char opoint) | |
587 (beep) | |
588 (message "(rolo-mail-to): Invalid buffer or no e-mail address found")))) | |
589 | |
590 (defun rolo-next-match () | |
591 "Move point forward to the start of the next rolodex search match." | |
592 (interactive) | |
593 (if (not (stringp rolo-match-regexp)) | |
594 (error "(rolo-next-match): Invoke a rolodex search expression first")) | |
595 (let ((start (point)) | |
596 (case-fold-search t)) | |
597 (if (looking-at rolo-match-regexp) | |
598 (goto-char (match-end 0))) | |
599 (if (re-search-forward rolo-match-regexp nil t) | |
600 (goto-char (match-beginning 0)) | |
601 (goto-char start) | |
602 (error | |
603 "(rolo-next-match): No following matches for \"%s\"" rolo-match-regexp)))) | |
604 | |
605 (defun rolo-previous-match () | |
606 "Move point back to the start of the previous rolodex search match." | |
607 (interactive) | |
608 (if (not (stringp rolo-match-regexp)) | |
609 (error "(rolo-previous-match): Invoke a rolodex search expression first")) | |
610 (let ((case-fold-search t)) | |
611 (if (re-search-backward rolo-match-regexp nil t) | |
612 nil | |
613 (error | |
614 "(rolo-previous-match): No prior matches for \"%s\"" rolo-match-regexp)))) | |
615 | |
616 (defun rolo-quit () | |
617 "Quit from the rolodex match buffer and restore the prior frame display." | |
618 (interactive) | |
619 (bury-buffer) | |
620 (if (and *rolo-wconfig* | |
621 (if (fboundp 'window-configuration-p) | |
622 (window-configuration-p *rolo-wconfig*) | |
623 t)) | |
624 (set-window-configuration *rolo-wconfig*))) | |
625 | |
626 ;;;###autoload | |
627 (defun rolo-sort (&optional rolo-file) | |
628 "Sorts up to 14 levels of entries in ROLO-FILE (default is personal rolo). | |
629 Assumes entries are delimited by one or more '*'characters. | |
630 Returns list of number of groupings at each entry level." | |
631 (interactive | |
632 (list (let ((default "") | |
633 (file)) | |
634 (setq file | |
635 (completing-read | |
636 (format "Sort rolo file (default %s): " | |
637 (file-name-nondirectory | |
638 (setq default | |
639 (if (and buffer-file-name | |
640 (memq | |
641 t (mapcar | |
642 (function | |
643 (lambda (file) | |
644 (equal buffer-file-name | |
645 (expand-file-name file)))) | |
646 rolo-file-list))) | |
647 buffer-file-name | |
648 (car rolo-file-list))))) | |
649 (mapcar 'list rolo-file-list))) | |
650 (if (string= file "") default file)))) | |
651 (if (or (not rolo-file) (equal rolo-file "")) | |
652 (setq rolo-file (car rolo-file-list))) | |
653 (if (not (and (stringp rolo-file) (file-readable-p rolo-file))) | |
654 (error "(rolo-sort): Invalid or unreadable file: %s" rolo-file)) | |
655 (let ((level-regexp (regexp-quote "**************")) | |
656 (entries-per-level-list) | |
657 (n)) | |
658 (while (not (equal level-regexp "")) | |
659 (setq n (rolo-sort-level rolo-file level-regexp)) | |
660 (if (or (/= n 0) entries-per-level-list) | |
661 (setq entries-per-level-list | |
662 (append (list n) entries-per-level-list))) | |
663 (setq level-regexp (substring level-regexp 0 (- (length level-regexp) 2)))) | |
664 entries-per-level-list)) | |
665 | |
666 (defun rolo-sort-level (rolo-file level-regexp &optional max-groupings) | |
667 "Sorts groupings of entries in ROLO-FILE at hierarchy level LEVEL-REGEXP. | |
668 To a maximum of optional MAX-GROUPINGS. Nil value of MAX-GROUPINGS means all | |
669 groupings at the given level. LEVEL-REGEXP should simply match the text of | |
670 any rolodex entry of the given level, not the beginning of a line (^); an | |
671 example, might be (regexp-quote \"**\") to match level two. Returns number | |
672 of groupings sorted." | |
673 (interactive "sRolodex file to sort: \nRegexp for level's entries: \nP") | |
674 (rolo-map-level | |
675 (function (lambda (start end) (sort-lines nil start end))) | |
676 rolo-file | |
677 level-regexp | |
678 max-groupings)) | |
679 | |
680 (defun rolo-toggle-narrow-to-entry () | |
681 "Toggle between display of current entry and display of all matched entries. | |
682 Useful when bound to a mouse key." | |
683 (interactive) | |
684 (if (rolo-narrowed-p) | |
685 (widen) | |
686 (if (or (looking-at rolo-entry-regexp) | |
687 (re-search-backward rolo-entry-regexp nil t)) | |
688 (progn (forward-char) | |
689 (narrow-to-region (1- (point)) (rolo-display-to-entry-end))))) | |
690 (rolo-shrink-window) | |
691 (goto-char (point-min))) | |
692 | |
693 (defun rolo-word (string | |
694 &optional max-matches rolo-file count-only no-display) | |
695 "Display rolodex entries with whole word matches for STRING. | |
696 To a maximum of optional prefix arg MAX-MATCHES, in file(s) from optional | |
697 ROLO-FILE or rolo-file-list. Default is to find all matching entries. Each | |
698 entry is displayed with all of its sub-entries. Optional COUNT-ONLY non-nil | |
699 means don't retrieve and don't display matching entries. Optional NO-DISPLAY | |
700 non-nil means retrieve entries but don't display. | |
701 | |
702 Nil value of MAX-MATCHES means find all matches, t value means find all matches | |
703 but omit file headers, negative values mean find up to the inverse of that | |
704 number of entries and omit file headers. | |
705 | |
706 Returns number of entries matched. See also documentation for the variable | |
707 rolo-file-list." | |
708 (interactive "sRolodex whole words to match: \nP") | |
709 (let ((total-matches (rolo-grep (format "\\b%s\\b" (regexp-quote string)) | |
710 max-matches | |
711 rolo-file count-only no-display))) | |
712 (if (interactive-p) | |
713 (message "%s matching entr%s found in rolodex." | |
714 (if (= total-matches 0) "No" total-matches) | |
715 (if (= total-matches 1) "y" "ies"))) | |
716 total-matches)) | |
717 | |
718 ;;;###autoload | |
719 (defun rolo-yank (name &optional regexp-p) | |
720 "Inserts at point the first rolodex entry matching NAME. | |
721 With optional prefix arg, REGEXP-P, treats NAME as a regular expression instead | |
722 of a string." | |
723 (interactive "sName to insert record for: \nP") | |
724 (let ((rolo-display-buffer (current-buffer)) | |
725 (start (point)) | |
726 found) | |
727 (save-excursion | |
728 (setq found (if regexp-p | |
729 (rolo-grep name -1) | |
730 (rolo-grep (regexp-quote name) -1)))) | |
731 ;; Let user reformat the region just yanked. | |
732 (if (and (= found 1) (fboundp wrolo-yank-reformat-function)) | |
733 (funcall wrolo-yank-reformat-function start (point))) | |
734 found)) | |
735 | |
736 ;;; ************************************************************************ | |
737 ;;; Public functions | |
738 ;;; ************************************************************************ | |
739 | |
740 (defun rolo-fgrep-file (rolo-buf string &optional max-matches count-only) | |
741 "Retrieve entries in ROLO-BUF matching STRING to a maximum of optional MAX-MATCHES. | |
742 Nil value of MAX-MATCHES means find all matches, t value means find all matches | |
743 but omit file headers, negative values mean find up to the inverse of that | |
744 number of entries and omit file headers. Optional COUNT-ONLY non-nil | |
745 means don't retrieve matching entries. | |
746 Returns number of matching entries found." | |
747 (rolo-grep-file rolo-buf (regexp-quote string) max-matches count-only)) | |
748 | |
749 (defun rolo-grep-file (rolo-buf regexp &optional max-matches count-only) | |
750 "Retrieve entries in ROLO-BUF matching REGEXP to a maximum of optional MAX-MATCHES. | |
751 Nil value of MAX-MATCHES means find all matches, t value means find all matches | |
752 but omit file headers, negative values mean find up to the inverse of that | |
753 number of entries and omit file headers. Optional COUNT-ONLY non-nil | |
754 means don't retrieve matching entries. | |
755 Returns number of matching entries found." | |
756 ;; | |
757 ;; Save regexp as last rolodex search expression. | |
758 (setq rolo-match-regexp regexp) | |
759 ;; | |
760 (let ((new-buf-p) (actual-buf)) | |
761 (if (and (or (null max-matches) (eq max-matches t) (integerp max-matches)) | |
762 (or (setq actual-buf (rolo-buffer-exists-p rolo-buf)) | |
763 (if (file-exists-p rolo-buf) | |
764 (setq actual-buf (find-file-noselect rolo-buf t) | |
765 new-buf-p t)))) | |
766 (let ((hdr-pos) (num-found 0) (curr-entry-level) | |
767 (incl-hdr t)) | |
768 (if max-matches | |
769 (cond ((eq max-matches t) | |
770 (setq incl-hdr nil max-matches nil)) | |
771 ((< max-matches 0) | |
772 (setq incl-hdr nil | |
773 max-matches (- max-matches))))) | |
774 (set-buffer actual-buf) | |
775 (if new-buf-p (setq buffer-read-only t)) | |
776 (save-excursion | |
777 (save-restriction | |
778 (widen) | |
779 (goto-char 1) | |
780 ;; Ensure no entries in outline mode are hidden. | |
781 ;; Uses 'show-all' function from outline.el. | |
782 (and (search-forward "\C-m" nil t) | |
783 (show-all)) | |
784 (if (re-search-forward rolo-hdr-regexp nil t 2) | |
785 (progn (forward-line) | |
786 (setq hdr-pos (cons (point-min) (point))))) | |
787 (re-search-forward rolo-entry-regexp nil t) | |
788 (while (and (or (null max-matches) (< num-found max-matches)) | |
789 (re-search-forward regexp nil t)) | |
790 (re-search-backward rolo-entry-regexp nil t) | |
791 (let ((start (point)) | |
792 (next-entry-exists)) | |
793 (re-search-forward rolo-entry-regexp nil t) | |
794 (setq curr-entry-level (buffer-substring start (point))) | |
795 (rolo-to-entry-end t curr-entry-level) | |
796 (or count-only | |
797 (if (and (= num-found 0) incl-hdr) | |
798 (let* ((src (or (buffer-file-name actual-buf) | |
799 actual-buf)) | |
800 (src-line | |
801 (format | |
802 (concat (if (boundp 'hbut:source-prefix) | |
803 hbut:source-prefix | |
804 "@loc> ") | |
805 "%s") | |
806 (prin1-to-string src)))) | |
807 (set-buffer rolo-display-buffer) | |
808 (goto-char (point-max)) | |
809 (if hdr-pos | |
810 (progn | |
811 (insert-buffer-substring | |
812 actual-buf (car hdr-pos) (cdr hdr-pos)) | |
813 (insert src-line "\n\n")) | |
814 (insert (format rolo-hdr-format src-line))) | |
815 (set-buffer actual-buf)))) | |
816 (setq num-found (1+ num-found)) | |
817 (or count-only | |
818 (rolo-add-match rolo-display-buffer regexp start (point))))))) | |
819 (rolo-kill-buffer actual-buf) | |
820 num-found) | |
821 0))) | |
822 | |
823 (defun rolo-map-level (func rolo-buf level-regexp &optional max-groupings) | |
824 "Perform FUNC on groupings of ROLO-BUF entries at level LEVEL-REGEXP, | |
825 to a maximum of optional argument MAX-GROUPINGS. Nil value of MAX-GROUPINGS | |
826 means all groupings at the given level. FUNC should take two arguments, the | |
827 start and the end of the region that it should manipulate. LEVEL-REGEXP | |
828 should simply match the text of any rolodex entry of the given level, not the | |
829 beginning of a line (^); an example, might be (regexp-quote \"**\") to match | |
830 level two. Returns number of groupings matched." | |
831 (let ((actual-buf)) | |
832 (if (and (or (null max-groupings) (< 0 max-groupings)) | |
833 (or (setq actual-buf (rolo-buffer-exists-p rolo-buf)) | |
834 (if (file-exists-p rolo-buf) | |
835 (progn (setq actual-buf (find-file-noselect rolo-buf t)) | |
836 t)))) | |
837 (progn | |
838 (set-buffer actual-buf) | |
839 (let ((num-found 0) | |
840 (exact-level-regexp (concat "^\\(" level-regexp "\\)[ \t\n]")) | |
841 (outline-regexp rolo-entry-regexp) | |
842 (buffer-read-only) | |
843 (level-len)) | |
844 ;; Load 'outline' library since its functions are used here. | |
845 (if (not (boundp 'outline-mode-map)) | |
846 (load-library "outline")) | |
847 (goto-char (point-min)) | |
848 ;; Pass buffer header if it exists | |
849 (if (re-search-forward rolo-hdr-regexp nil t 2) | |
850 (forward-line)) | |
851 (while (and (or (null max-groupings) (< num-found max-groupings)) | |
852 (re-search-forward exact-level-regexp nil t)) | |
853 (setq num-found (1+ num-found)) | |
854 (let* ((opoint (prog1 (point) (beginning-of-line))) | |
855 (grouping-start (point)) | |
856 (start grouping-start) | |
857 (level-len (or level-len (- (1- opoint) start))) | |
858 (next-level-len) | |
859 (next-entry-exists) | |
860 (grouping-end) | |
861 (no-subtree)) | |
862 (while (and (progn | |
863 (if (setq next-entry-exists | |
864 (re-search-forward | |
865 rolo-entry-regexp nil t 2)) | |
866 (setq next-level-len | |
867 (- (point) | |
868 (progn (beginning-of-line) | |
869 (point))) | |
870 grouping-end | |
871 (< next-level-len level-len) | |
872 no-subtree | |
873 (<= next-level-len level-len)) | |
874 (setq grouping-end t no-subtree t) | |
875 (goto-char (point-max))) | |
876 (let ((end (point))) | |
877 (goto-char start) | |
878 (hide-subtree) ; And hide multiple entry lines | |
879 ;; Move to start of next entry at equal | |
880 ;; or higher level. | |
881 (setq start | |
882 (if no-subtree | |
883 end | |
884 (if (re-search-forward | |
885 rolo-entry-regexp nil t) | |
886 (progn (beginning-of-line) (point)) | |
887 (point-max)))) | |
888 ;; Remember last expression in 'progn' | |
889 ;; must always return non-nil. | |
890 (goto-char start))) | |
891 (not grouping-end))) | |
892 (let ((end (point))) | |
893 (goto-char grouping-start) | |
894 (funcall func grouping-start end) | |
895 (goto-char end)))) | |
896 (show-all) | |
897 (rolo-kill-buffer actual-buf) | |
898 num-found)) | |
899 0))) | |
900 | |
901 ;;; ************************************************************************ | |
902 ;;; Private functions | |
903 ;;; ************************************************************************ | |
904 | |
905 (defun rolo-add-match (rolo-matches-buffer regexp start end) | |
906 "Insert before point in ROLO-MATCHES-BUFFER an entry matching REGEXP from the current region between START to END." | |
907 (let ((rolo-buf (current-buffer)) | |
908 opoint) | |
909 (set-buffer (get-buffer-create rolo-matches-buffer)) | |
910 (setq opoint (point)) | |
911 (insert-buffer-substring rolo-buf start end) | |
912 (rolo-highlight-matches regexp opoint (point)) | |
913 (set-buffer rolo-buf))) | |
914 | |
915 (defun rolo-buffer-exists-p (rolo-buf) | |
916 "Returns buffer given by ROLO-BUF or nil. | |
917 ROLO-BUF may be a file-name, buffer-name, or buffer." | |
918 (car (memq (get-buffer (or (and (stringp rolo-buf) | |
919 (get-file-buffer rolo-buf)) | |
920 rolo-buf)) | |
921 (buffer-list)))) | |
922 | |
923 (defun rolo-display-to-entry-end () | |
924 "Go to end of current entry, ignoring sub-entries." | |
925 (if (re-search-forward (concat rolo-hdr-regexp "\\|" | |
926 rolo-entry-regexp) nil t) | |
927 (progn (beginning-of-line) (point)) | |
928 (goto-char (point-max)))) | |
929 | |
930 | |
931 (defun rolo-format-name (name-str first last) | |
932 "Reverse order of NAME-STR field given my regexp match field FIRST and LAST." | |
933 (if (match-beginning last) | |
934 (concat (substring name-str (match-beginning last) (match-end last)) | |
935 ", " | |
936 (substring name-str (match-beginning first) (match-end first))))) | |
937 | |
938 (defun rolo-highlight-matches (regexp start end) | |
939 "Highlight matches for REGEXP in region from START to END." | |
940 (if (fboundp 'hproperty:but-add) | |
941 (let ((hproperty:but-emphasize-p)) | |
942 (save-excursion | |
943 (goto-char start) | |
944 (while (re-search-forward regexp nil t) | |
945 (hproperty:but-add (match-beginning 0) (match-end 0) | |
946 (or rolo-highlight-face | |
947 hproperty:highlight-face))))))) | |
948 | |
949 (defun rolo-kill-buffer (&optional rolo-buf) | |
950 "Kills optional ROLO-BUF if unchanged and 'rolo-kill-buffers-after-use' is t. | |
951 Default is current buffer." | |
952 (or rolo-buf (setq rolo-buf (current-buffer))) | |
953 (and rolo-kill-buffers-after-use (not (buffer-modified-p rolo-buf)) | |
954 (kill-buffer rolo-buf))) | |
955 | |
956 (defun rolo-name-and-email () | |
957 "If point is in a mail message, returns list of (name email-addr) of sender. | |
958 Name is returned as 'last, first-and-middle'." | |
959 (let ((email) (name) (from)) | |
960 (save-window-excursion | |
961 (if (or (hmail:lister-p) (hnews:lister-p)) | |
962 (other-window 1)) | |
963 (save-excursion | |
964 (save-restriction | |
965 (goto-char (point-min)) | |
966 (if (search-forward "\n\n" nil t) | |
967 (narrow-to-region (point-min) (point))) | |
968 (setq email (mail-fetch-field "reply-to") | |
969 from (mail-fetch-field "from"))))) | |
970 (if from | |
971 (cond | |
972 ;; Match: email, email (name), email "name" | |
973 ((string-match | |
974 (concat "^\\([^\"<>() \t\n]+\\)" | |
975 "\\([ \t]*[(\"][ \t]*\\([^\"()]+\\)[ \t]+" | |
976 "\\([^\" \t()]+\\)[ \t]*[)\"]\\)?[ \t]*$") | |
977 from) | |
978 (setq name (rolo-format-name from 3 4)) | |
979 (or email (setq email (substring from (match-beginning 1) | |
980 (match-end 1))))) | |
981 ;; Match: <email>, name <email>, "name" <email> | |
982 ((string-match | |
983 (concat "^\\(\"?\\([^\"<>()\n]+\\)[ \t]+" | |
984 "\\([^\" \t()<>]+\\)\"?[ \t]+\\)?" | |
985 "<\\([^\"<>() \t\n]+\\)>[ \t]*$") | |
986 from) | |
987 (setq name (rolo-format-name from 2 3)) | |
988 (or email (setq email (substring from (match-beginning 4) | |
989 (match-end 4))))))) | |
990 (if (or name email) | |
991 (list name email)))) | |
992 | |
993 (defun rolo-name-at () | |
994 "If point is within an entry in 'rolo-display-buffer', returns entry, else nil." | |
995 (if (string-equal (buffer-name) rolo-display-buffer) | |
996 (save-excursion | |
997 (if (or (looking-at rolo-entry-regexp) | |
998 (progn (end-of-line) | |
999 (re-search-backward rolo-entry-regexp nil t))) | |
1000 (progn (goto-char (match-end 0)) | |
1001 (skip-chars-forward " \t") | |
1002 (if (or (looking-at "[^ \t\n\^M]+ ?, ?[^ \t\n\^M]+") | |
1003 (looking-at "\\( ?[^ \t\n\^M]+\\)+")) | |
1004 (buffer-substring (match-beginning 0) | |
1005 (match-end 0)))))))) | |
1006 | |
1007 (defun rolo-narrowed-p () | |
1008 (or (/= (point-min) 1) (/= (1+ (buffer-size)) (point-max)))) | |
1009 | |
1010 (defun rolo-save-buffer (&optional rolo-buf) | |
1011 "Saves optional ROLO-BUF if changed and 'rolo-save-buffers-after-use' is t. | |
1012 Default is current buffer. Used, for example, after a rolo entry is killed." | |
1013 (or rolo-buf (setq rolo-buf (current-buffer))) | |
1014 (and rolo-save-buffers-after-use (buffer-modified-p rolo-buf) | |
1015 (set-buffer rolo-buf) (save-buffer))) | |
1016 | |
1017 (defun rolo-shrink-window () | |
1018 (let* ((lines (count-lines (point-min) (point-max))) | |
1019 (height (window-height)) | |
1020 (window-min-height 2) | |
1021 (desired-shrinkage (1- (min (- height lines))))) | |
1022 (and (>= lines 0) | |
1023 (/= desired-shrinkage 0) | |
1024 (> (frame-height) (1+ height)) | |
1025 (shrink-window | |
1026 (if (< desired-shrinkage 0) | |
1027 (max desired-shrinkage (- height (/ (frame-height) 2))) | |
1028 (min desired-shrinkage (- height window-min-height))))))) | |
1029 | |
1030 (defun rolo-to (name &optional file-list) | |
1031 "Moves point to entry for NAME within optional FILE-LIST. | |
1032 'rolo-file-list' is used as default when FILE-LIST is nil. | |
1033 Leaves point immediately after match for NAME within entry. | |
1034 Switches internal current buffer but does not alter the frame. | |
1035 Returns point where matching entry begins or nil if not found." | |
1036 (or file-list (setq file-list rolo-file-list)) | |
1037 (let ((found) file) | |
1038 (while (and (not found) file-list) | |
1039 (setq file (car file-list) | |
1040 file-list (cdr file-list)) | |
1041 (cond ((and file (or (not (stringp file)) (string= file ""))) | |
1042 (error "(rolo-to): Invalid file: '%s'" file)) | |
1043 ((and (file-exists-p file) (not (file-readable-p file))) | |
1044 (error "(rolo-to): File not readable: '%s'" file))) | |
1045 (set-buffer (or (get-file-buffer file) (find-file-noselect file))) | |
1046 (let ((case-fold-search t) (real-name name) (parent "") (level) end) | |
1047 (widen) (goto-char 1) | |
1048 (while (setq end (string-match "/" name)) | |
1049 (setq level nil | |
1050 parent (substring name 0 end) | |
1051 name (substring name (min (1+ end) (length name)))) | |
1052 (cond ((progn | |
1053 (while (and (not level) (search-forward parent nil t)) | |
1054 (save-excursion | |
1055 (beginning-of-line) | |
1056 (if (looking-at | |
1057 (concat "\\(" rolo-entry-regexp "\\)[ \t]*" | |
1058 (regexp-quote parent))) | |
1059 (setq level (buffer-substring (match-beginning 1) | |
1060 (match-end 1)))))) | |
1061 level)) | |
1062 ((equal name real-name));; Try next file. | |
1063 (t;; Found parent but not child | |
1064 (setq buffer-read-only nil) | |
1065 (rolo-to-buffer (current-buffer)) | |
1066 (error "(rolo-to): '%s' part of name not found in \"%s\"." | |
1067 parent file))) | |
1068 (if level | |
1069 (narrow-to-region (point) | |
1070 (save-excursion | |
1071 (rolo-to-entry-end t level) (point))))) | |
1072 (goto-char (point-min)) | |
1073 (while (and (search-forward name nil t) | |
1074 (not (save-excursion | |
1075 (beginning-of-line) | |
1076 (setq found | |
1077 (if (looking-at | |
1078 (concat "\\(" rolo-entry-regexp | |
1079 "\\)[ \t]*" | |
1080 (regexp-quote name))) | |
1081 (point)))))))) | |
1082 (or found (rolo-kill-buffer))) ;; conditionally kill | |
1083 (widen) | |
1084 found)) | |
1085 | |
1086 (defun rolo-to-buffer (buffer &optional other-window-flag frame) | |
1087 "Pop to BUFFER." | |
1088 (cond (hyperb:lemacs-p | |
1089 (pop-to-buffer buffer other-window-flag | |
1090 ;; default is to use selected frame | |
1091 (or frame (selected-frame)))) | |
1092 (t (pop-to-buffer buffer other-window-flag)))) | |
1093 | |
1094 (defun rolo-to-entry-end (&optional include-sub-entries curr-entry-level) | |
1095 "Goes to end of whole entry if optional INCLUDE-SUB-ENTRIES is non-nil. | |
1096 CURR-ENTRY-LEVEL is a string whose length is the same as the last found entry | |
1097 header. If INCLUDE-SUB-ENTRIES is nil, CURR-ENTRY-LEVEL is not needed. | |
1098 Returns current point." | |
1099 (while (and (setq next-entry-exists | |
1100 (re-search-forward rolo-entry-regexp nil t)) | |
1101 include-sub-entries | |
1102 (> (- (point) (save-excursion | |
1103 (beginning-of-line) | |
1104 (point))) | |
1105 (length curr-entry-level)))) | |
1106 (if next-entry-exists | |
1107 (progn (beginning-of-line) (point)) | |
1108 (goto-char (point-max)))) | |
1109 | |
1110 (defun wrolo-mode () | |
1111 "Major mode for the rolodex match buffer. | |
1112 Calls the functions given by `wrolo-mode-hook'. | |
1113 \\{wrolo-mode-map}" | |
1114 (interactive) | |
1115 (setq major-mode 'wrolo-mode | |
1116 mode-name "Rolodex") | |
1117 (use-local-map wrolo-mode-map) | |
1118 ;; | |
1119 ;; Loads menus under non-tty InfoDock, XEmacs or Emacs19; does nothing | |
1120 ;; otherwise. | |
1121 (and (not (featurep 'wrolo-menu)) hyperb:window-system | |
1122 (or hyperb:lemacs-p hyperb:emacs19-p) (require 'wrolo-menu)) | |
1123 ;; | |
1124 (if (not (fboundp 'outline-minor-mode)) | |
1125 nil | |
1126 (outline-minor-mode 1)) | |
1127 (run-hooks 'wrolo-mode-hook)) | |
1128 | |
1129 ;;; ************************************************************************ | |
1130 ;;; Private variables | |
1131 ;;; ************************************************************************ | |
1132 | |
1133 (defvar rolo-display-buffer "*Rolodex*" | |
1134 "Buffer used to display set of last matching rolodex entries.") | |
1135 | |
1136 (defvar rolo-entry-regexp "^\\*+" | |
1137 "Regular expression to match the beginning of a rolodex entry. | |
1138 This pattern must match the beginning of the line. Entries may be nested | |
1139 through the use of increasingly longer beginning patterns.") | |
1140 | |
1141 (defconst rolo-hdr-format | |
1142 (concat | |
1143 "======================================================================\n" | |
1144 "%s\n" | |
1145 "======================================================================\n") | |
1146 "Header to insert preceding a file's first rolodex entry match when | |
1147 file has none of its own. Used with one argument, the file name." | |
1148 ) | |
1149 | |
1150 (defconst rolo-hdr-regexp "^===" | |
1151 "Regular expression to match the first and last lines of rolodex file headers. | |
1152 This header is inserted into rolo-display-buffer before any entries from the | |
1153 file are added.") | |
1154 | |
1155 (defconst rolo-match-regexp nil | |
1156 "Last regular expression used to search the rolodex. | |
1157 Nil before a search is done. | |
1158 String search expressions are converted to regular expressions.") | |
1159 | |
1160 (defvar *rolo-wconfig* nil | |
1161 "Saves frame's window configuration prior to a rolodex search.") | |
1162 | |
1163 (defvar wrolo-mode-map nil | |
1164 "Keymap for the rolodex match buffer.") | |
1165 | |
1166 (if wrolo-mode-map | |
1167 nil | |
1168 (setq wrolo-mode-map (make-keymap)) | |
1169 (if (fboundp 'set-keymap-name) | |
1170 (set-keymap-name wrolo-mode-map 'wrolo-mode-map)) | |
1171 (suppress-keymap wrolo-mode-map) | |
1172 (define-key wrolo-mode-map "<" 'beginning-of-buffer) | |
1173 (define-key wrolo-mode-map ">" 'end-of-buffer) | |
1174 (define-key wrolo-mode-map "." 'beginning-of-buffer) | |
1175 (define-key wrolo-mode-map "," 'end-of-buffer) | |
1176 (define-key wrolo-mode-map "?" 'describe-mode) | |
1177 (define-key wrolo-mode-map "\177" 'scroll-down) | |
1178 (define-key wrolo-mode-map " " 'scroll-up) | |
1179 (define-key wrolo-mode-map "a" 'show-all) | |
1180 (define-key wrolo-mode-map "b" 'outline-backward-same-level) | |
1181 (define-key wrolo-mode-map "e" 'rolo-edit-entry) | |
1182 (define-key wrolo-mode-map "f" 'outline-forward-same-level) | |
1183 (define-key wrolo-mode-map "h" 'hide-subtree) | |
1184 (define-key wrolo-mode-map "m" 'rolo-mail-to) | |
1185 (define-key wrolo-mode-map "n" 'outline-next-visible-heading) | |
1186 (define-key wrolo-mode-map "p" 'outline-previous-visible-heading) | |
1187 (define-key wrolo-mode-map "q" 'rolo-quit) | |
1188 (define-key wrolo-mode-map "r" 'rolo-previous-match) | |
1189 (define-key wrolo-mode-map "s" 'show-subtree) | |
1190 (define-key wrolo-mode-map "\M-s" 'rolo-isearch) | |
1191 (define-key wrolo-mode-map "t" 'hide-body) | |
1192 (define-key wrolo-mode-map "\C-i" 'rolo-next-match) ;; {TAB} | |
1193 (define-key wrolo-mode-map "\M-\C-i" 'rolo-previous-match) ;; {M-TAB} | |
1194 (define-key wrolo-mode-map "u" 'outline-up-heading) | |
1195 ) | |
1196 | |
1197 (provide 'wrolo) |