Mercurial > hg > xemacs-beta
comparison lisp/hyperbole/kotl/kfile.el @ 0:376386a54a3c r19-14
Import from CVS: tag r19-14
author | cvs |
---|---|
date | Mon, 13 Aug 2007 08:45:50 +0200 |
parents | |
children | c53a95d3c46d |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:376386a54a3c |
---|---|
1 ;;!emacs | |
2 ;; | |
3 ;; FILE: kfile.el | |
4 ;; SUMMARY: Save and restore kotls from files. | |
5 ;; USAGE: GNU Emacs V19 Lisp Library | |
6 ;; KEYWORDS: outlines, wp | |
7 ;; | |
8 ;; AUTHOR: Bob Weiner & Kellie Clark | |
9 ;; | |
10 ;; ORIG-DATE: 10/31/93 | |
11 ;; LAST-MOD: 1-Nov-95 at 00:46:41 by Bob Weiner | |
12 ;;; ************************************************************************ | |
13 ;;; Other required Elisp libraries | |
14 ;;; ************************************************************************ | |
15 | |
16 (mapcar 'require '(kproperty kotl-mode)) | |
17 | |
18 ;; Loads menus under non-tty InfoDock, XEmacs or Emacs19; does nothing | |
19 ;; otherwise. | |
20 (and (not (featurep 'kmenu)) hyperb:window-system | |
21 (or hyperb:lemacs-p hyperb:emacs19-p) (require 'kmenu)) | |
22 | |
23 ;;; ************************************************************************ | |
24 ;;; Public variables | |
25 ;;; ************************************************************************ | |
26 | |
27 (defconst kfile:version "Kotl-4.0" | |
28 "Version number of persistent data format used for saving koutlines.") | |
29 | |
30 ;;; ************************************************************************ | |
31 ;;; Entry Points | |
32 ;;; ************************************************************************ | |
33 | |
34 ;;;###autoload | |
35 (defun kfile:find (file-name) | |
36 "Find a file FILE-NAME containing a kotl or create one if none exists. | |
37 Return the new kview." | |
38 (interactive | |
39 (list (kfile:read-name | |
40 "Find koutline file: " nil))) | |
41 (let ((existing-file (file-exists-p file-name)) | |
42 buffer) | |
43 (and existing-file | |
44 (not (file-readable-p file-name)) | |
45 (error | |
46 "(kfile:find): \"%s\" is not readable. Check permissions." | |
47 file-name)) | |
48 (setq buffer (find-file file-name)) | |
49 ;; Finding the file may have already done a kfile:read as invoked through | |
50 ;; kotl-mode via a file local variable setting. If so, don't read it | |
51 ;; again. | |
52 (if (kview:is-p kview) | |
53 nil | |
54 (kfile:read buffer existing-file)) | |
55 (or (eq major-mode 'kotl-mode) (kotl-mode)) | |
56 kview)) | |
57 | |
58 ;;;###autoload | |
59 (defun kfile:view (file-name) | |
60 "View an existing kotl version-2 file FILE-NAME in a read-only mode." | |
61 (interactive | |
62 (list (kfile:read-name | |
63 "View koutline file: " t))) | |
64 (let ((existing-file (file-exists-p file-name))) | |
65 (if existing-file | |
66 (if (not (file-readable-p file-name)) | |
67 (error | |
68 "(kfile:view): \"%s\" is not readable. Check permissions." | |
69 file-name)) | |
70 (error "(kfile:view): \"%s\" does not exist.")) | |
71 (view-file file-name)) | |
72 (kfile:narrow-to-kcells) | |
73 (goto-char (point-min))) | |
74 | |
75 ;;; ************************************************************************ | |
76 ;;; Public functions | |
77 ;;; ************************************************************************ | |
78 | |
79 (defun kfile:create (buffer) | |
80 "Create a new koutline file attached to BUFFER, with a single empty level 1 kotl cell. | |
81 Return file's kview." | |
82 (or buffer (setq buffer (current-buffer))) | |
83 (if (not (bufferp buffer)) | |
84 (error "(kfile:create): Invalid buffer argument, %s" buffer)) | |
85 (set-buffer buffer) | |
86 (if buffer-read-only | |
87 (error "(kfile:create): %s is read-only" buffer)) | |
88 (widen) | |
89 | |
90 (let ((empty-p (zerop (buffer-size))) | |
91 import-from view standard-output) | |
92 | |
93 (if (not empty-p) | |
94 ;; This is a foreign file whose elements must be converted into | |
95 ;; koutline cells. | |
96 (progn (setq import-from (kimport:copy-and-set-buffer buffer)) | |
97 (set-buffer buffer) | |
98 (erase-buffer))) ;; We copied the contents to `import-from'. | |
99 | |
100 (setq view (kview:create (buffer-name buffer)) | |
101 standard-output (current-buffer)) | |
102 (goto-char (point-min)) | |
103 (princ ";; -*- Mode: kotl -*- \n") | |
104 (prin1 kfile:version) | |
105 (princ " ;; file-format\n\^_\n") | |
106 ;; Ensure that last cell has two newlines after it so that | |
107 ;; kfile:insert-attributes finds it. | |
108 (goto-char (point-max)) | |
109 (princ "\n\n\^_\n") | |
110 (princ "\^_\n;; depth-first kcell attributes\n") | |
111 ;; Ensure that display is narrowed to cell region only. | |
112 (kfile:narrow-to-kcells) | |
113 (goto-char (point-min)) | |
114 (if empty-p | |
115 ;; This is a new koutline file. Always need at least one visible | |
116 ;; cell within a view. Insert initial empty cell. | |
117 (progn (kview:add-cell "1" 1) | |
118 ;; Mark view unmodified, so if kill right away, there is no | |
119 ;; prompt. | |
120 (set-buffer-modified-p nil) | |
121 ;; Move to first cell. | |
122 (goto-char (point-min)) | |
123 (goto-char (kcell-view:start))) | |
124 ;; Import buffer. Next line is necessary or the importation will fail. | |
125 (delete-region (point-min) (point-max)) | |
126 ;; Import foreign buffer as koutline cells. | |
127 (kimport:file import-from (current-buffer)) | |
128 ;; If import buffer name starts with a space, kill it, as it is no | |
129 ;; longer needed. | |
130 (if (= ?\ (aref (buffer-name import-from) 0)) | |
131 (kill-buffer import-from))) | |
132 | |
133 view)) | |
134 | |
135 ;;;###autoload | |
136 (defun kfile:is-p () | |
137 "Iff current buffer contains an unformatted or formatted koutline, return file format version string, else nil." | |
138 (let (ver-string) | |
139 (save-excursion | |
140 (save-restriction | |
141 (widen) | |
142 (goto-char (point-min)) | |
143 (condition-case () | |
144 (progn | |
145 (setq ver-string (read (current-buffer))) | |
146 (and (stringp ver-string) (string-match "^Kotl-" ver-string) | |
147 ver-string)) | |
148 (error nil)))))) | |
149 | |
150 (defun kfile:read (buffer existing-file-p) | |
151 "Create a new kotl view by reading BUFFER or create an empty view when EXISTING-FILE-P is nil. | |
152 Return the new view." | |
153 (let (ver-string) | |
154 (cond ((not (bufferp buffer)) | |
155 (error "(kfile:read): Argument must be a buffer, '%s'." buffer)) | |
156 ((not existing-file-p) | |
157 (kfile:create buffer)) | |
158 ((progn | |
159 (set-buffer buffer) | |
160 (not (setq ver-string (kfile:is-p)))) | |
161 (error "(kfile:read): '%s' is not a koutline file." buffer)) | |
162 ((equal ver-string "Kotl-4.0") | |
163 (kfile:read-v4-or-v3 buffer nil)) | |
164 ((equal ver-string "Kotl-3.0") | |
165 (kfile:read-v4-or-v3 buffer t)) | |
166 ((equal ver-string "Kotl-2.0") | |
167 (kfile:read-v2 buffer)) | |
168 ((equal ver-string "Kotl-1.0") | |
169 (error "(kfile:read): V1 koutlines are no longer supported")) | |
170 (t (error "(kfile:read): '%s' has unknown kotl version, %s." | |
171 buffer ver-string))))) | |
172 | |
173 (defun kfile:read-v2 (buffer) | |
174 "Create a kotl view by reading kotl version-2 BUFFER. Return the new view." | |
175 (let ((standard-input buffer) | |
176 cell-count label-type label-min-width label-separator | |
177 level-indent cell-data kotl-structure view kcell-list) | |
178 (widen) | |
179 (goto-char (point-min)) | |
180 ;; Skip past cell contents here. | |
181 (search-forward "\n\^_" nil t 2) | |
182 ;; Read rest of file data. | |
183 (setq cell-count (read) | |
184 label-type (read) | |
185 label-min-width (read) | |
186 label-separator (read) | |
187 level-indent (read) | |
188 cell-data (read) | |
189 kotl-structure (read)) | |
190 ;; | |
191 ;; kcell-list is a depth-first list of kcells to be attached to the cell | |
192 ;; contents within the kview down below. | |
193 (setq kcell-list (kfile:build-structure-v2 kotl-structure cell-data) | |
194 view (kview:create (buffer-name buffer) cell-count label-type | |
195 level-indent label-separator label-min-width)) | |
196 ;; | |
197 (kfile:narrow-to-kcells) | |
198 (goto-char (point-min)) | |
199 ;; | |
200 ;; Add attributes to cells. | |
201 (kfile:insert-attributes-v2 view kcell-list) | |
202 ;; | |
203 ;; Mark view unmodified and move to first cell. | |
204 (set-buffer-modified-p nil) | |
205 (goto-char (point-min)) | |
206 (goto-char (kcell-view:start)) | |
207 view)) | |
208 | |
209 (defun kfile:read-v4-or-v3 (buffer v3-flag) | |
210 "Create a koutline view by reading version-4 BUFFER. Return the new view. | |
211 If V3-FLAG is true, read as a version-3 buffer." | |
212 (let ((standard-input buffer) | |
213 cell-count label-type label-min-width label-separator | |
214 level-indent cell-data view) | |
215 (widen) | |
216 (goto-char (point-min)) | |
217 ;; Skip past cell contents here. | |
218 (search-forward "\n\^_" nil t 2) | |
219 ;; Read rest of file data. | |
220 (if v3-flag | |
221 nil ;; V3 files did not store viewspecs. | |
222 (kvspec:initialize) | |
223 (setq kvspec:current (read))) | |
224 (setq cell-count (read) | |
225 label-type (read) | |
226 label-min-width (read) | |
227 label-separator (read) | |
228 level-indent (read) | |
229 cell-data (read)) | |
230 ;; | |
231 (setq view (kview:create (buffer-name buffer) cell-count label-type | |
232 level-indent label-separator label-min-width)) | |
233 ;; | |
234 (kfile:narrow-to-kcells) | |
235 (goto-char (point-min)) | |
236 ;; | |
237 ;; Add attributes to cells. | |
238 (kfile:insert-attributes-v3 view cell-data) | |
239 ;; | |
240 ;; Mark view unmodified and move to first cell. | |
241 (set-buffer-modified-p nil) | |
242 (goto-char (point-min)) | |
243 (goto-char (kcell-view:start)) | |
244 view)) | |
245 | |
246 (defun kfile:update (&optional visible-only-p) | |
247 "Update kfile internal structure so that view is ready for saving to a file. | |
248 Leave outline file expanded with structure data showing unless optional | |
249 VISIBLE-ONLY-P is non-nil. Signal an error if kotl is not attached to a file." | |
250 (let* ((top (kview:top-cell kview)) | |
251 (file (kcell:get-attr top 'file)) | |
252 (label-type (kview:label-type kview)) | |
253 (label-min-width (kview:label-min-width kview)) | |
254 (label-separator (kview:label-separator kview)) | |
255 (level-indent (kview:level-indent kview)) | |
256 ;; If this happens to be non-nil, it is virtually impossible to save | |
257 ;; a file, so ensure it is nil. | |
258 (debug-on-error)) | |
259 (cond ((null file) | |
260 (error "(kfile:update): Current outline is not attached to a file.")) | |
261 ((not (file-writable-p file)) | |
262 (error "(kfile:update): File \"%s\" is not writable." file))) | |
263 (let* ((buffer-read-only) | |
264 (id-counter (kcell:get-attr top 'id-counter)) | |
265 (kotl-data (make-vector (1+ id-counter) nil)) | |
266 (standard-output (current-buffer)) | |
267 (opoint (set-marker (make-marker) (point))) | |
268 (kcell-num 1) | |
269 cell) | |
270 ;; | |
271 ;; Prepare cell data for saving. | |
272 (kfile:narrow-to-kcells) | |
273 (kview:map-tree | |
274 (function | |
275 (lambda (view) | |
276 (setq cell (kcell-view:cell)) | |
277 (aset kotl-data | |
278 kcell-num | |
279 (kotl-data:create cell)) | |
280 (setq kcell-num (1+ kcell-num)))) | |
281 kview t) | |
282 ;; Save top cell, 0, last since above loop may increment the total | |
283 ;; number of cells counter stored in it, if any invalid cells are | |
284 ;; encountered. | |
285 (aset kotl-data 0 (kotl-data:create top)) | |
286 (setq id-counter (kcell:get-attr top 'id-counter)) | |
287 ;; | |
288 (widen) | |
289 (goto-char (point-min)) | |
290 (if (search-forward "\n\^_\n" nil t) | |
291 (delete-region (point-min) (match-end 0))) | |
292 (princ ";; -*- Mode: kotl -*- \n") | |
293 (prin1 kfile:version) | |
294 (princ " ;; file-format\n\^_\n") | |
295 ;; Skip past cells. | |
296 (if (search-forward "\n\^_\n" nil t) | |
297 ;; Get rid of excess blank lines after last cell. | |
298 (progn (goto-char (match-beginning 0)) | |
299 (skip-chars-backward "\n") | |
300 (delete-region (point) (point-max))) | |
301 (goto-char (point-max))) | |
302 ;; Ensure that last cell has two newlines after it so that | |
303 ;; kfile:insert-attributes finds it. | |
304 (princ "\n\n\^_\n") | |
305 (princ (format (concat | |
306 "%S ;; kvspec:current\n%d ;; id-counter\n" | |
307 "%S ;; label-type\n%d ;; label-min-width\n" | |
308 "%S ;; label-separator\n%d ;; level-indent\n") | |
309 kvspec:current id-counter label-type label-min-width | |
310 label-separator level-indent)) | |
311 (princ "\^_\n;; depth-first kcell attributes\n") | |
312 (kfile:pretty-print kotl-data) | |
313 ;; | |
314 ;; Don't re-narrow buffer by default since this is used in | |
315 ;; write-contents-hooks after save-buffer has widened buffer. If | |
316 ;; buffer is narrowed here, only the narrowed portion will be saved to | |
317 ;; the file. Narrow as an option since saving only the portion of the | |
318 ;; file visible in a view may be useful in some situations. | |
319 (if visible-only-p (kfile:narrow-to-kcells)) | |
320 ;; | |
321 ;; Return point to its original position as given by the opoint marker. | |
322 (goto-char opoint) | |
323 (set-marker opoint nil) | |
324 nil))) | |
325 | |
326 ;;; Next function is adapted from 'file-write' of GNU Emacs 19, copyright FSF, | |
327 ;;; under the GPL. | |
328 (defun kfile:write (file) | |
329 "Write current outline to FILE." | |
330 (interactive "FWrite outline file: ") | |
331 (if (or (null file) (string-equal file "")) | |
332 (error "(kfile:write): Invalid file name, \"%s\"" file)) | |
333 ;; If arg is just a directory, use same file name, but in that directory. | |
334 (if (and (file-directory-p file) buffer-file-name) | |
335 (setq file (concat (file-name-as-directory file) | |
336 (file-name-nondirectory buffer-file-name)))) | |
337 (kcell:set-attr (kview:top-cell kview) 'file file) | |
338 (set-visited-file-name file) | |
339 ;; Set-visited-file-name clears local-write-file-hooks that we use to save | |
340 ;; koutlines properly, so reinitialize local variables. | |
341 (kotl-mode) | |
342 (set-buffer-modified-p t) | |
343 ;; This next line must come before the save-buffer since write-file-hooks | |
344 ;; can make use of it. | |
345 (kview:set-buffer-name kview (buffer-name)) | |
346 (save-buffer)) | |
347 | |
348 ;;; ************************************************************************ | |
349 ;;; Private functions | |
350 ;;; ************************************************************************ | |
351 | |
352 (defun kfile:build-structure-v2 (kotl-structure cell-data) | |
353 "Build cell list from the KOTL-STRUCTURE and its CELL-DATA. | |
354 Assumes all arguments are valid. CELL-DATA is a vector of cell fields read | |
355 from a koutline file. | |
356 | |
357 Return list of outline cells in depth first order. Invisible top cell is not | |
358 included in the list." | |
359 (let ((stack) (sibling-p) (cell-list) func cell) | |
360 (mapcar | |
361 (function | |
362 (lambda (item) | |
363 (setq func (cdr (assoc item | |
364 (list | |
365 (cons "\(" | |
366 (function | |
367 (lambda () | |
368 (setq stack (cons sibling-p stack) | |
369 sibling-p nil)))) | |
370 (cons "\)" | |
371 (function | |
372 (lambda () | |
373 (setq sibling-p (car stack) | |
374 stack (cdr stack))))))))) | |
375 (cond (func (funcall func)) | |
376 ;; 0th cell was created with kview:create. | |
377 ((equal item 0) nil) | |
378 (t (setq cell (kotl-data:to-kcell-v2 (aref cell-data item)) | |
379 cell-list (cons cell cell-list) | |
380 sibling-p t) | |
381 )))) | |
382 kotl-structure) | |
383 (nreverse cell-list))) | |
384 | |
385 (defun kfile:insert-attributes-v2 (kview kcell-list) | |
386 "Set cell attributes within kview for each element in KCELL-LIST. | |
387 Assumes all cell contents are already in kview and that no cells are | |
388 hidden." | |
389 (let (buffer-read-only) | |
390 (while | |
391 (progn | |
392 (skip-chars-forward "\n") | |
393 ;; !!! Won't work if label-type is 'no. | |
394 ;; Here we search past the cell identifier | |
395 ;; for the location at which to place cell properties. | |
396 ;; Be sure not to skip past a period which may terminate the label. | |
397 (if (re-search-forward "[A-Za-z0-9]\\(\\.?[A-Za-z0-9]\\)*" nil t) | |
398 (progn | |
399 (kproperty:set 'kcell (car kcell-list)) | |
400 (setq kcell-list (cdr kcell-list)))) | |
401 (search-forward "\n\n" nil t))))) | |
402 | |
403 (defun kfile:insert-attributes-v3 (kview kcell-vector) | |
404 "Set cell attributes within kview for each element in KCELL-VECTOR. | |
405 Assumes all cell contents are already in kview and that no cells are | |
406 hidden." | |
407 (let ((kcell-num 1) | |
408 (buffer-read-only)) | |
409 (while | |
410 (progn | |
411 (skip-chars-forward "\n") | |
412 ;; !!! Won't work if label-type is 'no. | |
413 ;; Here we search past the cell identifier | |
414 ;; for the location at which to place cell properties. | |
415 ;; Be sure not to skip past a period which may terminate the label. | |
416 (if (re-search-forward "[A-Za-z0-9]\\(\\.?[A-Za-z0-9]\\)*" nil t) | |
417 (progn | |
418 (kproperty:set 'kcell | |
419 (kotl-data:to-kcell-v3 | |
420 (aref kcell-vector kcell-num))) | |
421 (setq kcell-num (1+ kcell-num)))) | |
422 (search-forward "\n\n" nil t))))) | |
423 | |
424 (defun kfile:narrow-to-kcells () | |
425 "Narrow kotl file to kcell section only." | |
426 (interactive) | |
427 (if (kview:is-p kview) | |
428 (let ((start-text) (end-text)) | |
429 (save-excursion | |
430 (widen) | |
431 (goto-char (point-min)) | |
432 ;; Skip to start of kcells. | |
433 (if (search-forward "\n\^_" nil t) | |
434 (setq start-text (1+ (match-end 0)))) | |
435 ;; Skip past end of kcells. | |
436 (if (and start-text (search-forward "\n\^_" nil t)) | |
437 (setq end-text (1+ (match-beginning 0)))) | |
438 (if (and start-text end-text) | |
439 (progn (narrow-to-region start-text end-text) | |
440 (goto-char (point-min))) | |
441 (error | |
442 "(kfile:narrow-to-kcells): Cannot find start or end of kcells")) | |
443 )))) | |
444 | |
445 (defun kfile:print-to-string (object) | |
446 "Return a string containing OBJECT, any Lisp object, in pretty-printed form. | |
447 Quoting characters are used when needed to make output that `read' can | |
448 handle, whenever this is possible." | |
449 (save-excursion | |
450 (set-buffer (get-buffer-create " kfile:print-to-string")) | |
451 (let ((emacs-lisp-mode-hook) | |
452 (buffer-read-only)) | |
453 (erase-buffer) | |
454 (unwind-protect | |
455 (progn | |
456 (emacs-lisp-mode) | |
457 (let ((print-escape-newlines kfile:escape-newlines)) | |
458 (prin1 object (current-buffer))) | |
459 (goto-char (point-min)) | |
460 (while (not (eobp)) | |
461 ;; (message "%06d" (- (point-max) (point))) | |
462 (cond | |
463 ((looking-at "\\s\(") | |
464 (while (looking-at "\\s(") | |
465 (forward-char 1))) | |
466 ((and (looking-at "\\(quote[ \t]+\\)\\([^.)]\\)") | |
467 (> (match-beginning 1) 1) | |
468 (= ?\( (char-after (1- (match-beginning 1)))) | |
469 ;; Make sure this is a two-element list. | |
470 (save-excursion | |
471 (goto-char (match-beginning 2)) | |
472 (forward-sexp) | |
473 ;; (looking-at "[ \t]*\)") | |
474 ;; Avoid mucking with match-data; does this test work? | |
475 (char-equal ?\) (char-after (point))))) | |
476 ;; -1 gets the paren preceding the quote as well. | |
477 (delete-region (1- (match-beginning 1)) (match-end 1)) | |
478 (insert "'") | |
479 (forward-sexp 1) | |
480 (if (looking-at "[ \t]*\)") | |
481 (delete-region (match-beginning 0) (match-end 0)) | |
482 (error "Malformed quote")) | |
483 (backward-sexp 1)) | |
484 ((condition-case () | |
485 (prog1 t (down-list 1)) | |
486 (error nil)) | |
487 (backward-char 1) | |
488 (skip-chars-backward " \t") | |
489 (delete-region | |
490 (point) | |
491 (progn (skip-chars-forward " \t") (point))) | |
492 (if (not (char-equal ?' (char-after (1- (point))))) | |
493 (insert ?\n))) | |
494 ((condition-case () | |
495 (prog1 t (up-list 1)) | |
496 (error nil)) | |
497 (while (looking-at "\\s)") | |
498 (forward-char 1)) | |
499 (skip-chars-backward " \t") | |
500 (delete-region | |
501 (point) | |
502 (progn (skip-chars-forward " \t") (point))) | |
503 (if (not (char-equal ?' (char-after (1- (point))))) | |
504 (insert ?\n))) | |
505 (t (goto-char (point-max))))) | |
506 (goto-char (point-min)) | |
507 (indent-sexp) | |
508 (buffer-string)) | |
509 (kill-buffer (current-buffer)))))) | |
510 | |
511 (defun kfile:pretty-print (object &optional stream) | |
512 "Output the pretty-printed representation of OBJECT, any Lisp object. | |
513 Quoting characters are printed when needed to make output that `read' | |
514 can handle, whenever this is possible. | |
515 Output stream is STREAM, or value of `standard-output' (which see)." | |
516 (princ (kfile:print-to-string object) (or stream standard-output))) | |
517 | |
518 (defun kfile:read-name (prompt existing-p) | |
519 "PROMPT for and read a koutline file name. EXISTING-P means must exist." | |
520 (let ((filename)) | |
521 (while (not filename) | |
522 (setq filename (read-file-name prompt nil nil existing-p)) | |
523 (if (or (null filename) (equal filename "")) | |
524 (progn (ding) (setq filename nil)))) | |
525 filename)) | |
526 | |
527 ;;; ************************************************************************ | |
528 ;;; Private variables | |
529 ;;; ************************************************************************ | |
530 | |
531 (defvar kfile:escape-newlines t | |
532 "Value of print-escape-newlines used by 'kfile:print-to-string' function.") | |
533 | |
534 (provide 'kfile) |