(undigestify-rmail-message): Better error messages.
[bpt/emacs.git] / lisp / forms.el
CommitLineData
b22c9ebf 1;;; forms.el -- Forms mode: edit a file as a form to fill in.
2996d9f8 2;;; Copyright (C) 1991, 1994, 1995 Free Software Foundation, Inc.
b22c9ebf 3
891f0daa 4;; Author: Johan Vromans <jv@nl.net>
b22c9ebf
RS
5
6;; This file is part of GNU Emacs.
7
8;; GNU Emacs is free software; you can redistribute it and/or modify
9;; it under the terms of the GNU General Public License as published by
10;; the Free Software Foundation; either version 2, or (at your option)
11;; any later version.
12
13;; GNU Emacs is distributed in the hope that it will be useful,
14;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16;; GNU General Public License for more details.
c1110355 17
b22c9ebf
RS
18;; You should have received a copy of the GNU General Public License
19;; along with GNU Emacs; see the file COPYING. If not, write to
20;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
21
22;;; Commentary:
c1110355
BP
23
24;;; Visit a file using a form.
25;;;
26;;; === Naming conventions
27;;;
1f111018
RS
28;;; The names of all variables and functions start with 'forms-'.
29;;; Names which start with 'forms--' are intended for internal use, and
c1110355
BP
30;;; should *NOT* be used from the outside.
31;;;
32;;; All variables are buffer-local, to enable multiple forms visits
33;;; simultaneously.
fbee9727 34;;; Variable `forms--mode-setup' is local to *ALL* buffers, for it
c1110355
BP
35;;; controls if forms-mode has been enabled in a buffer.
36;;;
37;;; === How it works ===
38;;;
39;;; Forms mode means visiting a data file which is supposed to consist
fbee9727 40;;; of records each containing a number of fields. The records are
c1110355
BP
41;;; separated by a newline, the fields are separated by a user-defined
42;;; field separater (default: TAB).
fbee9727 43;;; When shown, a record is transferred to an Emacs buffer and
ac2a7a91 44;;; presented using a user-defined form. One record is shown at a
c1110355
BP
45;;; time.
46;;;
ac2a7a91 47;;; Forms mode is a composite mode. It involves two files, and two
c1110355
BP
48;;; buffers.
49;;; The first file, called the control file, defines the name of the
ac2a7a91 50;;; data file and the forms format. This file buffer will be used to
c1110355 51;;; present the forms.
ac2a7a91 52;;; The second file holds the actual data. The buffer of this file
c1110355
BP
53;;; will be buried, for it is never accessed directly.
54;;;
fbee9727
RS
55;;; Forms mode is invoked using M-x forms-find-file control-file .
56;;; Alternativily `forms-find-file-other-window' can be used.
c1110355
BP
57;;;
58;;; You may also visit the control file, and switch to forms mode by hand
59;;; with M-x forms-mode .
60;;;
fbee9727
RS
61;;; Automatic mode switching is supported if you specify
62;;; "-*- forms -*-" in the first line of the control file.
c1110355 63;;;
fbee9727
RS
64;;; The control file is visited, evaluated using `eval-current-buffer',
65;;; and should set at least the following variables:
c1110355 66;;;
fbee9727
RS
67;;; forms-file [string]
68;;; The name of the data file.
c1110355 69;;;
fbee9727 70;;; forms-number-of-fields [integer]
c1110355
BP
71;;; The number of fields in each record.
72;;;
fbee9727
RS
73;;; forms-format-list [list]
74;;; Formatting instructions.
c1110355 75;;;
fbee9727 76;;; `forms-format-list' should be a list, each element containing
c1110355 77;;;
fbee9727
RS
78;;; - a string, e.g. "hello". The string is inserted in the forms
79;;; "as is".
80;;;
81;;; - an integer, denoting a field number.
82;;; The contents of this field are inserted at this point.
83;;; Fields are numbered starting with number one.
84;;;
85;;; - a function call, e.g. (insert "text").
86;;; This function call is dynamically evaluated and should return a
87;;; string. It should *NOT* have side-effects on the forms being
88;;; constructed. The current fields are available to the function
89;;; in the variable `forms-fields', they should *NOT* be modified.
90;;;
91;;; - a lisp symbol, that must evaluate to one of the above.
01a45313 92;;;
c1110355
BP
93;;; Optional variables which may be set in the control file:
94;;;
95;;; forms-field-sep [string, default TAB]
96;;; The field separator used to separate the
ac2a7a91 97;;; fields in the data file. It may be a string.
c1110355
BP
98;;;
99;;; forms-read-only [bool, default nil]
fbee9727
RS
100;;; Non-nil means that the data file is visited
101;;; read-only (view mode) as opposed to edit mode.
c1110355 102;;; If no write access to the data file is
fbee9727 103;;; possible, view mode is enforced.
c1110355 104;;;
4fd3a710
RS
105;;; forms-check-number-of-fields [bool, default t]
106;;; If non-nil, a warning will be issued whenever
107;;; a record is found that does not have the number
108;;; of fields specified by `forms-number-of-fields'.
109;;;
c1110355
BP
110;;; forms-multi-line [string, default "^K"]
111;;; If non-null the records of the data file may
fbee9727 112;;; contain fields that can span multiple lines in
c1110355 113;;; the form.
fbee9727 114;;; This variable denotes the separator character
ac2a7a91 115;;; to be used for this purpose. Upon display, all
c1110355 116;;; occurrencies of this character are translated
ac2a7a91 117;;; to newlines. Upon storage they are translated
fbee9727 118;;; back to the separator character.
c1110355 119;;;
2cc27dd3 120;;; forms-forms-scroll [bool, default nil]
fbee9727
RS
121;;; Non-nil means: rebind locally the commands that
122;;; perform `scroll-up' or `scroll-down' to use
123;;; `forms-next-field' resp. `forms-prev-field'.
c1110355 124;;;
2cc27dd3 125;;; forms-forms-jump [bool, default nil]
fbee9727
RS
126;;; Non-nil means: rebind locally the commands that
127;;; perform `beginning-of-buffer' or `end-of-buffer'
128;;; to perform `forms-first-field' resp. `forms-last-field'.
c1110355 129;;;
9c308ed2
RS
130;;; forms-read-file-filter [symbol, default nil]
131;;; If not nil: this should be the name of a
132;;; function that is called after the forms data file
133;;; has been read. It can be used to transform
134;;; the contents of the file into a format more suitable
135;;; for forms-mode processing.
136;;;
137;;; forms-write-file-filter [symbol, default nil]
138;;; If not nil: this should be the name of a
139;;; function that is called before the forms data file
140;;; is written (saved) to disk. It can be used to undo
141;;; the effects of `forms-read-file-filter', if any.
142;;;
2cc27dd3
RS
143;;; forms-new-record-filter [symbol, default nil]
144;;; If not nil: this should be the name of a
01a45313 145;;; function that is called when a new
ac2a7a91 146;;; record is created. It can be used to fill in
c1110355 147;;; the new record with default fields, for example.
01a45313 148;;;
2cc27dd3
RS
149;;; forms-modified-record-filter [symbol, default nil]
150;;; If not nil: this should be the name of a
01a45313 151;;; function that is called when a record has
ac2a7a91
RS
152;;; been modified. It is called after the fields
153;;; are parsed. It can be used to register
01a45313 154;;; modification dates, for example.
c1110355 155;;;
fbee9727
RS
156;;; forms-use-text-properties [bool, see text for default]
157;;; This variable controls if forms mode should use
158;;; text properties to protect the form text from being
159;;; modified (using text-property `read-only').
160;;; Also, the read-write fields are shown using a
161;;; distinct face, if possible.
2996d9f8
RS
162;;; As of emacs 19.29, the `intangible' text property
163;;; is used to prevent moving into read-only fields.
fbee9727
RS
164;;; This variable defaults to t if running Emacs 19
165;;; with text properties.
166;;; The default face to show read-write fields is
167;;; copied from face `region'.
168;;;
169;;; forms-ro-face [symbol, default 'default]
170;;; This is the face that is used to show
171;;; read-only text on the screen.If used, this
172;;; variable should be set to a symbol that is a
173;;; valid face.
174;;; E.g.
175;;; (make-face 'my-face)
176;;; (setq forms-ro-face 'my-face)
177;;;
178;;; forms-rw-face [symbol, default 'region]
179;;; This is the face that is used to show
180;;; read-write text on the screen.
181;;;
c1110355
BP
182;;; After evaluating the control file, its buffer is cleared and used
183;;; for further processing.
fbee9727
RS
184;;; The data file (as designated by `forms-file') is visited in a buffer
185;;; `forms--file-buffer' which will not normally be shown.
c1110355 186;;; Great malfunctioning may be expected if this file/buffer is modified
fbee9727 187;;; outside of this package while it is being visited!
c1110355 188;;;
fbee9727
RS
189;;; Normal operation is to transfer one line (record) from the data file,
190;;; split it into fields (into `forms--the-record-list'), and display it
191;;; using the specs in `forms-format-list'.
192;;; A format routine `forms--format' is built upon startup to format
193;;; the records according to `forms-format-list'.
c1110355
BP
194;;;
195;;; When a form is changed the record is updated as soon as this form
fbee9727
RS
196;;; is left. The contents of the form are parsed using information
197;;; obtained from `forms-format-list', and the fields which are
198;;; deduced from the form are modified. Fields not shown on the forms
199;;; retain their origional values. The newly formed record then
200;;; replaces the contents of the old record in `forms--file-buffer'.
201;;; A parse routine `forms--parser' is built upon startup to parse
c1110355
BP
202;;; the records.
203;;;
fbee9727
RS
204;;; Two exit functions exist: `forms-exit' and `forms-exit-no-save'.
205;;; `forms-exit' saves the data to the file, if modified.
206;;; `forms-exit-no-save` does not. However, if `forms-exit-no-save'
207;;; is executed and the file buffer has been modified, Emacs will ask
208;;; questions anyway.
c1110355 209;;;
fbee9727 210;;; Other functions provided by forms mode are:
c1110355
BP
211;;;
212;;; paging (forward, backward) by record
213;;; jumping (first, last, random number)
214;;; searching
215;;; creating and deleting records
216;;; reverting the form (NOT the file buffer)
217;;; switching edit <-> view mode v.v.
218;;; jumping from field to field
219;;;
220;;; As an documented side-effect: jumping to the last record in the
221;;; file (using forms-last-record) will adjust forms--total-records if
222;;; needed.
223;;;
2cc27dd3
RS
224;;; The forms buffer can be in on eof two modes: edit mode or view
225;;; mode. View mode is a read-only mode, you cannot modify the
226;;; contents of the buffer.
c1110355 227;;;
2cc27dd3
RS
228;;; Edit mode commands:
229;;;
230;;; TAB forms-next-field
231;;; \C-c TAB forms-next-field
232;;; \C-c < forms-first-record
233;;; \C-c > forms-last-record
234;;; \C-c ? describe-mode
235;;; \C-c \C-k forms-delete-record
236;;; \C-c \C-q forms-toggle-read-only
237;;; \C-c \C-o forms-insert-record
238;;; \C-c \C-l forms-jump-record
239;;; \C-c \C-n forms-next-record
240;;; \C-c \C-p forms-prev-record
ac7e3dbe
JV
241;;; \C-c \C-r forms-search-backward
242;;; \C-c \C-s forms-search-forward
2cc27dd3
RS
243;;; \C-c \C-x forms-exit
244;;;
245;;; Read-only mode commands:
246;;;
247;;; SPC forms-next-record
248;;; DEL forms-prev-record
249;;; ? describe-mode
250;;; \C-q forms-toggle-read-only
251;;; l forms-jump-record
252;;; n forms-next-record
253;;; p forms-prev-record
ac7e3dbe
JV
254;;; r forms-search-backward
255;;; s forms-search-forward
2cc27dd3
RS
256;;; x forms-exit
257;;;
258;;; Of course, it is also possible to use the \C-c prefix to obtain the
259;;; same command keys as in edit mode.
260;;;
261;;; The following bindings are available, independent of the mode:
262;;;
263;;; [next] forms-next-record
264;;; [prior] forms-prev-record
265;;; [begin] forms-first-record
266;;; [end] forms-last-record
267;;; [S-TAB] forms-prev-field
268;;; [backtab] forms-prev-field
c1110355 269;;;
fbee9727
RS
270;;; For convenience, TAB is always bound to `forms-next-field', so you
271;;; don't need the C-c prefix for this command.
272;;;
273;;; As mentioned above (see `forms-forms-scroll' and `forms-forms-jump')
274;;; the bindings of standard functions `scroll-up', `scroll-down',
2cc27dd3 275;;; `beginning-of-buffer' and `end-of-buffer' can be locally replaced with
0cfa68a9 276;;; forms mode functions next/prev record and first/last
fbee9727 277;;; record.
c1110355 278;;;
fbee9727
RS
279;;; `local-write-file hook' is defined to save the actual data file
280;;; instead of the buffer data, `revert-file-hook' is defined to
0cfa68a9 281;;; revert a forms to original.
b22c9ebf
RS
282\f
283;;; Code:
284
fbee9727 285;;; Global variables and constants:
c1110355 286
b22c9ebf
RS
287(provide 'forms) ;;; official
288(provide 'forms-mode) ;;; for compatibility
289
4fd3a710 290(defconst forms-version (substring "$Revision: 2.19 $" 11 -2)
2cc27dd3
RS
291 "The version number of forms-mode (as string). The complete RCS id is:
292
4fd3a710 293 $Id: forms.el,v 2.19 1995/07/08 13:16:54 jvromans Exp rms $")
c1110355 294
c1110355 295(defvar forms-mode-hooks nil
b22c9ebf
RS
296 "Hook functions to be run upon entering Forms mode.")
297\f
fbee9727 298;;; Mandatory variables - must be set by evaluating the control file.
c1110355
BP
299
300(defvar forms-file nil
01a45313 301 "Name of the file holding the data.")
c1110355
BP
302
303(defvar forms-format-list nil
01a45313 304 "List of formatting specifications.")
c1110355
BP
305
306(defvar forms-number-of-fields nil
307 "Number of fields per record.")
b22c9ebf 308\f
fbee9727 309;;; Optional variables with default values.
c1110355 310
4fd3a710
RS
311(defvar forms-check-number-of-fields t
312 "If non-nil, warn about records with wrong number of fields.")
313
c1110355 314(defvar forms-field-sep "\t"
b22c9ebf 315 "Field separator character (default TAB).")
c1110355
BP
316
317(defvar forms-read-only nil
fbee9727 318 "Non-nil means: visit the file in view (read-only) mode.
a4e104bf 319\(Defaults to the write access on the data file).")
c1110355
BP
320
321(defvar forms-multi-line "\C-k"
fbee9727 322 "If not nil: use this character to separate multi-line fields (default C-k).")
c1110355 323
2cc27dd3 324(defvar forms-forms-scroll nil
ea3d9551
RS
325 "*Non-nil means replace scroll-up/down commands in Forms mode.
326The replacement commands performs forms-next/prev-record.")
c1110355 327
2cc27dd3 328(defvar forms-forms-jump nil
ea3d9551
RS
329 "*Non-nil means redefine beginning/end-of-buffer in Forms mode.
330The replacement commands performs forms-first/last-record.")
fbee9727 331
9c308ed2
RS
332(defvar forms-read-file-filter nil
333 "The name of a function that is called after reading the data file.
334This can be used to change the contents of the file to something more
335suitable for forms processing.")
336
337(defvar forms-write-file-filter nil
338 "The name of a function that is called before writing the data file.
339This can be used to undo the effects of form-read-file-hook.")
340
fbee9727
RS
341(defvar forms-new-record-filter nil
342 "The name of a function that is called when a new record is created.")
343
344(defvar forms-modified-record-filter nil
345 "The name of a function that is called when a record has been modified.")
346
347(defvar forms-fields nil
348 "List with fields of the current forms. First field has number 1.
349This variable is for use by the filter routines only.
350The contents may NOT be modified.")
351
352(defvar forms-use-text-properties (fboundp 'set-text-properties)
353 "*Non-nil means: use emacs-19 text properties.
354Defaults to t if this emacs is capable of handling text properties.")
355
356(defvar forms-ro-face 'default
357 "The face (a symbol) that is used to display read-only text on the screen.")
358
359(defvar forms-rw-face 'region
360 "The face (a symbol) that is used to display read-write text on the screen.")
b22c9ebf 361\f
c1110355
BP
362;;; Internal variables.
363
364(defvar forms--file-buffer nil
365 "Buffer which holds the file data")
366
367(defvar forms--total-records 0
368 "Total number of records in the data file.")
369
370(defvar forms--current-record 0
371 "Number of the record currently on the screen.")
372
2cc27dd3 373(defvar forms-mode-map nil
c1110355 374 "Keymap for form buffer.")
2cc27dd3
RS
375(defvar forms-mode-ro-map nil
376 "Keymap for form buffer in view mode.")
377(defvar forms-mode-edit-map nil
378 "Keymap for form buffer in edit mode.")
c1110355
BP
379
380(defvar forms--markers nil
381 "Field markers in the screen.")
382
fbee9727
RS
383(defvar forms--dyntexts nil
384 "Dynamic texts (resulting from function calls) on the screen.")
c1110355
BP
385
386(defvar forms--the-record-list nil
387 "List of strings of the current record, as parsed from the file.")
388
389(defvar forms--search-regexp nil
ac7e3dbe 390 "Last regexp used by forms-search functions.")
c1110355
BP
391
392(defvar forms--format nil
393 "Formatting routine.")
394
395(defvar forms--parser nil
396 "Forms parser routine.")
397
398(defvar forms--mode-setup nil
fbee9727 399 "To keep track of forms-mode being set-up.")
c1110355
BP
400(make-variable-buffer-local 'forms--mode-setup)
401
01a45313 402(defvar forms--dynamic-text nil
fbee9727 403 "Array that holds dynamic texts to insert between fields.")
01a45313 404
fbee9727
RS
405(defvar forms--elements nil
406 "Array with the order in which the fields are displayed.")
01a45313 407
fbee9727
RS
408(defvar forms--ro-face nil
409 "Face used to represent read-only data on the screen.")
b22c9ebf 410
fbee9727
RS
411(defvar forms--rw-face nil
412 "Face used to represent read-write data on the screen.")
b22c9ebf 413\f
ac2a7a91 414;;;###autoload
c1110355
BP
415(defun forms-mode (&optional primary)
416 "Major mode to visit files in a field-structured manner using a form.
417
2cc27dd3
RS
418Commands: Equivalent keys in read-only mode:
419 TAB forms-next-field TAB
420 \\C-c TAB forms-next-field
421 \\C-c < forms-first-record <
422 \\C-c > forms-last-record >
423 \\C-c ? describe-mode ?
424 \\C-c \\C-k forms-delete-record
425 \\C-c \\C-q forms-toggle-read-only q
426 \\C-c \\C-o forms-insert-record
427 \\C-c \\C-l forms-jump-record l
428 \\C-c \\C-n forms-next-record n
429 \\C-c \\C-p forms-prev-record p
ac7e3dbe
JV
430 \\C-c \\C-r forms-search-reverse r
431 \\C-c \\C-s forms-search-forward s
2cc27dd3
RS
432 \\C-c \\C-x forms-exit x
433"
434 (interactive)
c1110355 435
fbee9727
RS
436 ;; This is not a simple major mode, as usual. Therefore, forms-mode
437 ;; takes an optional argument `primary' which is used for the
438 ;; initial set-up. Normal use would leave `primary' to nil.
439 ;; A global buffer-local variable `forms--mode-setup' has the same
440 ;; effect but makes it possible to auto-invoke forms-mode using
441 ;; `find-file'.
442 ;; Note: although it seems logical to have `make-local-variable'
443 ;; executed where the variable is first needed, I have deliberately
444 ;; placed all calls in this function.
445
c1110355
BP
446 ;; Primary set-up: evaluate buffer and check if the mandatory
447 ;; variables have been set.
448 (if (or primary (not forms--mode-setup))
449 (progn
fbee9727 450 ;;(message "forms: setting up...")
c1110355
BP
451 (kill-all-local-variables)
452
fbee9727 453 ;; Make mandatory variables.
c1110355
BP
454 (make-local-variable 'forms-file)
455 (make-local-variable 'forms-number-of-fields)
456 (make-local-variable 'forms-format-list)
457
fbee9727 458 ;; Make optional variables.
c1110355
BP
459 (make-local-variable 'forms-field-sep)
460 (make-local-variable 'forms-read-only)
461 (make-local-variable 'forms-multi-line)
462 (make-local-variable 'forms-forms-scroll)
463 (make-local-variable 'forms-forms-jump)
fbee9727 464 (make-local-variable 'forms-use-text-properties)
9c308ed2
RS
465
466 ;; Filter functions.
467 (make-local-variable 'forms-read-file-filter)
468 (make-local-variable 'forms-write-file-filter)
2cc27dd3
RS
469 (make-local-variable 'forms-new-record-filter)
470 (make-local-variable 'forms-modified-record-filter)
fbee9727
RS
471
472 ;; Make sure no filters exist.
9c308ed2
RS
473 (setq forms-read-file-filter nil)
474 (setq forms-write-file-filter nil)
2cc27dd3
RS
475 (setq forms-new-record-filter nil)
476 (setq forms-modified-record-filter nil)
fbee9727
RS
477
478 ;; If running Emacs 19 under X, setup faces to show read-only and
479 ;; read-write fields.
480 (if (fboundp 'make-face)
481 (progn
482 (make-local-variable 'forms-ro-face)
483 (make-local-variable 'forms-rw-face)))
c1110355
BP
484
485 ;; eval the buffer, should set variables
fbee9727 486 ;;(message "forms: processing control file...")
485efad0
RS
487 ;; If enable-local-eval is not set to t the user is asked first.
488 (if (or (eq enable-local-eval t)
489 (yes-or-no-p
490 (concat "Evaluate lisp code in buffer "
491 (buffer-name) " to display forms ")))
492 (eval-current-buffer)
493 (error "`enable-local-eval' inhibits buffer evaluation"))
c1110355 494
9c308ed2 495 ;; Check if the mandatory variables make sense.
c1110355 496 (or forms-file
2cc27dd3
RS
497 (error (concat "Forms control file error: "
498 "'forms-file' has not been set")))
9c308ed2
RS
499
500 ;; Check forms-field-sep first, since it can be needed to
501 ;; construct a default format list.
2cc27dd3
RS
502 (or (stringp forms-field-sep)
503 (error (concat "Forms control file error: "
504 "'forms-field-sep' is not a string")))
9c308ed2
RS
505
506 (if forms-number-of-fields
507 (or (and (numberp forms-number-of-fields)
508 (> forms-number-of-fields 0))
509 (error (concat "Forms control file error: "
510 "'forms-number-of-fields' must be a number > 0")))
511 (or (null forms-format-list)
512 (error (concat "Forms control file error: "
513 "'forms-number-of-fields' has not been set"))))
514
515 (or forms-format-list
516 (forms--intuit-from-file))
517
c1110355
BP
518 (if forms-multi-line
519 (if (and (stringp forms-multi-line)
520 (eq (length forms-multi-line) 1))
521 (if (string= forms-multi-line forms-field-sep)
2cc27dd3
RS
522 (error (concat "Forms control file error: "
523 "'forms-multi-line' is equal to 'forms-field-sep'")))
524 (error (concat "Forms control file error: "
525 "'forms-multi-line' must be nil or a one-character string"))))
fbee9727
RS
526 (or (fboundp 'set-text-properties)
527 (setq forms-use-text-properties nil))
c1110355 528
fbee9727
RS
529 ;; Validate and process forms-format-list.
530 ;;(message "forms: pre-processing format list...")
c1110355
BP
531 (forms--process-format-list)
532
fbee9727
RS
533 ;; Build the formatter and parser.
534 ;;(message "forms: building formatter...")
c1110355 535 (make-local-variable 'forms--format)
fbee9727
RS
536 (make-local-variable 'forms--markers)
537 (make-local-variable 'forms--dyntexts)
538 (make-local-variable 'forms--elements)
539 ;;(message "forms: building parser...")
c1110355
BP
540 (forms--make-format)
541 (make-local-variable 'forms--parser)
542 (forms--make-parser)
fbee9727 543 ;;(message "forms: building parser... done.")
c1110355 544
fbee9727 545 ;; Check if record filters are defined.
2cc27dd3
RS
546 (if (and forms-new-record-filter
547 (not (fboundp forms-new-record-filter)))
548 (error (concat "Forms control file error: "
549 "'forms-new-record-filter' is not a function")))
550
551 (if (and forms-modified-record-filter
552 (not (fboundp forms-modified-record-filter)))
553 (error (concat "Forms control file error: "
554 "'forms-modified-record-filter' is not a function")))
01a45313 555
fbee9727 556 ;; The filters acces the contents of the forms using `forms-fields'.
01a45313 557 (make-local-variable 'forms-fields)
c1110355 558
fbee9727
RS
559 ;; Dynamic text support.
560 (make-local-variable 'forms--dynamic-text)
c1110355 561
fbee9727 562 ;; Prevent accidental overwrite of the control file and autosave.
724244d2 563 (set-visited-file-name nil)
c1110355 564
fbee9727
RS
565 ;; Prepare this buffer for further processing.
566 (setq buffer-read-only nil)
567 (erase-buffer)
568
569 ;;(message "forms: setting up... done.")
570 ))
571
485efad0
RS
572 ;; initialization done
573 (setq forms--mode-setup t)
574
fbee9727
RS
575 ;; Copy desired faces to the actual variables used by the forms formatter.
576 (if (fboundp 'make-face)
577 (progn
578 (make-local-variable 'forms--ro-face)
579 (make-local-variable 'forms--rw-face)
580 (if forms-read-only
581 (progn
582 (setq forms--ro-face forms-ro-face)
583 (setq forms--rw-face forms-ro-face))
584 (setq forms--ro-face forms-ro-face)
585 (setq forms--rw-face forms-rw-face))))
c1110355 586
ac2a7a91 587 ;; Make more local variables.
c1110355
BP
588 (make-local-variable 'forms--file-buffer)
589 (make-local-variable 'forms--total-records)
590 (make-local-variable 'forms--current-record)
591 (make-local-variable 'forms--the-record-list)
fbee9727 592 (make-local-variable 'forms--search-regexp)
c1110355 593
2cc27dd3
RS
594 ; The keymaps are global, so multiple forms mode buffers can share them.
595 ;(make-local-variable 'forms-mode-map)
596 ;(make-local-variable 'forms-mode-ro-map)
597 ;(make-local-variable 'forms-mode-edit-map)
c1110355
BP
598 (if forms-mode-map ; already defined
599 nil
fbee9727 600 ;;(message "forms: building keymap...")
2cc27dd3 601 (forms--mode-commands)
fbee9727
RS
602 ;;(message "forms: building keymap... done.")
603 )
c1110355 604
4a971a93
KH
605 ;; set the major mode indicator
606 (setq major-mode 'forms-mode)
607 (setq mode-name "Forms")
608
c1110355
BP
609 ;; find the data file
610 (setq forms--file-buffer (find-file-noselect forms-file))
611
9c308ed2
RS
612 ;; Pre-transform.
613 (let ((read-file-filter forms-read-file-filter)
614 (write-file-filter forms-write-file-filter))
615 (if read-file-filter
616 (save-excursion
617 (set-buffer forms--file-buffer)
ac7e3dbe
JV
618 (let ((inhibit-read-only t)
619 (file-modified (buffer-modified-p)))
620 (run-hooks 'read-file-filter)
621 (if (not file-modified) (set-buffer-modified-p nil)))
9c308ed2
RS
622 (if write-file-filter
623 (progn
624 (make-variable-buffer-local 'local-write-file-hooks)
625 (setq local-write-file-hooks (list write-file-filter)))))
626 (if write-file-filter
627 (save-excursion
628 (set-buffer forms--file-buffer)
629 (make-variable-buffer-local 'local-write-file-hooks)
be3e4bb1 630 (setq local-write-file-hooks (list write-file-filter))))))
9c308ed2 631
c1110355
BP
632 ;; count the number of records, and set see if it may be modified
633 (let (ro)
634 (setq forms--total-records
635 (save-excursion
fbee9727
RS
636 (prog1
637 (progn
638 ;;(message "forms: counting records...")
639 (set-buffer forms--file-buffer)
640 (bury-buffer (current-buffer))
641 (setq ro buffer-read-only)
642 (count-lines (point-min) (point-max)))
643 ;;(message "forms: counting records... done.")
644 )))
c1110355
BP
645 (if ro
646 (setq forms-read-only t)))
647
fbee9727 648 ;;(message "forms: proceeding setup...")
891f0daa
RS
649
650 ;; Since we aren't really implementing a minor mode, we hack the modeline
651 ;; directly to get the text " View " into forms-read-only form buffers. For
652 ;; that reason, this variable must be buffer only.
653 (make-local-variable 'minor-mode-alist)
654 (setq minor-mode-alist (list (list 'forms-read-only " View")))
655
fbee9727 656 ;;(message "forms: proceeding setup (keymaps)...")
c1110355 657 (forms--set-keymaps)
fbee9727 658 ;;(message "forms: proceeding setup (commands)...")
0cfa68a9 659 (forms--change-commands)
c1110355 660
fbee9727 661 ;;(message "forms: proceeding setup (buffer)...")
c1110355
BP
662 (set-buffer-modified-p nil)
663
9c308ed2
RS
664 (if (= forms--total-records 0)
665 ;;(message "forms: proceeding setup (new file)...")
666 (progn
667 (insert
668 "GNU Emacs Forms Mode version " forms-version "\n\n"
669 (if (file-exists-p forms-file)
670 (concat "No records available in file \"" forms-file "\".\n\n")
671 (format "Creating new file \"%s\"\nwith %d field%s per record.\n\n"
672 forms-file forms-number-of-fields
673 (if (= 1 forms-number-of-fields) "" "s")))
674 "Use " (substitute-command-keys "\\[forms-insert-record]")
675 " to create new records.\n")
676 (setq forms--current-record 1)
677 (setq buffer-read-only t)
678 (set-buffer-modified-p nil))
679
680 ;; setup the first (or current) record to show
681 (if (< forms--current-record 1)
682 (setq forms--current-record 1))
683 (forms-jump-record forms--current-record)
684 )
c1110355
BP
685
686 ;; user customising
fbee9727 687 ;;(message "forms: proceeding setup (user hooks)...")
c1110355 688 (run-hooks 'forms-mode-hooks)
fbee9727 689 ;;(message "forms: setting up... done.")
c1110355
BP
690
691 ;; be helpful
692 (forms--help)
485efad0 693)
b22c9ebf 694\f
c1110355 695(defun forms--process-format-list ()
fbee9727
RS
696 ;; Validate `forms-format-list' and set some global variables.
697 ;; Symbols in the list are evaluated, and consecutive strings are
698 ;; concatenated.
699 ;; Array `forms--elements' is constructed that contains the order
700 ;; of the fields on the display. This array is used by
701 ;; `forms--parser-using-text-properties' to extract the fields data
702 ;; from the form on the screen.
703 ;; Upon completion, `forms-format-list' is garanteed correct, so
704 ;; `forms--make-format' and `forms--make-parser' do not need to perform
705 ;; any checks.
706
707 ;; Verify that `forms-format-list' is not nil.
c1110355 708 (or forms-format-list
2cc27dd3
RS
709 (error (concat "Forms control file error: "
710 "'forms-format-list' has not been set")))
fbee9727 711 ;; It must be a list.
c1110355 712 (or (listp forms-format-list)
2cc27dd3
RS
713 (error (concat "Forms control file error: "
714 "'forms-format-list' is not a list")))
c1110355 715
fbee9727
RS
716 ;; Assume every field is painted once.
717 ;; `forms--elements' will grow if needed.
718 (setq forms--elements (make-vector forms-number-of-fields nil))
c1110355
BP
719
720 (let ((the-list forms-format-list) ; the list of format elements
01a45313 721 (this-item 0) ; element in list
fbee9727 722 (prev-item nil)
c1110355
BP
723 (field-num 0)) ; highest field number
724
01a45313
RS
725 (setq forms-format-list nil) ; gonna rebuild
726
c1110355
BP
727 (while the-list
728
729 (let ((el (car-safe the-list))
730 (rem (cdr-safe the-list)))
731
fbee9727 732 ;; If it is a symbol, eval it first.
01a45313
RS
733 (if (and (symbolp el)
734 (boundp el))
735 (setq el (eval el)))
736
c1110355
BP
737 (cond
738
fbee9727
RS
739 ;; Try string ...
740 ((stringp el)
741 (if (stringp prev-item) ; try to concatenate strings
742 (setq prev-item (concat prev-item el))
743 (if prev-item
744 (setq forms-format-list
745 (append forms-format-list (list prev-item) nil)))
746 (setq prev-item el)))
747
748 ;; Try numeric ...
01a45313 749 ((numberp el)
c1110355 750
fbee9727 751 ;; Validate range.
c1110355
BP
752 (if (or (<= el 0)
753 (> el forms-number-of-fields))
2cc27dd3
RS
754 (error (concat "Forms format error: "
755 "field number %d out of range 1..%d")
756 el forms-number-of-fields))
c1110355 757
fbee9727
RS
758 ;; Store forms order.
759 (if (> field-num (length forms--elements))
760 (setq forms--elements (vconcat forms--elements (1- el)))
761 (aset forms--elements field-num (1- el)))
762 (setq field-num (1+ field-num))
763
fbee9727
RS
764 (if prev-item
765 (setq forms-format-list
1f111018 766 (append forms-format-list (list prev-item) nil)))
fbee9727
RS
767 (setq prev-item el))
768
769 ;; Try function ...
01a45313 770 ((listp el)
fbee9727
RS
771
772 ;; Validate.
01a45313 773 (or (fboundp (car-safe el))
2cc27dd3
RS
774 (error (concat "Forms format error: "
775 "not a function "
776 (prin1-to-string (car-safe el)))))
fbee9727
RS
777
778 ;; Shift.
779 (if prev-item
780 (setq forms-format-list
781 (append forms-format-list (list prev-item) nil)))
782 (setq prev-item el))
01a45313 783
c1110355
BP
784 ;; else
785 (t
2cc27dd3
RS
786 (error (concat "Forms format error: "
787 "invalid element "
788 (prin1-to-string el)))))
c1110355 789
fbee9727
RS
790 ;; Advance to next element of the list.
791 (setq the-list rem)))
c1110355 792
fbee9727
RS
793 ;; Append last item.
794 (if prev-item
795 (progn
796 (setq forms-format-list
797 (append forms-format-list (list prev-item) nil))
798 ;; Append a newline if the last item is a field.
1f111018 799 ;; This prevents parsing problems.
fbee9727
RS
800 ;; Also it makes it possible to insert an empty last field.
801 (if (numberp prev-item)
802 (setq forms-format-list
803 (append forms-format-list (list "\n") nil))))))
804
805 (forms--debug 'forms-format-list
806 'forms--elements))
b22c9ebf 807\f
fbee9727
RS
808;; Special treatment for read-only segments.
809;;
1f111018 810;; If text is inserted between two read-only segments, it inherits the
fbee9727 811;; read-only properties. This is not what we want.
1f111018
RS
812;; To solve this, read-only segments get the `insert-in-front-hooks'
813;; property set with a function that temporarily switches the properties
814;; of the first character of the segment to read-write, so the new
fbee9727 815;; text gets the right properties.
1f111018
RS
816;; The `post-command-hook' is used to restore the original properties.
817
818(defvar forms--iif-start nil
fbee9727 819 "Record start of modification command.")
1f111018 820(defvar forms--iif-properties nil
fbee9727
RS
821 "Original properties of the character being overridden.")
822
1f111018
RS
823(defun forms--iif-hook (begin end)
824 "`insert-in-front-hooks' function for read-only segments."
fbee9727 825
1f111018
RS
826 ;; Note start location. By making it a marker that points one
827 ;; character beyond the actual location, it is guaranteed to move
828 ;; correctly if text is inserted.
829 (or forms--iif-start
830 (setq forms--iif-start (copy-marker (1+ (point)))))
fbee9727 831
1f111018
RS
832 ;; Check if there is special treatment required.
833 (if (or (<= forms--iif-start 2)
834 (get-text-property (- forms--iif-start 2)
835 'read-only))
836 (progn
837 ;; Fetch current properties.
838 (setq forms--iif-properties
839 (text-properties-at (1- forms--iif-start)))
fbee9727 840
1f111018
RS
841 ;; Replace them.
842 (let ((inhibit-read-only t))
843 (set-text-properties
844 (1- forms--iif-start) forms--iif-start
845 (list 'face forms--rw-face 'front-sticky '(face))))
fbee9727 846
1f111018
RS
847 ;; Enable `post-command-hook' to restore the properties.
848 (setq post-command-hook
849 (append (list 'forms--iif-post-command-hook) post-command-hook)))
fbee9727 850
1f111018
RS
851 ;; No action needed. Clear marker.
852 (setq forms--iif-start nil)))
fbee9727 853
1f111018
RS
854(defun forms--iif-post-command-hook ()
855 "`post-command-hook' function for read-only segments."
fbee9727
RS
856
857 ;; Disable `post-command-hook'.
858 (setq post-command-hook
1f111018 859 (delq 'forms--iif-hook-post-command-hook post-command-hook))
fbee9727
RS
860
861 ;; Restore properties.
1f111018 862 (if forms--iif-start
fbee9727
RS
863 (let ((inhibit-read-only t))
864 (set-text-properties
1f111018
RS
865 (1- forms--iif-start) forms--iif-start
866 forms--iif-properties)))
fbee9727
RS
867
868 ;; Cleanup.
1f111018 869 (setq forms--iif-start nil))
fbee9727
RS
870\f
871(defvar forms--marker)
872(defvar forms--dyntext)
c1110355
BP
873
874(defun forms--make-format ()
fbee9727
RS
875 "Generate `forms--format' using the information in `forms-format-list'."
876
877 ;; The real work is done using a mapcar of `forms--make-format-elt' on
878 ;; `forms-format-list'.
879 ;; This function sets up the necessary environment, and decides
880 ;; which function to mapcar.
881
882 (let ((forms--marker 0)
883 (forms--dyntext 0))
884 (setq
885 forms--format
886 (if forms-use-text-properties
887 (` (lambda (arg)
888 (let ((inhibit-read-only t))
fbee9727
RS
889 (,@ (apply 'append
890 (mapcar 'forms--make-format-elt-using-text-properties
1f111018
RS
891 forms-format-list)))
892 ;; Prevent insertion before the first text.
893 (,@ (if (numberp (car forms-format-list))
894 nil
895 '((add-text-properties (point-min) (1+ (point-min))
2996d9f8 896 '(front-sticky (read-only intangible))))))
1f111018
RS
897 ;; Prevent insertion after the last text.
898 (remove-text-properties (1- (point)) (point)
899 '(rear-nonsticky)))
900 (setq forms--iif-start nil)))
fbee9727
RS
901 (` (lambda (arg)
902 (,@ (apply 'append
903 (mapcar 'forms--make-format-elt forms-format-list)))))))
904
905 ;; We have tallied the number of markers and dynamic texts,
906 ;; so we can allocate the arrays now.
907 (setq forms--markers (make-vector forms--marker nil))
908 (setq forms--dyntexts (make-vector forms--dyntext nil)))
01a45313 909 (forms--debug 'forms--format))
c1110355 910
fbee9727
RS
911(defun forms--make-format-elt-using-text-properties (el)
912 "Helper routine to generate format function."
913
914 ;; The format routine `forms--format' will look like
915 ;;
916 ;; ;; preamble
917 ;; (lambda (arg)
918 ;; (let ((inhibit-read-only t))
fbee9727 919 ;;
1f111018 920 ;; ;; A string, e.g. "text: ".
fbee9727
RS
921 ;; (set-text-properties
922 ;; (point)
923 ;; (progn (insert "text: ") (point))
1f111018
RS
924 ;; (list 'face forms--ro-face
925 ;; 'read-only 1
926 ;; 'insert-in-front-hooks 'forms--iif-hook
927 ;; 'rear-nonsticky '(read-only face insert-in-front-hooks)))
fbee9727 928 ;;
1f111018 929 ;; ;; A field, e.g. 6.
fbee9727
RS
930 ;; (let ((here (point)))
931 ;; (aset forms--markers 0 (point-marker))
932 ;; (insert (elt arg 5))
933 ;; (or (= (point) here)
934 ;; (set-text-properties
935 ;; here (point)
1f111018
RS
936 ;; (list 'face forms--rw-face
937 ;; 'front-sticky '(face))))
fbee9727 938 ;;
1f111018 939 ;; ;; Another string, e.g. "\nmore text: ".
fbee9727
RS
940 ;; (set-text-properties
941 ;; (point)
942 ;; (progn (insert "\nmore text: ") (point))
943 ;; (list 'face forms--ro-face
1f111018
RS
944 ;; 'read-only 2
945 ;; 'insert-in-front-hooks 'forms--iif-hook
946 ;; 'rear-nonsticky '(read-only face insert-in-front-hooks)))
fbee9727 947 ;;
1f111018 948 ;; ;; A function, e.g. (tocol 40).
fbee9727
RS
949 ;; (set-text-properties
950 ;; (point)
951 ;; (progn
952 ;; (insert (aset forms--dyntexts 0 (tocol 40)))
953 ;; (point))
954 ;; (list 'face forms--ro-face
1f111018
RS
955 ;; 'read-only 2
956 ;; 'insert-in-front-hooks 'forms--iif-hook
957 ;; 'rear-nonsticky '(read-only face insert-in-front-hooks)))
958 ;;
959 ;; ;; Prevent insertion before the first text.
960 ;; (add-text-properties (point-min) (1+ (point-min))
961 ;; '(front-sticky (read-only))))))
962 ;; ;; Prevent insertion after the last text.
963 ;; (remove-text-properties (1- (point)) (point)
964 ;; '(rear-nonsticky)))
fbee9727
RS
965 ;;
966 ;; ;; wrap up
1f111018 967 ;; (setq forms--iif-start nil)
fbee9727
RS
968 ;; ))
969
970 (cond
971 ((stringp el)
972
973 (` ((set-text-properties
974 (point) ; start at point
975 (progn ; until after insertion
976 (insert (, el))
977 (point))
978 (list 'face forms--ro-face ; read-only appearance
1f111018 979 'read-only (,@ (list (1+ forms--marker)))
2996d9f8 980 'intangible t
1f111018 981 'insert-in-front-hooks '(forms--iif-hook)
2996d9f8
RS
982 'rear-nonsticky '(face read-only insert-in-front-hooks
983 intangible))))))
1f111018 984
fbee9727
RS
985 ((numberp el)
986 (` ((let ((here (point)))
987 (aset forms--markers
988 (, (prog1 forms--marker
989 (setq forms--marker (1+ forms--marker))))
990 (point-marker))
991 (insert (elt arg (, (1- el))))
992 (or (= (point) here)
993 (set-text-properties
994 here (point)
1f111018
RS
995 (list 'face forms--rw-face
996 'front-sticky '(face))))))))
fbee9727
RS
997
998 ((listp el)
999 (` ((set-text-properties
1000 (point)
1001 (progn
1002 (insert (aset forms--dyntexts
1003 (, (prog1 forms--dyntext
1004 (setq forms--dyntext (1+ forms--dyntext))))
1005 (, el)))
1006 (point))
1007 (list 'face forms--ro-face
1f111018 1008 'read-only (,@ (list (1+ forms--marker)))
2996d9f8 1009 'intangible t
1f111018 1010 'insert-in-front-hooks '(forms--iif-hook)
2996d9f8
RS
1011 'rear-nonsticky '(read-only face insert-in-front-hooks
1012 intangible))))))
fbee9727
RS
1013
1014 ;; end of cond
1015 ))
c1110355
BP
1016
1017(defun forms--make-format-elt (el)
fbee9727
RS
1018 "Helper routine to generate format function."
1019
1020 ;; If we're not using text properties, the format routine
1021 ;; `forms--format' will look like
1022 ;;
1023 ;; (lambda (arg)
1024 ;; ;; a string, e.g. "text: "
1025 ;; (insert "text: ")
1026 ;; ;; a field, e.g. 6
1027 ;; (aset forms--markers 0 (point-marker))
1028 ;; (insert (elt arg 5))
1029 ;; ;; another string, e.g. "\nmore text: "
1030 ;; (insert "\nmore text: ")
1031 ;; ;; a function, e.g. (tocol 40)
1032 ;; (insert (aset forms--dyntexts 0 (tocol 40)))
1033 ;; ... )
1034
b22c9ebf
RS
1035 (cond
1036 ((stringp el)
1037 (` ((insert (, el)))))
1038 ((numberp el)
1039 (prog1
fbee9727 1040 (` ((aset forms--markers (, forms--marker) (point-marker))
b22c9ebf 1041 (insert (elt arg (, (1- el))))))
fbee9727 1042 (setq forms--marker (1+ forms--marker))))
b22c9ebf
RS
1043 ((listp el)
1044 (prog1
fbee9727
RS
1045 (` ((insert (aset forms--dyntexts (, forms--dyntext) (, el)))))
1046 (setq forms--dyntext (1+ forms--dyntext))))))
b22c9ebf 1047\f
fbee9727
RS
1048(defvar forms--field)
1049(defvar forms--recordv)
1050(defvar forms--seen-text)
c1110355
BP
1051
1052(defun forms--make-parser ()
fbee9727
RS
1053 "Generate `forms--parser' from the information in `forms-format-list'."
1054
1055 ;; If we can use text properties, we simply set it to
1056 ;; `forms--parser-using-text-properties'.
1057 ;; Otherwise, the function is constructed using a mapcar of
1058 ;; `forms--make-parser-elt on `forms-format-list'.
1059
1060 (setq
1061 forms--parser
1062 (if forms-use-text-properties
1063 (function forms--parser-using-text-properties)
1064 (let ((forms--field nil)
1065 (forms--seen-text nil)
1066 (forms--dyntext 0))
1067
1068 ;; Note: we add a nil element to the list passed to `mapcar',
1069 ;; see `forms--make-parser-elt' for details.
1070 (` (lambda nil
1071 (let (here)
1072 (goto-char (point-min))
1073 (,@ (apply 'append
1074 (mapcar
1075 'forms--make-parser-elt
1076 (append forms-format-list (list nil)))))))))))
1077
01a45313 1078 (forms--debug 'forms--parser))
c1110355 1079
fbee9727
RS
1080(defun forms--parser-using-text-properties ()
1081 "Extract field info from forms when using text properties."
1082
1083 ;; Using text properties, we can simply jump to the markers, and
1084 ;; extract the information up to the following read-only segment.
1085
1086 (let ((i 0)
1087 here there)
1088 (while (< i (length forms--markers))
1089 (goto-char (setq here (aref forms--markers i)))
1090 (if (get-text-property here 'read-only)
1091 (aset forms--recordv (aref forms--elements i) nil)
1092 (if (setq there
1093 (next-single-property-change here 'read-only))
1094 (aset forms--recordv (aref forms--elements i)
1095 (buffer-substring here there))
1096 (aset forms--recordv (aref forms--elements i)
1097 (buffer-substring here (point-max)))))
1098 (setq i (1+ i)))))
c1110355
BP
1099
1100(defun forms--make-parser-elt (el)
fbee9727
RS
1101 "Helper routine to generate forms parser function."
1102
1103 ;; The parse routine will look like:
1104 ;;
1105 ;; (lambda nil
1106 ;; (let (here)
1107 ;; (goto-char (point-min))
1108 ;;
1109 ;; ;; "text: "
1110 ;; (if (not (looking-at "text: "))
1111 ;; (error "Parse error: cannot find \"text: \""))
1112 ;; (forward-char 6) ; past "text: "
1113 ;;
1114 ;; ;; 6
1115 ;; ;; "\nmore text: "
1116 ;; (setq here (point))
1117 ;; (if (not (search-forward "\nmore text: " nil t nil))
1118 ;; (error "Parse error: cannot find \"\\nmore text: \""))
1119 ;; (aset forms--recordv 5 (buffer-substring here (- (point) 12)))
1120 ;;
1121 ;; ;; (tocol 40)
1122 ;; (let ((forms--dyntext (car-safe forms--dynamic-text)))
1123 ;; (if (not (looking-at (regexp-quote forms--dyntext)))
1124 ;; (error "Parse error: not looking at \"%s\"" forms--dyntext))
1125 ;; (forward-char (length forms--dyntext))
1126 ;; (setq forms--dynamic-text (cdr-safe forms--dynamic-text)))
1127 ;; ...
1128 ;; ;; final flush (due to terminator sentinel, see below)
1129 ;; (aset forms--recordv 7 (buffer-substring (point) (point-max)))
1130
01a45313
RS
1131 (cond
1132 ((stringp el)
1133 (prog1
fbee9727 1134 (if forms--field
01a45313
RS
1135 (` ((setq here (point))
1136 (if (not (search-forward (, el) nil t nil))
1137 (error "Parse error: cannot find \"%s\"" (, el)))
fbee9727 1138 (aset forms--recordv (, (1- forms--field))
01a45313
RS
1139 (buffer-substring here
1140 (- (point) (, (length el)))))))
1141 (` ((if (not (looking-at (, (regexp-quote el))))
1142 (error "Parse error: not looking at \"%s\"" (, el)))
1143 (forward-char (, (length el))))))
fbee9727
RS
1144 (setq forms--seen-text t)
1145 (setq forms--field nil)))
01a45313 1146 ((numberp el)
fbee9727 1147 (if forms--field
01a45313 1148 (error "Cannot parse adjacent fields %d and %d"
fbee9727
RS
1149 forms--field el)
1150 (setq forms--field el)
01a45313
RS
1151 nil))
1152 ((null el)
fbee9727
RS
1153 (if forms--field
1154 (` ((aset forms--recordv (, (1- forms--field))
01a45313
RS
1155 (buffer-substring (point) (point-max)))))))
1156 ((listp el)
1157 (prog1
fbee9727 1158 (if forms--field
01a45313 1159 (` ((let ((here (point))
fbee9727
RS
1160 (forms--dyntext (aref forms--dyntexts (, forms--dyntext))))
1161 (if (not (search-forward forms--dyntext nil t nil))
1162 (error "Parse error: cannot find \"%s\"" forms--dyntext))
1163 (aset forms--recordv (, (1- forms--field))
01a45313 1164 (buffer-substring here
fbee9727
RS
1165 (- (point) (length forms--dyntext)))))))
1166 (` ((let ((forms--dyntext (aref forms--dyntexts (, forms--dyntext))))
1167 (if (not (looking-at (regexp-quote forms--dyntext)))
1168 (error "Parse error: not looking at \"%s\"" forms--dyntext))
1169 (forward-char (length forms--dyntext))))))
1170 (setq forms--dyntext (1+ forms--dyntext))
1171 (setq forms--seen-text t)
1172 (setq forms--field nil)))
01a45313 1173 ))
b22c9ebf 1174\f
9c308ed2
RS
1175(defun forms--intuit-from-file ()
1176 "Get number of fields and a default form using the data file."
1177
1178 ;; If `forms-number-of-fields' is not set, get it from the data file.
1179 (if (null forms-number-of-fields)
1180
1181 ;; Need a file to do this.
1182 (if (not (file-exists-p forms-file))
1183 (error "Need existing file or explicit 'forms-number-of-records'.")
1184
1185 ;; Visit the file and extract the first record.
1186 (setq forms--file-buffer (find-file-noselect forms-file))
1187 (let ((read-file-filter forms-read-file-filter)
1188 (the-record))
1189 (setq the-record
1190 (save-excursion
1191 (set-buffer forms--file-buffer)
1192 (let ((inhibit-read-only t))
1193 (run-hooks 'read-file-filter))
1194 (goto-char (point-min))
1195 (forms--get-record)))
1196
1197 ;; This may be overkill, but try to avoid interference with
1198 ;; the normal processing.
1199 (kill-buffer forms--file-buffer)
1200
1201 ;; Count the number of fields in `the-record'.
1202 (let (the-result
1203 (start-pos 0)
1204 found-pos
1205 (field-sep-length (length forms-field-sep)))
1206 (setq forms-number-of-fields 1)
1207 (while (setq found-pos
1208 (string-match forms-field-sep the-record start-pos))
1209 (progn
1210 (setq forms-number-of-fields (1+ forms-number-of-fields))
1211 (setq start-pos (+ field-sep-length found-pos))))))))
1212
1213 ;; Construct default format list.
1214 (setq forms-format-list (list "Forms file \"" forms-file "\".\n\n"))
1215 (let ((i 0))
1216 (while (<= (setq i (1+ i)) forms-number-of-fields)
1217 (setq forms-format-list
1218 (append forms-format-list
1219 (list (format "%4d: " i) i "\n"))))))
1220\f
c1110355
BP
1221(defun forms--set-keymaps ()
1222 "Set the keymaps used in this mode."
1223
2cc27dd3
RS
1224 (use-local-map (if forms-read-only
1225 forms-mode-ro-map
1226 forms-mode-edit-map)))
1227
1228(defun forms--mode-commands ()
1229 "Fill the Forms mode keymaps."
1230
1231 ;; `forms-mode-map' is always accessible via \C-c prefix.
1232 (setq forms-mode-map (make-keymap))
1233 (define-key forms-mode-map "\t" 'forms-next-field)
1234 (define-key forms-mode-map "\C-k" 'forms-delete-record)
1235 (define-key forms-mode-map "\C-q" 'forms-toggle-read-only)
1236 (define-key forms-mode-map "\C-o" 'forms-insert-record)
1237 (define-key forms-mode-map "\C-l" 'forms-jump-record)
1238 (define-key forms-mode-map "\C-n" 'forms-next-record)
1239 (define-key forms-mode-map "\C-p" 'forms-prev-record)
ac7e3dbe
JV
1240 (define-key forms-mode-map "\C-r" 'forms-search-backward)
1241 (define-key forms-mode-map "\C-s" 'forms-search-forward)
2cc27dd3
RS
1242 (define-key forms-mode-map "\C-x" 'forms-exit)
1243 (define-key forms-mode-map "<" 'forms-first-record)
1244 (define-key forms-mode-map ">" 'forms-last-record)
2cc27dd3
RS
1245 (define-key forms-mode-map "\C-?" 'forms-prev-record)
1246
1247 ;; `forms-mode-ro-map' replaces the local map when in read-only mode.
1248 (setq forms-mode-ro-map (make-keymap))
1249 (suppress-keymap forms-mode-ro-map)
1250 (define-key forms-mode-ro-map "\C-c" forms-mode-map)
1251 (define-key forms-mode-ro-map "\t" 'forms-next-field)
1252 (define-key forms-mode-ro-map "q" 'forms-toggle-read-only)
1253 (define-key forms-mode-ro-map "l" 'forms-jump-record)
1254 (define-key forms-mode-ro-map "n" 'forms-next-record)
1255 (define-key forms-mode-ro-map "p" 'forms-prev-record)
ac7e3dbe
JV
1256 (define-key forms-mode-ro-map "r" 'forms-search-backward)
1257 (define-key forms-mode-ro-map "s" 'forms-search-forward)
2cc27dd3
RS
1258 (define-key forms-mode-ro-map "x" 'forms-exit)
1259 (define-key forms-mode-ro-map "<" 'forms-first-record)
1260 (define-key forms-mode-ro-map ">" 'forms-last-record)
1261 (define-key forms-mode-ro-map "?" 'describe-mode)
1262 (define-key forms-mode-ro-map " " 'forms-next-record)
1263 (forms--mode-commands1 forms-mode-ro-map)
ac7e3dbe 1264 (forms--mode-menu-ro forms-mode-ro-map)
2cc27dd3
RS
1265
1266 ;; This is the normal, local map.
1267 (setq forms-mode-edit-map (make-keymap))
1268 (define-key forms-mode-edit-map "\t" 'forms-next-field)
1269 (define-key forms-mode-edit-map "\C-c" forms-mode-map)
1270 (forms--mode-commands1 forms-mode-edit-map)
ac7e3dbe 1271 (forms--mode-menu-edit forms-mode-edit-map)
2cc27dd3
RS
1272 )
1273
ac7e3dbe
JV
1274(defun forms--mode-menu-ro (map)
1275;;; Menu initialisation
1276; (define-key map [menu-bar] (make-sparse-keymap))
1277 (define-key map [menu-bar forms]
1278 (cons "Forms" (make-sparse-keymap "Forms")))
1279 (define-key map [menu-bar forms menu-forms-exit]
1280 '("Exit" . forms-exit))
1281 (define-key map [menu-bar forms menu-forms-sep1]
1282 '("----"))
1283 (define-key map [menu-bar forms menu-forms-save]
dad40e13 1284 '("Save Data" . forms-save-buffer))
ac7e3dbe 1285 (define-key map [menu-bar forms menu-forms-print]
dad40e13 1286 '("Print Data" . forms-print))
ac7e3dbe 1287 (define-key map [menu-bar forms menu-forms-describe]
dad40e13 1288 '("Describe Mode" . describe-mode))
ac7e3dbe
JV
1289 (define-key map [menu-bar forms menu-forms-toggle-ro]
1290 '("Toggle View/Edit" . forms-toggle-read-only))
1291 (define-key map [menu-bar forms menu-forms-jump-record]
1292 '("Jump" . forms-jump-record))
1293 (define-key map [menu-bar forms menu-forms-search-backward]
dad40e13 1294 '("Search Backward" . forms-search-backward))
ac7e3dbe 1295 (define-key map [menu-bar forms menu-forms-search-forward]
dad40e13 1296 '("Search Forward" . forms-search-forward))
ac7e3dbe
JV
1297 (define-key map [menu-bar forms menu-forms-delete-record]
1298 '("Delete" . forms-delete-record))
1299 (define-key map [menu-bar forms menu-forms-insert-record]
1300 '("Insert" . forms-insert-record))
1301 (define-key map [menu-bar forms menu-forms-sep2]
1302 '("----"))
1303 (define-key map [menu-bar forms menu-forms-last-record]
dad40e13 1304 '("Last Record" . forms-last-record))
ac7e3dbe 1305 (define-key map [menu-bar forms menu-forms-first-record]
dad40e13 1306 '("First Record" . forms-first-record))
ac7e3dbe 1307 (define-key map [menu-bar forms menu-forms-prev-record]
dad40e13 1308 '("Previous Record" . forms-prev-record))
ac7e3dbe 1309 (define-key map [menu-bar forms menu-forms-next-record]
dad40e13 1310 '("Next Record" . forms-next-record))
ac7e3dbe
JV
1311 (define-key map [menu-bar forms menu-forms-sep3]
1312 '("----"))
1313 (define-key map [menu-bar forms menu-forms-prev-field]
dad40e13 1314 '("Previous Field" . forms-prev-field))
ac7e3dbe 1315 (define-key map [menu-bar forms menu-forms-next-field]
dad40e13 1316 '("Next Field" . forms-next-field))
ac7e3dbe
JV
1317 (put 'forms-insert-record 'menu-enable '(not forms-read-only))
1318 (put 'forms-delete-record 'menu-enable '(not forms-read-only))
1319)
1320(defun forms--mode-menu-edit (map)
1321;;; Menu initialisation
1322; (define-key map [menu-bar] (make-sparse-keymap))
1323 (define-key map [menu-bar forms]
1324 (cons "Forms" (make-sparse-keymap "Forms")))
1325 (define-key map [menu-bar forms menu-forms-edit--exit]
1326 '("Exit" . forms-exit))
1327 (define-key map [menu-bar forms menu-forms-edit-sep1]
1328 '("----"))
1329 (define-key map [menu-bar forms menu-forms-edit-save]
dad40e13 1330 '("Save Data" . forms-save-buffer))
ac7e3dbe 1331 (define-key map [menu-bar forms menu-forms-edit-print]
dad40e13 1332 '("Print Data" . forms-print))
ac7e3dbe 1333 (define-key map [menu-bar forms menu-forms-edit-describe]
dad40e13 1334 '("Describe Mode" . describe-mode))
ac7e3dbe
JV
1335 (define-key map [menu-bar forms menu-forms-edit-toggle-ro]
1336 '("Toggle View/Edit" . forms-toggle-read-only))
1337 (define-key map [menu-bar forms menu-forms-edit-jump-record]
1338 '("Jump" . forms-jump-record))
1339 (define-key map [menu-bar forms menu-forms-edit-search-backward]
dad40e13 1340 '("Search Backward" . forms-search-backward))
ac7e3dbe 1341 (define-key map [menu-bar forms menu-forms-edit-search-forward]
dad40e13 1342 '("Search Forward" . forms-search-forward))
ac7e3dbe
JV
1343 (define-key map [menu-bar forms menu-forms-edit-delete-record]
1344 '("Delete" . forms-delete-record))
1345 (define-key map [menu-bar forms menu-forms-edit-insert-record]
1346 '("Insert" . forms-insert-record))
1347 (define-key map [menu-bar forms menu-forms-edit-sep2]
1348 '("----"))
1349 (define-key map [menu-bar forms menu-forms-edit-last-record]
dad40e13 1350 '("Last Record" . forms-last-record))
ac7e3dbe 1351 (define-key map [menu-bar forms menu-forms-edit-first-record]
dad40e13 1352 '("First Record" . forms-first-record))
ac7e3dbe 1353 (define-key map [menu-bar forms menu-forms-edit-prev-record]
dad40e13 1354 '("Previous Record" . forms-prev-record))
ac7e3dbe 1355 (define-key map [menu-bar forms menu-forms-edit-next-record]
dad40e13 1356 '("Next Record" . forms-next-record))
ac7e3dbe
JV
1357 (define-key map [menu-bar forms menu-forms-edit-sep3]
1358 '("----"))
1359 (define-key map [menu-bar forms menu-forms-edit-prev-field]
dad40e13 1360 '("Previous Field" . forms-prev-field))
ac7e3dbe 1361 (define-key map [menu-bar forms menu-forms-edit-next-field]
dad40e13 1362 '("Next Field" . forms-next-field))
ac7e3dbe
JV
1363 (put 'forms-insert-record 'menu-enable '(not forms-read-only))
1364 (put 'forms-delete-record 'menu-enable '(not forms-read-only))
1365)
1366
1367(defun forms--mode-commands1 (map)
2cc27dd3
RS
1368 "Helper routine to define keys."
1369 (define-key map [TAB] 'forms-next-field)
1370 (define-key map [S-tab] 'forms-prev-field)
1371 (define-key map [next] 'forms-next-record)
1372 (define-key map [prior] 'forms-prev-record)
1373 (define-key map [begin] 'forms-first-record)
1374 (define-key map [last] 'forms-last-record)
1375 (define-key map [backtab] 'forms-prev-field)
c1110355 1376 )
b22c9ebf 1377\f
c1110355 1378;;; Changed functions
c1110355
BP
1379
1380(defun forms--change-commands ()
ac2a7a91 1381 "Localize some commands for Forms mode."
fbee9727 1382
c1110355 1383 ;; scroll-down -> forms-prev-record
c1110355 1384 ;; scroll-up -> forms-next-record
ea3d9551
RS
1385 (if forms-forms-scroll
1386 (progn
1387 (substitute-key-definition 'scroll-up 'forms-next-record
1388 (current-local-map)
1389 (current-global-map))
1390 (substitute-key-definition 'scroll-down 'forms-prev-record
1391 (current-local-map)
1392 (current-global-map))))
c1110355
BP
1393 ;;
1394 ;; beginning-of-buffer -> forms-first-record
c1110355 1395 ;; end-of-buffer -> forms-end-record
ea3d9551
RS
1396 (if forms-forms-jump
1397 (progn
1398 (substitute-key-definition 'beginning-of-buffer 'forms-first-record
1399 (current-local-map)
1400 (current-global-map))
1401 (substitute-key-definition 'end-of-buffer 'forms-last-record
1402 (current-local-map)
1403 (current-global-map))))
c1110355 1404 ;;
9c308ed2
RS
1405 ;; Save buffer
1406 (local-set-key "\C-x\C-s" 'forms-save-buffer)
1407 ;;
485efad0 1408 ;; We have our own revert function - use it.
2cc27dd3 1409 (make-local-variable 'revert-buffer-function)
485efad0 1410 (setq revert-buffer-function 'forms--revert-buffer)
2cc27dd3
RS
1411
1412 t)
c1110355
BP
1413
1414(defun forms--help ()
ac2a7a91 1415 "Initial help for Forms mode."
2cc27dd3
RS
1416 (message (substitute-command-keys (concat
1417 "\\[forms-next-record]:next"
1418 " \\[forms-prev-record]:prev"
1419 " \\[forms-first-record]:first"
1420 " \\[forms-last-record]:last"
1421 " \\[describe-mode]:help"))))
c1110355
BP
1422
1423(defun forms--trans (subj arg rep)
ac2a7a91 1424 "Translate in SUBJ all chars ARG into char REP. ARG and REP should
c1110355
BP
1425 be single-char strings."
1426 (let ((i 0)
1427 (x (length subj))
1428 (re (regexp-quote arg))
1429 (k (string-to-char rep)))
1430 (while (setq i (string-match re subj i))
1431 (aset subj i k)
1432 (setq i (1+ i)))))
1433
1434(defun forms--exit (query &optional save)
fbee9727
RS
1435 "Internal exit from forms mode function."
1436
c1110355
BP
1437 (let ((buf (buffer-name forms--file-buffer)))
1438 (forms--checkmod)
1439 (if (and save
1440 (buffer-modified-p forms--file-buffer))
9c308ed2 1441 (forms-save-buffer))
c1110355
BP
1442 (save-excursion
1443 (set-buffer forms--file-buffer)
1444 (delete-auto-save-file-if-necessary)
1445 (kill-buffer (current-buffer)))
1446 (if (get-buffer buf) ; not killed???
1447 (if save
1448 (progn
1449 (beep)
1450 (message "Problem saving buffers?")))
1451 (delete-auto-save-file-if-necessary)
1452 (kill-buffer (current-buffer)))))
1453
1454(defun forms--get-record ()
1455 "Fetch the current record from the file buffer."
fbee9727
RS
1456
1457 ;; This function is executed in the context of the `forms--file-buffer'.
1458
c1110355
BP
1459 (or (bolp)
1460 (beginning-of-line nil))
1461 (let ((here (point)))
1462 (prog2
1463 (end-of-line)
1464 (buffer-substring here (point))
1465 (goto-char here))))
1466
1467(defun forms--show-record (the-record)
ac2a7a91 1468 "Format THE-RECORD and display it in the current buffer."
c1110355 1469
fbee9727 1470 ;; Split the-record.
c1110355
BP
1471 (let (the-result
1472 (start-pos 0)
1473 found-pos
1474 (field-sep-length (length forms-field-sep)))
1475 (if forms-multi-line
1476 (forms--trans the-record forms-multi-line "\n"))
fbee9727 1477 ;; Add an extra separator (makes splitting easy).
c1110355
BP
1478 (setq the-record (concat the-record forms-field-sep))
1479 (while (setq found-pos (string-match forms-field-sep the-record start-pos))
1480 (let ((ent (substring the-record start-pos found-pos)))
1481 (setq the-result
1482 (append the-result (list ent)))
1483 (setq start-pos (+ field-sep-length found-pos))))
1484 (setq forms--the-record-list the-result))
1485
1486 (setq buffer-read-only nil)
fbee9727
RS
1487 (if forms-use-text-properties
1488 (let ((inhibit-read-only t))
fbee9727 1489 (set-text-properties (point-min) (point-max) nil)))
c1110355
BP
1490 (erase-buffer)
1491
fbee9727 1492 ;; Verify the number of fields, extend forms--the-record-list if needed.
c1110355
BP
1493 (if (= (length forms--the-record-list) forms-number-of-fields)
1494 nil
4fd3a710
RS
1495 (if (null forms-check-number-of-fields)
1496 nil
1497 (beep)
1498 (message "Warning: this record has %d fields instead of %d"
1499 (length forms--the-record-list) forms-number-of-fields))
c1110355
BP
1500 (if (< (length forms--the-record-list) forms-number-of-fields)
1501 (setq forms--the-record-list
1502 (append forms--the-record-list
1503 (make-list
1504 (- forms-number-of-fields
1505 (length forms--the-record-list))
1506 "")))))
1507
fbee9727 1508 ;; Call the formatter function.
01a45313 1509 (setq forms-fields (append (list nil) forms--the-record-list nil))
c1110355
BP
1510 (funcall forms--format forms--the-record-list)
1511
fbee9727 1512 ;; Prepare.
c1110355
BP
1513 (goto-char (point-min))
1514 (set-buffer-modified-p nil)
1515 (setq buffer-read-only forms-read-only)
1516 (setq mode-line-process
1517 (concat " " forms--current-record "/" forms--total-records)))
1518
1519(defun forms--parse-form ()
1520 "Parse contents of form into list of strings."
1521 ;; The contents of the form are parsed, and a new list of strings
1522 ;; is constructed.
1523 ;; A vector with the strings from the original record is
ac2a7a91 1524 ;; constructed, which is updated with the new contents. Therefore
c1110355
BP
1525 ;; fields which were not in the form are not modified.
1526 ;; Finally, the vector is transformed into a list for further processing.
1527
fbee9727 1528 (let (forms--recordv)
c1110355 1529
fbee9727
RS
1530 ;; Build the vector.
1531 (setq forms--recordv (vconcat forms--the-record-list))
c1110355 1532
fbee9727 1533 ;; Parse the form and update the vector.
01a45313
RS
1534 (let ((forms--dynamic-text forms--dynamic-text))
1535 (funcall forms--parser))
c1110355 1536
2cc27dd3 1537 (if forms-modified-record-filter
01a45313
RS
1538 ;; As a service to the user, we add a zeroth element so she
1539 ;; can use the same indices as in the forms definition.
fbee9727 1540 (let ((the-fields (vconcat [nil] forms--recordv)))
2cc27dd3 1541 (setq the-fields (funcall forms-modified-record-filter the-fields))
01a45313
RS
1542 (cdr (append the-fields nil)))
1543
fbee9727
RS
1544 ;; Transform to a list and return.
1545 (append forms--recordv nil))))
c1110355
BP
1546
1547(defun forms--update ()
ac2a7a91 1548 "Update current record with contents of form.
fbee9727 1549As a side effect: sets `forms--the-record-list'."
ac2a7a91 1550
c1110355
BP
1551 (if forms-read-only
1552 (progn
1553 (message "Read-only buffer!")
1554 (beep))
1555
1556 (let (the-record)
fbee9727 1557 ;; Build new record.
c1110355
BP
1558 (setq forms--the-record-list (forms--parse-form))
1559 (setq the-record
1560 (mapconcat 'identity forms--the-record-list forms-field-sep))
1561
9c308ed2
RS
1562 (if (string-match (regexp-quote forms-field-sep)
1563 (mapconcat 'identity forms--the-record-list ""))
1564 (error "Field separator occurs in record - update refused!"))
1565
fbee9727 1566 ;; Handle multi-line fields, if allowed.
c1110355
BP
1567 (if forms-multi-line
1568 (forms--trans the-record "\n" forms-multi-line))
1569
fbee9727 1570 ;; A final sanity check before updating.
c1110355
BP
1571 (if (string-match "\n" the-record)
1572 (progn
1573 (message "Multi-line fields in this record - update refused!")
1574 (beep))
1575
1576 (save-excursion
1577 (set-buffer forms--file-buffer)
1f111018
RS
1578 ;; Use delete-region instead of kill-region, to avoid
1579 ;; adding junk to the kill-ring.
11c05cd6
RS
1580 (delete-region (save-excursion (beginning-of-line) (point))
1581 (save-excursion (end-of-line) (point)))
c1110355
BP
1582 (insert the-record)
1583 (beginning-of-line))))))
1584
1585(defun forms--checkmod ()
1586 "Check if this form has been modified, and call forms--update if so."
1587 (if (buffer-modified-p nil)
1588 (let ((here (point)))
1589 (forms--update)
1590 (set-buffer-modified-p nil)
1591 (goto-char here))))
b22c9ebf 1592\f
c1110355 1593;;; Start and exit
ac2a7a91
RS
1594
1595;;;###autoload
c1110355 1596(defun forms-find-file (fn)
ac2a7a91 1597 "Visit a file in Forms mode."
c1110355 1598 (interactive "fForms file: ")
485efad0
RS
1599 (let ((enable-local-eval t)
1600 (enable-local-variables t))
1601 (find-file-read-only fn)
1602 (or forms--mode-setup (forms-mode t))))
c1110355 1603
ac2a7a91 1604;;;###autoload
c1110355 1605(defun forms-find-file-other-window (fn)
ac2a7a91 1606 "Visit a file in Forms mode in other window."
c1110355 1607 (interactive "fFbrowse file in other window: ")
485efad0
RS
1608 (let ((enable-local-eval t)
1609 (enable-local-variables t))
1610 (find-file-other-window fn)
1611 (or forms--mode-setup (forms-mode t))))
c1110355
BP
1612
1613(defun forms-exit (query)
ac2a7a91 1614 "Normal exit from Forms mode. Modified buffers are saved."
c1110355
BP
1615 (interactive "P")
1616 (forms--exit query t))
1617
1618(defun forms-exit-no-save (query)
ac2a7a91 1619 "Exit from Forms mode without saving buffers."
c1110355
BP
1620 (interactive "P")
1621 (forms--exit query nil))
b22c9ebf 1622\f
c1110355
BP
1623;;; Navigating commands
1624
1625(defun forms-next-record (arg)
1626 "Advance to the ARGth following record."
1627 (interactive "P")
1628 (forms-jump-record (+ forms--current-record (prefix-numeric-value arg)) t))
1629
1630(defun forms-prev-record (arg)
1631 "Advance to the ARGth previous record."
1632 (interactive "P")
1633 (forms-jump-record (- forms--current-record (prefix-numeric-value arg)) t))
1634
1635(defun forms-jump-record (arg &optional relative)
1636 "Jump to a random record."
1637 (interactive "NRecord number: ")
1638
fbee9727 1639 ;; Verify that the record number is within range.
c1110355
BP
1640 (if (or (> arg forms--total-records)
1641 (<= arg 0))
1642 (progn
1643 (beep)
fbee9727 1644 ;; Don't give the message if just paging.
c1110355
BP
1645 (if (not relative)
1646 (message "Record number %d out of range 1..%d"
1647 arg forms--total-records))
1648 )
1649
fbee9727 1650 ;; Flush.
c1110355
BP
1651 (forms--checkmod)
1652
fbee9727 1653 ;; Calculate displacement.
c1110355
BP
1654 (let ((disp (- arg forms--current-record))
1655 (cur forms--current-record))
1656
fbee9727 1657 ;; `forms--show-record' needs it now.
c1110355
BP
1658 (setq forms--current-record arg)
1659
fbee9727 1660 ;; Get the record and show it.
c1110355
BP
1661 (forms--show-record
1662 (save-excursion
1663 (set-buffer forms--file-buffer)
1664 (beginning-of-line)
1665
fbee9727 1666 ;; Move, and adjust the amount if needed (shouldn't happen).
c1110355
BP
1667 (if relative
1668 (if (zerop disp)
1669 nil
1670 (setq cur (+ cur disp (- (forward-line disp)))))
1671 (setq cur (+ cur disp (- (goto-line arg)))))
1672
1673 (forms--get-record)))
1674
fbee9727 1675 ;; This shouldn't happen.
c1110355
BP
1676 (if (/= forms--current-record cur)
1677 (progn
1678 (setq forms--current-record cur)
1679 (beep)
2cc27dd3 1680 (message "Stuck at record %d" cur))))))
c1110355
BP
1681
1682(defun forms-first-record ()
1683 "Jump to first record."
1684 (interactive)
1685 (forms-jump-record 1))
1686
1687(defun forms-last-record ()
ac2a7a91
RS
1688 "Jump to last record.
1689As a side effect: re-calculates the number of records in the data file."
c1110355
BP
1690 (interactive)
1691 (let
1692 ((numrec
1693 (save-excursion
1694 (set-buffer forms--file-buffer)
1695 (count-lines (point-min) (point-max)))))
1696 (if (= numrec forms--total-records)
1697 nil
1698 (beep)
1699 (setq forms--total-records numrec)
2cc27dd3 1700 (message "Warning: number of records changed to %d" forms--total-records)))
c1110355 1701 (forms-jump-record forms--total-records))
b22c9ebf 1702\f
c1110355 1703;;; Other commands
ac2a7a91 1704
2cc27dd3
RS
1705(defun forms-toggle-read-only (arg)
1706 "Toggles read-only mode of a forms mode buffer.
1707With an argument, enables read-only mode if the argument is positive.
1708Otherwise enables edit mode if the visited file is writeable."
c1110355 1709
2cc27dd3
RS
1710 (interactive "P")
1711
1712 (if (if arg
1713 ;; Negative arg means switch it off.
1714 (<= (prefix-numeric-value arg) 0)
1715 ;; No arg means toggle.
1716 forms-read-only)
1717
1718 ;; Enable edit mode, if possible.
1719 (let ((ro forms-read-only))
1720 (if (save-excursion
1721 (set-buffer forms--file-buffer)
1722 buffer-read-only)
1723 (progn
1724 (setq forms-read-only t)
1725 (message "No write access to \"%s\"" forms-file)
1726 (beep))
1727 (setq forms-read-only nil))
1728 (if (equal ro forms-read-only)
1729 nil
1730 (forms-mode)))
1731
1732 ;; Enable view mode.
1733 (if forms-read-only
c1110355 1734 nil
2cc27dd3
RS
1735 (forms--checkmod) ; sync
1736 (setq forms-read-only t)
c1110355
BP
1737 (forms-mode))))
1738
1739;; Sample:
01a45313 1740;; (defun my-new-record-filter (the-fields)
c1110355
BP
1741;; ;; numbers are relative to 1
1742;; (aset the-fields 4 (current-time-string))
1743;; (aset the-fields 6 (user-login-name))
1744;; the-list)
01a45313 1745;; (setq forms-new-record-filter 'my-new-record-filter)
c1110355
BP
1746
1747(defun forms-insert-record (arg)
ac2a7a91
RS
1748 "Create a new record before the current one.
1749With ARG: store the record after the current one.
2cc27dd3 1750If `forms-new-record-filter' contains the name of a function,
ac2a7a91 1751it is called to fill (some of) the fields with default values."
c1110355
BP
1752
1753 (interactive "P")
1754
2cc27dd3
RS
1755 (if forms-read-only
1756 (error ""))
1757
c1110355
BP
1758 (let ((ln (if arg (1+ forms--current-record) forms--current-record))
1759 the-list the-record)
1760
1761 (forms--checkmod)
2cc27dd3 1762 (if forms-new-record-filter
c1110355
BP
1763 ;; As a service to the user, we add a zeroth element so she
1764 ;; can use the same indices as in the forms definition.
1765 (let ((the-fields (make-vector (1+ forms-number-of-fields) "")))
2cc27dd3 1766 (setq the-fields (funcall forms-new-record-filter the-fields))
c1110355
BP
1767 (setq the-list (cdr (append the-fields nil))))
1768 (setq the-list (make-list forms-number-of-fields "")))
1769
1770 (setq the-record
1771 (mapconcat
1772 'identity
1773 the-list
1774 forms-field-sep))
1775
1776 (save-excursion
1777 (set-buffer forms--file-buffer)
1778 (goto-line ln)
1779 (open-line 1)
1780 (insert the-record)
1781 (beginning-of-line))
1782
1783 (setq forms--current-record ln))
1784
1785 (setq forms--total-records (1+ forms--total-records))
1786 (forms-jump-record forms--current-record))
1787
1788(defun forms-delete-record (arg)
ac2a7a91 1789 "Deletes a record. With a prefix argument: don't ask."
c1110355 1790 (interactive "P")
2cc27dd3
RS
1791
1792 (if forms-read-only
1793 (error ""))
1794
c1110355
BP
1795 (forms--checkmod)
1796 (if (or arg
1797 (y-or-n-p "Really delete this record? "))
1798 (let ((ln forms--current-record))
1799 (save-excursion
1800 (set-buffer forms--file-buffer)
1801 (goto-line ln)
1f111018
RS
1802 ;; Use delete-region instead of kill-region, to avoid
1803 ;; adding junk to the kill-ring.
eb4ca295
RS
1804 (delete-region (progn (beginning-of-line) (point))
1805 (progn (beginning-of-line 2) (point))))
c1110355
BP
1806 (setq forms--total-records (1- forms--total-records))
1807 (if (> forms--current-record forms--total-records)
1808 (setq forms--current-record forms--total-records))
1809 (forms-jump-record forms--current-record)))
1810 (message ""))
1811
ac7e3dbe
JV
1812(defun forms-search-forward (regexp)
1813 "Search forward for record containing REGEXP."
c1110355 1814 (interactive
ac7e3dbe 1815 (list (read-string (concat "Search forward for"
c1110355
BP
1816 (if forms--search-regexp
1817 (concat " ("
1818 forms--search-regexp
1819 ")"))
1820 ": "))))
1821 (if (equal "" regexp)
1822 (setq regexp forms--search-regexp))
1823 (forms--checkmod)
1824
1825 (let (the-line the-record here
1826 (fld-sep forms-field-sep))
1827 (if (save-excursion
1828 (set-buffer forms--file-buffer)
1829 (setq here (point))
1830 (end-of-line)
1831 (if (null (re-search-forward regexp nil t))
1832 (progn
1833 (goto-char here)
1834 (message (concat "\"" regexp "\" not found."))
1835 nil)
1836 (setq the-record (forms--get-record))
1837 (setq the-line (1+ (count-lines (point-min) (point))))))
1838 (progn
1839 (setq forms--current-record the-line)
1840 (forms--show-record the-record)
1841 (re-search-forward regexp nil t))))
1842 (setq forms--search-regexp regexp))
1843
ac7e3dbe
JV
1844(defun forms-search-backward (regexp)
1845 "Search backward for record containing REGEXP."
1846 (interactive
1847 (list (read-string (concat "Search backward for"
1848 (if forms--search-regexp
1849 (concat " ("
1850 forms--search-regexp
1851 ")"))
1852 ": "))))
1853 (if (equal "" regexp)
1854 (setq regexp forms--search-regexp))
1855 (forms--checkmod)
1856
1857 (let (the-line the-record here
1858 (fld-sep forms-field-sep))
1859 (if (save-excursion
1860 (set-buffer forms--file-buffer)
1861 (setq here (point))
1862 (beginning-of-line)
1863 (if (null (re-search-backward regexp nil t))
1864 (progn
1865 (goto-char here)
1866 (message (concat "\"" regexp "\" not found."))
1867 nil)
1868 (setq the-record (forms--get-record))
1869 (setq the-line (1+ (count-lines (point-min) (point))))))
1870 (progn
1871 (setq forms--current-record the-line)
1872 (forms--show-record the-record)
1873 (re-search-forward regexp nil t))))
1874 (setq forms--search-regexp regexp))
1875
9c308ed2
RS
1876(defun forms-save-buffer (&optional args)
1877 "Forms mode replacement for save-buffer.
1878It saves the data buffer instead of the forms buffer.
1879Calls `forms-write-file-filter' before writing out the data."
1880 (interactive "p")
485efad0 1881 (forms--checkmod)
9c308ed2
RS
1882 (let ((read-file-filter forms-read-file-filter))
1883 (save-excursion
1884 (set-buffer forms--file-buffer)
1885 (let ((inhibit-read-only t))
1886 (save-buffer args)
1887 (if read-file-filter
1888 (run-hooks 'read-file-filter))
1889 (set-buffer-modified-p nil))))
485efad0
RS
1890 t)
1891
1892(defun forms--revert-buffer (&optional arg noconfirm)
c1110355
BP
1893 "Reverts current form to un-modified."
1894 (interactive "P")
1895 (if (or noconfirm
1896 (yes-or-no-p "Revert form to unmodified? "))
1897 (progn
1898 (set-buffer-modified-p nil)
1899 (forms-jump-record forms--current-record))))
1900
1901(defun forms-next-field (arg)
1902 "Jump to ARG-th next field."
1903 (interactive "p")
1904
1905 (let ((i 0)
1906 (here (point))
1907 there
2996d9f8
RS
1908 (cnt 0)
1909 (inhibit-point-motion-hooks t))
c1110355
BP
1910
1911 (if (zerop arg)
1912 (setq cnt 1)
1913 (setq cnt (+ cnt arg)))
1914
1915 (if (catch 'done
fbee9727 1916 (while (< i (length forms--markers))
c1110355
BP
1917 (if (or (null (setq there (aref forms--markers i)))
1918 (<= there here))
1919 nil
1920 (if (<= (setq cnt (1- cnt)) 0)
1921 (progn
1922 (goto-char there)
1923 (throw 'done t))))
1924 (setq i (1+ i))))
1925 nil
1926 (goto-char (aref forms--markers 0)))))
01a45313 1927
2cc27dd3
RS
1928(defun forms-prev-field (arg)
1929 "Jump to ARG-th previous field."
1930 (interactive "p")
1931
1932 (let ((i (length forms--markers))
1933 (here (point))
1934 there
2996d9f8
RS
1935 (cnt 0)
1936 (inhibit-point-motion-hooks t))
2cc27dd3
RS
1937
1938 (if (zerop arg)
1939 (setq cnt 1)
1940 (setq cnt (+ cnt arg)))
1941
1942 (if (catch 'done
1943 (while (> i 0)
1944 (setq i ( 1- i))
1945 (if (or (null (setq there (aref forms--markers i)))
1946 (>= there here))
1947 nil
1948 (if (<= (setq cnt (1- cnt)) 0)
1949 (progn
1950 (goto-char there)
1951 (throw 'done t))))))
1952 nil
1953 (goto-char (aref forms--markers (1- (length forms--markers)))))))
ac7e3dbe
JV
1954
1955(defun forms-print ()
1956 "Send the records to the printer with 'print-buffer', one record per page."
1957 (interactive)
1958 (let ((inhibit-read-only t)
1959 (save-record forms--current-record)
1960 (nb-record 1)
1961 (record nil))
1962 (while (<= nb-record forms--total-records)
1963 (forms-jump-record nb-record)
1964 (setq record (buffer-string))
1965 (save-excursion
1966 (set-buffer (get-buffer-create "*forms-print*"))
1967 (goto-char (buffer-end 1))
1968 (insert record)
1969 (setq buffer-read-only nil)
1970 (if (< nb-record forms--total-records)
1971 (insert "\n\f\n")))
1972 (setq nb-record (1+ nb-record)))
1973 (save-excursion
1974 (set-buffer "*forms-print*")
1975 (print-buffer)
1976 (set-buffer-modified-p nil)
1977 (kill-buffer (current-buffer)))
1978 (forms-jump-record save-record)))
1979
01a45313
RS
1980;;;
1981;;; Special service
1982;;;
1983(defun forms-enumerate (the-fields)
ac2a7a91
RS
1984 "Take a quoted list of symbols, and set their values to sequential numbers.
1985The first symbol gets number 1, the second 2 and so on.
1986It returns the higest number.
01a45313
RS
1987
1988Usage: (setq forms-number-of-fields
1989 (forms-enumerate
1990 '(field1 field2 field2 ...)))"
1991
1992 (let ((the-index 0))
1993 (while the-fields
1994 (setq the-index (1+ the-index))
1995 (let ((el (car-safe the-fields)))
1996 (setq the-fields (cdr-safe the-fields))
1997 (set el the-index)))
1998 the-index))
b22c9ebf 1999\f
01a45313 2000;;; Debugging
ac2a7a91 2001
01a45313
RS
2002(defvar forms--debug nil
2003 "*Enables forms-mode debugging if not nil.")
2004
2005(defun forms--debug (&rest args)
ac2a7a91 2006 "Internal debugging routine."
01a45313
RS
2007 (if forms--debug
2008 (let ((ret nil))
2009 (while args
2010 (let ((el (car-safe args)))
2011 (setq args (cdr-safe args))
2012 (if (stringp el)
2013 (setq ret (concat ret el))
2014 (setq ret (concat ret (prin1-to-string el) " = "))
2015 (if (boundp el)
2016 (let ((vel (eval el)))
2017 (setq ret (concat ret (prin1-to-string vel) "\n")))
2018 (setq ret (concat ret "<unbound>" "\n")))
2019 (if (fboundp el)
2020 (setq ret (concat ret (prin1-to-string (symbol-function el))
2021 "\n"))))))
2022 (save-excursion
2023 (set-buffer (get-buffer-create "*forms-mode debug*"))
fbee9727
RS
2024 (if (zerop (buffer-size))
2025 (emacs-lisp-mode))
01a45313
RS
2026 (goto-char (point-max))
2027 (insert ret)))))
2028
b22c9ebf 2029;;; forms.el ends here.