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