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