1 ;;; kfile.el --- Save and restore kotls from files.
3 ;; Copyright (C) 1995, 2004, 2006 Free Software Foundation, Inc.
4 ;; Developed with support from Motorola Inc.
6 ;; Author: Bob Weiner, Brown U.
8 ;; Maintainer: Mats Lidell <matsl@contactor.se>
9 ;; Keywords: outlines, wp
11 ;; This file is part of GNU Hyperbole.
13 ;; GNU Hyperbole is free software; you can redistribute it and/or
14 ;; modify it under the terms of the GNU General Public License as
15 ;; published by the Free Software Foundation; either version 3, or (at
16 ;; your option) any later version.
18 ;; GNU Hyperbole is distributed in the hope that it will be useful,
19 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 ;; General Public License for more details.
23 ;; You should have received a copy of the GNU General Public License
24 ;; along with GNU Emacs; see the file COPYING. If not, write to the
25 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
26 ;; Boston, MA 02110-1301, USA.
33 ;;; Other required Elisp libraries
36 (mapcar 'require '(kproperty kotl-mode))
42 (defconst kfile:version "Kotl-4.0"
43 "Version number of persistent data format used for saving koutlines.")
50 (defun kfile:find (file-name)
51 "Find a file FILE-NAME containing a kotl or create one if none exists.
52 Return the new kview."
54 (list (kfile:read-name
55 "Find koutline file: " nil)))
56 (let ((existing-file (file-exists-p file-name))
59 (not (file-readable-p file-name))
61 "(kfile:find): \"%s\" is not readable. Check permissions."
63 (setq buffer (find-file file-name))
64 ;; Finding the file may have already done a kfile:read as invoked through
65 ;; kotl-mode via a file local variable setting. If so, don't read it
67 (if (kview:is-p kview)
69 (kfile:read buffer existing-file))
70 (or (eq major-mode 'kotl-mode) (kotl-mode))
74 (defun kfile:view (file-name)
75 "View an existing kotl version-2 file FILE-NAME in a read-only mode."
77 (list (kfile:read-name
78 "View koutline file: " t)))
79 (let ((existing-file (file-exists-p file-name)))
81 (if (not (file-readable-p file-name))
83 "(kfile:view): \"%s\" is not readable. Check permissions."
85 (error "(kfile:view): \"%s\" does not exist."))
86 (view-file file-name))
87 (kfile:narrow-to-kcells)
88 (goto-char (point-min)))
94 (defun kfile:create (buffer)
95 "Create a new koutline file attached to BUFFER, with a single empty level 1 kotl cell.
97 (or buffer (setq buffer (current-buffer)))
98 (if (not (bufferp buffer))
99 (error "(kfile:create): Invalid buffer argument, %s" buffer))
102 (error "(kfile:create): %s is read-only" buffer))
105 (let ((empty-p (zerop (buffer-size)))
106 import-from view standard-output)
109 ;; This is a foreign file whose elements must be converted into
111 (progn (setq import-from (kimport:copy-and-set-buffer buffer))
113 (erase-buffer))) ;; We copied the contents to `import-from'.
115 (setq view (kview:create (buffer-name buffer))
116 standard-output (current-buffer))
117 (goto-char (point-min))
118 (princ ";; -*- Mode: kotl -*- \n")
119 (prin1 kfile:version)
120 (princ " ;; file-format\n\^_\n")
121 ;; Ensure that last cell has two newlines after it so that
122 ;; kfile:insert-attributes finds it.
123 (goto-char (point-max))
125 (princ "\^_\n;; depth-first kcell attributes\n")
126 ;; Ensure that display is narrowed to cell region only.
127 (kfile:narrow-to-kcells)
128 (goto-char (point-min))
130 ;; This is a new koutline file. Always need at least one visible
131 ;; cell within a view. Insert initial empty cell.
132 (progn (kview:add-cell "1" 1)
133 ;; Mark view unmodified, so if kill right away, there is no
135 (set-buffer-modified-p nil)
136 ;; Move to first cell.
137 (goto-char (point-min))
138 (goto-char (kcell-view:start)))
139 ;; Import buffer. Next line is necessary or the importation will fail.
140 (delete-region (point-min) (point-max))
141 ;; Import foreign buffer as koutline cells.
142 (kimport:file import-from (current-buffer))
143 ;; If import buffer name starts with a space, kill it, as it is no
145 (if (= ?\ (aref (buffer-name import-from) 0))
146 (kill-buffer import-from)))
152 "Iff current buffer contains an unformatted or formatted koutline, return file format version string, else nil."
157 (goto-char (point-min))
160 (setq ver-string (read (current-buffer)))
161 (and (stringp ver-string) (string-match "^Kotl-" ver-string)
165 (defun kfile:read (buffer existing-file-p)
166 "Create a new kotl view by reading BUFFER or create an empty view when EXISTING-FILE-P is nil.
167 Return the new view."
169 (cond ((not (bufferp buffer))
170 (error "(kfile:read): Argument must be a buffer, '%s'." buffer))
171 ((not existing-file-p)
172 (kfile:create buffer))
175 (not (setq ver-string (kfile:is-p))))
176 (error "(kfile:read): '%s' is not a koutline file." buffer))
177 ((equal ver-string "Kotl-4.0")
178 (kfile:read-v4-or-v3 buffer nil))
179 ((equal ver-string "Kotl-3.0")
180 (kfile:read-v4-or-v3 buffer t))
181 ((equal ver-string "Kotl-2.0")
182 (kfile:read-v2 buffer))
183 ((equal ver-string "Kotl-1.0")
184 (error "(kfile:read): V1 koutlines are no longer supported"))
185 (t (error "(kfile:read): '%s' has unknown kotl version, %s."
186 buffer ver-string)))))
188 (defun kfile:read-v2 (buffer)
189 "Create a kotl view by reading kotl version-2 BUFFER. Return the new view."
190 (let ((standard-input buffer)
191 cell-count label-type label-min-width label-separator
192 level-indent cell-data kotl-structure view kcell-list)
194 (goto-char (point-min))
195 ;; Skip past cell contents here.
196 (search-forward "\n\^_" nil t 2)
197 ;; Read rest of file data.
198 (setq cell-count (read)
200 label-min-width (read)
201 label-separator (read)
204 kotl-structure (read))
206 ;; kcell-list is a depth-first list of kcells to be attached to the cell
207 ;; contents within the kview down below.
208 (setq kcell-list (kfile:build-structure-v2 kotl-structure cell-data)
209 view (kview:create (buffer-name buffer) cell-count label-type
210 level-indent label-separator label-min-width))
212 (kfile:narrow-to-kcells)
213 (goto-char (point-min))
215 ;; Add attributes to cells.
216 (kfile:insert-attributes-v2 view kcell-list)
218 ;; Mark view unmodified and move to first cell.
219 (set-buffer-modified-p nil)
220 (goto-char (point-min))
221 (goto-char (kcell-view:start))
224 (defun kfile:read-v4-or-v3 (buffer v3-flag)
225 "Create a koutline view by reading version-4 BUFFER. Return the new view.
226 If V3-FLAG is true, read as a version-3 buffer."
227 (let ((standard-input buffer)
228 cell-count label-type label-min-width label-separator
229 level-indent cell-data view)
231 (goto-char (point-min))
232 ;; Skip past cell contents here.
233 (search-forward "\n\^_" nil t 2)
234 ;; Read rest of file data.
236 nil ;; V3 files did not store viewspecs.
238 (setq kvspec:current (read)))
239 (setq cell-count (read)
241 label-min-width (read)
242 label-separator (read)
246 (setq view (kview:create (buffer-name buffer) cell-count label-type
247 level-indent label-separator label-min-width))
249 (kfile:narrow-to-kcells)
250 (goto-char (point-min))
252 ;; Add attributes to cells.
253 (kfile:insert-attributes-v3 view cell-data)
255 ;; Mark view unmodified and move to first cell.
256 (set-buffer-modified-p nil)
257 (goto-char (point-min))
258 (goto-char (kcell-view:start))
261 (defun kfile:update (&optional visible-only-p)
262 "Update kfile internal structure so that view is ready for saving to a file.
263 Leave outline file expanded with structure data showing unless optional
264 VISIBLE-ONLY-P is non-nil. Signal an error if kotl is not attached to a file."
265 (let* ((top (kview:top-cell kview))
266 (file (kcell:get-attr top 'file))
267 (label-type (kview:label-type kview))
268 (label-min-width (kview:label-min-width kview))
269 (label-separator (kview:label-separator kview))
270 (level-indent (kview:level-indent kview))
271 ;; If this happens to be non-nil, it is virtually impossible to save
272 ;; a file, so ensure it is nil.
275 (error "(kfile:update): Current outline is not attached to a file."))
276 ((not (file-writable-p file))
277 (error "(kfile:update): File \"%s\" is not writable." file)))
278 (let* ((buffer-read-only)
279 (id-counter (kcell:get-attr top 'id-counter))
280 (kotl-data (make-vector (1+ id-counter) nil))
281 (standard-output (current-buffer))
282 (opoint (set-marker (make-marker) (point)))
286 ;; Prepare cell data for saving.
287 (kfile:narrow-to-kcells)
291 (setq cell (kcell-view:cell))
294 (kotl-data:create cell))
295 (setq kcell-num (1+ kcell-num))))
297 ;; Save top cell, 0, last since above loop may increment the total
298 ;; number of cells counter stored in it, if any invalid cells are
300 (aset kotl-data 0 (kotl-data:create top))
301 (setq id-counter (kcell:get-attr top 'id-counter))
304 (goto-char (point-min))
305 (if (search-forward "\n\^_\n" nil t)
306 (delete-region (point-min) (match-end 0)))
307 (princ ";; -*- Mode: kotl -*- \n")
308 (prin1 kfile:version)
309 (princ " ;; file-format\n\^_\n")
311 (if (search-forward "\n\^_\n" nil t)
312 ;; Get rid of excess blank lines after last cell.
313 (progn (goto-char (match-beginning 0))
314 (skip-chars-backward "\n")
315 (delete-region (point) (point-max)))
316 (goto-char (point-max)))
317 ;; Ensure that last cell has two newlines after it so that
318 ;; kfile:insert-attributes finds it.
320 (princ (format (concat
321 "%S ;; kvspec:current\n%d ;; id-counter\n"
322 "%S ;; label-type\n%d ;; label-min-width\n"
323 "%S ;; label-separator\n%d ;; level-indent\n")
324 kvspec:current id-counter label-type label-min-width
325 label-separator level-indent))
326 (princ "\^_\n;; depth-first kcell attributes\n")
327 (kfile:pretty-print kotl-data)
329 ;; Don't re-narrow buffer by default since this is used in
330 ;; write-contents-hooks after save-buffer has widened buffer. If
331 ;; buffer is narrowed here, only the narrowed portion will be saved to
332 ;; the file. Narrow as an option since saving only the portion of the
333 ;; file visible in a view may be useful in some situations.
334 (if visible-only-p (kfile:narrow-to-kcells))
336 ;; Return point to its original position as given by the opoint marker.
338 (set-marker opoint nil)
341 ;;; Next function is adapted from 'file-write' of GNU Emacs 19, copyright FSF,
343 (defun kfile:write (file)
344 "Write current outline to FILE."
345 (interactive "FWrite outline file: ")
346 (if (or (null file) (string-equal file ""))
347 (error "(kfile:write): Invalid file name, \"%s\"" file))
348 ;; If arg is just a directory, use same file name, but in that directory.
349 (if (and (file-directory-p file) buffer-file-name)
350 (setq file (concat (file-name-as-directory file)
351 (file-name-nondirectory buffer-file-name))))
352 (kcell:set-attr (kview:top-cell kview) 'file file)
353 (set-visited-file-name file)
354 ;; Set-visited-file-name clears local-write-file-hooks that we use to save
355 ;; koutlines properly, so reinitialize local variables.
357 (set-buffer-modified-p t)
358 ;; This next line must come before the save-buffer since write-file-hooks
359 ;; can make use of it.
360 (kview:set-buffer-name kview (buffer-name))
364 ;;; Private functions
367 (defun kfile:build-structure-v2 (kotl-structure cell-data)
368 "Build cell list from the KOTL-STRUCTURE and its CELL-DATA.
369 Assumes all arguments are valid. CELL-DATA is a vector of cell fields read
370 from a koutline file.
372 Return list of outline cells in depth first order. Invisible top cell is not
373 included in the list."
374 (let ((stack) (sibling-p) (cell-list) func cell)
378 (setq func (cdr (assoc item
383 (setq stack (cons sibling-p stack)
388 (setq sibling-p (car stack)
389 stack (cdr stack)))))))))
390 (cond (func (funcall func))
391 ;; 0th cell was created with kview:create.
393 (t (setq cell (kotl-data:to-kcell-v2 (aref cell-data item))
394 cell-list (cons cell cell-list)
398 (nreverse cell-list)))
400 (defun kfile:insert-attributes-v2 (kview kcell-list)
401 "Set cell attributes within kview for each element in KCELL-LIST.
402 Assumes all cell contents are already in kview and that no cells are
404 (let (buffer-read-only)
407 (skip-chars-forward "\n")
408 ;; !!! Won't work if label-type is 'no.
409 ;; Here we search past the cell identifier
410 ;; for the location at which to place cell properties.
411 ;; Be sure not to skip past a period which may terminate the label.
412 (if (re-search-forward "[A-Za-z0-9]\\(\\.?[A-Za-z0-9]\\)*" nil t)
414 (kproperty:set 'kcell (car kcell-list))
415 (setq kcell-list (cdr kcell-list))))
416 (search-forward "\n\n" nil t)))))
418 (defun kfile:insert-attributes-v3 (kview kcell-vector)
419 "Set cell attributes within kview for each element in KCELL-VECTOR.
420 Assumes all cell contents are already in kview and that no cells are
426 (skip-chars-forward "\n")
427 ;; !!! Won't work if label-type is 'no.
428 ;; Here we search past the cell identifier
429 ;; for the location at which to place cell properties.
430 ;; Be sure not to skip past a period which may terminate the label.
431 (if (re-search-forward "[A-Za-z0-9]\\(\\.?[A-Za-z0-9]\\)*" nil t)
433 (kproperty:set 'kcell
434 (kotl-data:to-kcell-v3
435 (aref kcell-vector kcell-num)))
436 (setq kcell-num (1+ kcell-num))))
437 (search-forward "\n\n" nil t)))))
439 (defun kfile:narrow-to-kcells ()
440 "Narrow kotl file to kcell section only."
442 (if (kview:is-p kview)
443 (let ((start-text) (end-text))
446 (goto-char (point-min))
447 ;; Skip to start of kcells.
448 (if (search-forward "\n\^_" nil t)
449 (setq start-text (1+ (match-end 0))))
450 ;; Skip past end of kcells.
451 (if (and start-text (search-forward "\n\^_" nil t))
452 (setq end-text (1+ (match-beginning 0))))
453 (if (and start-text end-text)
454 (progn (narrow-to-region start-text end-text)
455 (goto-char (point-min)))
457 "(kfile:narrow-to-kcells): Cannot find start or end of kcells"))
460 (defun kfile:print-to-string (object)
461 "Return a string containing OBJECT, any Lisp object, in pretty-printed form.
462 Quoting characters are used when needed to make output that `read' can
463 handle, whenever this is possible."
465 (set-buffer (get-buffer-create " kfile:print-to-string"))
466 (let ((emacs-lisp-mode-hook)
472 (let ((print-escape-newlines kfile:escape-newlines))
473 (prin1 object (current-buffer)))
474 (goto-char (point-min))
476 ;; (message "%06d" (- (point-max) (point)))
478 ((looking-at "\\s\(")
479 (while (looking-at "\\s(")
481 ((and (looking-at "\\(quote[ \t]+\\)\\([^.)]\\)")
482 (> (match-beginning 1) 1)
483 (= ?\( (char-after (1- (match-beginning 1))))
484 ;; Make sure this is a two-element list.
486 (goto-char (match-beginning 2))
488 ;; (looking-at "[ \t]*\)")
489 ;; Avoid mucking with match-data; does this test work?
490 (char-equal ?\) (char-after (point)))))
491 ;; -1 gets the paren preceding the quote as well.
492 (delete-region (1- (match-beginning 1)) (match-end 1))
495 (if (looking-at "[ \t]*\)")
496 (delete-region (match-beginning 0) (match-end 0))
497 (error "Malformed quote"))
500 (prog1 t (down-list 1))
503 (skip-chars-backward " \t")
506 (progn (skip-chars-forward " \t") (point)))
507 (if (not (char-equal ?' (char-after (1- (point)))))
510 (prog1 t (up-list 1))
512 (while (looking-at "\\s)")
514 (skip-chars-backward " \t")
517 (progn (skip-chars-forward " \t") (point)))
518 (if (not (char-equal ?' (char-after (1- (point)))))
520 (t (goto-char (point-max)))))
521 (goto-char (point-min))
524 (kill-buffer (current-buffer))))))
526 (defun kfile:pretty-print (object &optional stream)
527 "Output the pretty-printed representation of OBJECT, any Lisp object.
528 Quoting characters are printed when needed to make output that `read'
529 can handle, whenever this is possible.
530 Output stream is STREAM, or value of `standard-output' (which see)."
531 (princ (kfile:print-to-string object) (or stream standard-output)))
533 (defun kfile:read-name (prompt existing-p)
534 "PROMPT for and read a koutline file name. EXISTING-P means must exist."
536 (while (not filename)
537 (setq filename (read-file-name prompt nil nil existing-p))
538 (if (or (null filename) (equal filename ""))
539 (progn (ding) (setq filename nil))))
543 ;;; Private variables
546 (defvar kfile:escape-newlines t
547 "Value of print-escape-newlines used by 'kfile:print-to-string' function.")
551 ;;; kfile.el ends here