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