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)