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