comparison lisp/utils/forms.el @ 70:131b0175ea99 r20-0b30

Import from CVS: tag r20-0b30
author cvs
date Mon, 13 Aug 2007 09:02:59 +0200
parents 56c54cf7c5b6
children b9518feda344
comparison
equal deleted inserted replaced
69:804d1389bcd6 70:131b0175ea99
1 ;;; forms.el --- Forms mode: edit a file as a form to fill in 1 ;;; forms.el --- Forms mode: edit a file as a form to fill in.
2 2
3 ;; Copyright (C) 1991, 1994, 1995, 1996 Free Software Foundation, Inc. 3 ;;; Copyright (C) 1991, 1993 Free Software Foundation, Inc.
4 4
5 ;; Author: Johan Vromans <jvromans@squirrel.nl> 5 ;; Author: Johan Vromans <jv@nl.net>
6 ;; Version: Revision: 2.10 6 ;; Version: Revision: 2.10
7 ;; Keywords: extensions 7 ;; Keywords: extensions
8 ;; hacked on by jwz for XEmacs 8 ;; hacked on by jwz for XEmacs
9 9
10 ;; This file is part of XEmacs. 10 ;; This file is part of XEmacs.
18 ;; WITHOUT ANY WARRANTY; without even the implied warranty of 18 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 ;; General Public License for more details. 20 ;; General Public License for more details.
21 21
22 ;; You should have received a copy of the GNU General Public License 22 ;; You should have received a copy of the GNU General Public License
23 ;; along with XEmacs; see the file COPYING. If not, write to the Free 23 ;; along with XEmacs; see the file COPYING. If not, write to the
24 ;; Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 24 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
25 ;; 02111-1307, USA. 25 ;; Boston, MA 02111-1307, USA.
26 26
27 ;;; Synched up with: FSF 19.34. 27 ;;; Synched up with: FSF 19.28.
28 28
29 ;;; Commentary: 29 ;;; Commentary:
30 30
31 ;; Visit a file using a form. 31 ;;; Visit a file using a form.
32 ;; 32 ;;;
33 ;; === Naming conventions 33 ;;; === Naming conventions
34 ;; 34 ;;;
35 ;; The names of all variables and functions start with 'forms-'. 35 ;;; The names of all variables and functions start with 'forms-'.
36 ;; Names which start with 'forms--' are intended for internal use, and 36 ;;; Names which start with 'forms--' are intended for internal use, and
37 ;; should *NOT* be used from the outside. 37 ;;; should *NOT* be used from the outside.
38 ;; 38 ;;;
39 ;; All variables are buffer-local, to enable multiple forms visits 39 ;;; All variables are buffer-local, to enable multiple forms visits
40 ;; simultaneously. 40 ;;; simultaneously.
41 ;; Variable `forms--mode-setup' is local to *ALL* buffers, for it 41 ;;; Variable `forms--mode-setup' is local to *ALL* buffers, for it
42 ;; controls if forms-mode has been enabled in a buffer. 42 ;;; controls if forms-mode has been enabled in a buffer.
43 ;; 43 ;;;
44 ;; === How it works === 44 ;;; === How it works ===
45 ;; 45 ;;;
46 ;; Forms mode means visiting a data file which is supposed to consist 46 ;;; Forms mode means visiting a data file which is supposed to consist
47 ;; of records each containing a number of fields. The records are 47 ;;; of records each containing a number of fields. The records are
48 ;; separated by a newline, the fields are separated by a user-defined 48 ;;; separated by a newline, the fields are separated by a user-defined
49 ;; field separator (default: TAB). 49 ;;; field separater (default: TAB).
50 ;; When shown, a record is transferred to an Emacs buffer and 50 ;;; When shown, a record is transferred to an Emacs buffer and
51 ;; presented using a user-defined form. One record is shown at a 51 ;;; presented using a user-defined form. One record is shown at a
52 ;; time. 52 ;;; time.
53 ;; 53 ;;;
54 ;; Forms mode is a composite mode. It involves two files, and two 54 ;;; Forms mode is a composite mode. It involves two files, and two
55 ;; buffers. 55 ;;; buffers.
56 ;; The first file, called the control file, defines the name of the 56 ;;; The first file, called the control file, defines the name of the
57 ;; data file and the forms format. This file buffer will be used to 57 ;;; data file and the forms format. This file buffer will be used to
58 ;; present the forms. 58 ;;; present the forms.
59 ;; The second file holds the actual data. The buffer of this file 59 ;;; The second file holds the actual data. The buffer of this file
60 ;; will be buried, for it is never accessed directly. 60 ;;; will be buried, for it is never accessed directly.
61 ;; 61 ;;;
62 ;; Forms mode is invoked using M-x forms-find-file control-file . 62 ;;; Forms mode is invoked using M-x forms-find-file control-file .
63 ;; Alternatively `forms-find-file-other-window' can be used. 63 ;;; Alternativily `forms-find-file-other-window' can be used.
64 ;; 64 ;;;
65 ;; You may also visit the control file, and switch to forms mode by hand 65 ;;; You may also visit the control file, and switch to forms mode by hand
66 ;; with M-x forms-mode . 66 ;;; with M-x forms-mode .
67 ;; 67 ;;;
68 ;; Automatic mode switching is supported if you specify 68 ;;; Automatic mode switching is supported if you specify
69 ;; "-*- forms -*-" in the first line of the control file. 69 ;;; "-*- forms -*-" in the first line of the control file.
70 ;; 70 ;;;
71 ;; The control file is visited, evaluated using `eval-current-buffer', 71 ;;; The control file is visited, evaluated using `eval-current-buffer',
72 ;; and should set at least the following variables: 72 ;;; and should set at least the following variables:
73 ;; 73 ;;;
74 ;; forms-file [string] 74 ;;; forms-file [string]
75 ;; The name of the data file. 75 ;;; The name of the data file.
76 ;; 76 ;;;
77 ;; forms-number-of-fields [integer] 77 ;;; forms-number-of-fields [integer]
78 ;; The number of fields in each record. 78 ;;; The number of fields in each record.
79 ;; 79 ;;;
80 ;; forms-format-list [list] 80 ;;; forms-format-list [list]
81 ;; Formatting instructions. 81 ;;; Formatting instructions.
82 ;; 82 ;;;
83 ;; `forms-format-list' should be a list, each element containing 83 ;;; `forms-format-list' should be a list, each element containing
84 ;; 84 ;;;
85 ;; - a string, e.g. "hello". The string is inserted in the forms 85 ;;; - a string, e.g. "hello". The string is inserted in the forms
86 ;; "as is". 86 ;;; "as is".
87 ;; 87 ;;;
88 ;; - an integer, denoting a field number. 88 ;;; - an integer, denoting a field number.
89 ;; The contents of this field are inserted at this point. 89 ;;; The contents of this field are inserted at this point.
90 ;; Fields are numbered starting with number one. 90 ;;; Fields are numbered starting with number one.
91 ;; 91 ;;;
92 ;; - a function call, e.g. (insert "text"). 92 ;;; - a function call, e.g. (insert "text").
93 ;; This function call is dynamically evaluated and should return a 93 ;;; This function call is dynamically evaluated and should return a
94 ;; string. It should *NOT* have side-effects on the forms being 94 ;;; string. It should *NOT* have side-effects on the forms being
95 ;; constructed. The current fields are available to the function 95 ;;; constructed. The current fields are available to the function
96 ;; in the variable `forms-fields', they should *NOT* be modified. 96 ;;; in the variable `forms-fields', they should *NOT* be modified.
97 ;; 97 ;;;
98 ;; - a lisp symbol, that must evaluate to one of the above. 98 ;;; - a lisp symbol, that must evaluate to one of the above.
99 ;; 99 ;;;
100 ;; Optional variables which may be set in the control file: 100 ;;; Optional variables which may be set in the control file:
101 ;; 101 ;;;
102 ;; forms-field-sep [string, default TAB] 102 ;;; forms-field-sep [string, default TAB]
103 ;; The field separator used to separate the 103 ;;; The field separator used to separate the
104 ;; fields in the data file. It may be a string. 104 ;;; fields in the data file. It may be a string.
105 ;; 105 ;;;
106 ;; forms-read-only [bool, default nil] 106 ;;; forms-read-only [bool, default nil]
107 ;; Non-nil means that the data file is visited 107 ;;; Non-nil means that the data file is visited
108 ;; read-only (view mode) as opposed to edit mode. 108 ;;; read-only (view mode) as opposed to edit mode.
109 ;; If no write access to the data file is 109 ;;; If no write access to the data file is
110 ;; possible, view mode is enforced. 110 ;;; possible, view mode is enforced.
111 ;; 111 ;;;
112 ;; forms-check-number-of-fields [bool, default t] 112 ;;; forms-multi-line [string, default "^K"]
113 ;; If non-nil, a warning will be issued whenever 113 ;;; If non-null the records of the data file may
114 ;; a record is found that does not have the number 114 ;;; contain fields that can span multiple lines in
115 ;; of fields specified by `forms-number-of-fields'. 115 ;;; the form.
116 ;; 116 ;;; This variable denotes the separator character
117 ;; forms-multi-line [string, default "^K"] 117 ;;; to be used for this purpose. Upon display, all
118 ;; If non-null the records of the data file may 118 ;;; occurrencies of this character are translated
119 ;; contain fields that can span multiple lines in 119 ;;; to newlines. Upon storage they are translated
120 ;; the form. 120 ;;; back to the separator character.
121 ;; This variable denotes the separator character 121 ;;;
122 ;; to be used for this purpose. Upon display, all 122 ;;; forms-forms-scroll [bool, default nil]
123 ;; occurrences of this character are translated 123 ;;; Non-nil means: rebind locally the commands that
124 ;; to newlines. Upon storage they are translated 124 ;;; perform `scroll-up' or `scroll-down' to use
125 ;; back to the separator character. 125 ;;; `forms-next-field' resp. `forms-prev-field'.
126 ;; 126 ;;;
127 ;; forms-forms-scroll [bool, default nil] 127 ;;; forms-forms-jump [bool, default nil]
128 ;; Non-nil means: rebind locally the commands that 128 ;;; Non-nil means: rebind locally the commands that
129 ;; perform `scroll-up' or `scroll-down' to use 129 ;;; perform `beginning-of-buffer' or `end-of-buffer'
130 ;; `forms-next-field' resp. `forms-prev-field'. 130 ;;; to perform `forms-first-field' resp. `forms-last-field'.
131 ;; 131 ;;;
132 ;; forms-forms-jump [bool, default nil] 132 ;;; forms-read-file-filter [symbol, default nil]
133 ;; Non-nil means: rebind locally the commands that 133 ;;; If not nil: this should be the name of a
134 ;; perform `beginning-of-buffer' or `end-of-buffer' 134 ;;; function that is called after the forms data file
135 ;; to perform `forms-first-field' and `forms-last-field'. 135 ;;; has been read. It can be used to transform
136 ;; 136 ;;; the contents of the file into a format more suitable
137 ;; forms-insert-after [bool, default nil] 137 ;;; for forms-mode processing.
138 ;; Non-nil means: inserts of new records go after 138 ;;;
139 ;; current record, also initial position is at last 139 ;;; forms-write-file-filter [symbol, default nil]
140 ;; record. 140 ;;; If not nil: this should be the name of a
141 ;; 141 ;;; function that is called before the forms data file
142 ;; forms-read-file-filter [symbol, default nil] 142 ;;; is written (saved) to disk. It can be used to undo
143 ;; If not nil: this should be the name of a 143 ;;; the effects of `forms-read-file-filter', if any.
144 ;; function that is called after the forms data file 144 ;;;
145 ;; has been read. It can be used to transform 145 ;;; forms-new-record-filter [symbol, default nil]
146 ;; the contents of the file into a format more suitable 146 ;;; If not nil: this should be the name of a
147 ;; for forms-mode processing. 147 ;;; function that is called when a new
148 ;; 148 ;;; record is created. It can be used to fill in
149 ;; forms-write-file-filter [symbol, default nil] 149 ;;; the new record with default fields, for example.
150 ;; If not nil: this should be the name of a 150 ;;;
151 ;; function that is called before the forms data file 151 ;;; forms-modified-record-filter [symbol, default nil]
152 ;; is written (saved) to disk. It can be used to undo 152 ;;; If not nil: this should be the name of a
153 ;; the effects of `forms-read-file-filter', if any. 153 ;;; function that is called when a record has
154 ;; 154 ;;; been modified. It is called after the fields
155 ;; forms-new-record-filter [symbol, default nil] 155 ;;; are parsed. It can be used to register
156 ;; If not nil: this should be the name of a 156 ;;; modification dates, for example.
157 ;; function that is called when a new 157 ;;;
158 ;; record is created. It can be used to fill in 158 ;;; forms-use-extents [bool, see text for default]
159 ;; the new record with default fields, for example. 159 ;;; forms-use-text-properties [bool, see text for default]
160 ;; 160 ;;; These variables control if forms mode should use
161 ;; forms-modified-record-filter [symbol, default nil] 161 ;;; text properties or extents to protect the form text
162 ;; If not nil: this should be the name of a 162 ;;; from being modified (using text-property `read-only').
163 ;; function that is called when a record has 163 ;;; Also, the read-write fields are shown using a
164 ;; been modified. It is called after the fields 164 ;;; distinct face, if possible.
165 ;; are parsed. It can be used to register 165 ;;; One of these variables defaults to t if running
166 ;; modification dates, for example. 166 ;;; FSF or Lucid Emacs 19.
167 ;; 167 ;;;
168 ;; forms-use-extents [bool, see text for default] 168 ;;; forms-ro-face [symbol, default 'default]
169 ;; forms-use-text-properties [bool, see text for default] 169 ;;; This is the face that is used to show
170 ;; These variables control if forms mode should use 170 ;;; read-only text on the screen.If used, this
171 ;; text properties or extents to protect the form text 171 ;;; variable should be set to a symbol that is a
172 ;; from being modified (using text-property `read-only'). 172 ;;; valid face.
173 ;; Also, the read-write fields are shown using a 173 ;;; E.g.
174 ;; distinct face, if possible. 174 ;;; (make-face 'my-face)
175 ;; As of emacs 19.29, the `intangible' text property 175 ;;; (setq forms-ro-face 'my-face)
176 ;; is used to prevent moving into read-only fields. 176 ;;;
177 ;; This variable defaults to t if running Emacs 19 177 ;;; forms-rw-face [symbol, default 'region]
178 ;; with text properties. 178 ;;; This is the face that is used to show
179 ;; The default face to show read-write fields is 179 ;;; read-write text on the screen.
180 ;; copied from face `region'. 180 ;;;
181 ;; 181 ;;; After evaluating the control file, its buffer is cleared and used
182 ;; forms-ro-face [symbol, default 'default] 182 ;;; for further processing.
183 ;; This is the face that is used to show 183 ;;; The data file (as designated by `forms-file') is visited in a buffer
184 ;; read-only text on the screen.If used, this 184 ;;; `forms--file-buffer' which will not normally be shown.
185 ;; variable should be set to a symbol that is a 185 ;;; Great malfunctioning may be expected if this file/buffer is modified
186 ;; valid face. 186 ;;; outside of this package while it is being visited!
187 ;; E.g. 187 ;;;
188 ;; (make-face 'my-face) 188 ;;; Normal operation is to transfer one line (record) from the data file,
189 ;; (setq forms-ro-face 'my-face) 189 ;;; split it into fields (into `forms--the-record-list'), and display it
190 ;; 190 ;;; using the specs in `forms-format-list'.
191 ;; forms-rw-face [symbol, default 'region] 191 ;;; A format routine `forms--format' is built upon startup to format
192 ;; This is the face that is used to show 192 ;;; the records according to `forms-format-list'.
193 ;; read-write text on the screen. 193 ;;;
194 ;; 194 ;;; When a form is changed the record is updated as soon as this form
195 ;; After evaluating the control file, its buffer is cleared and used 195 ;;; is left. The contents of the form are parsed using information
196 ;; for further processing. 196 ;;; obtained from `forms-format-list', and the fields which are
197 ;; The data file (as designated by `forms-file') is visited in a buffer 197 ;;; deduced from the form are modified. Fields not shown on the forms
198 ;; `forms--file-buffer' which will not normally be shown. 198 ;;; retain their origional values. The newly formed record then
199 ;; Great malfunctioning may be expected if this file/buffer is modified 199 ;;; replaces the contents of the old record in `forms--file-buffer'.
200 ;; outside of this package while it is being visited! 200 ;;; A parse routine `forms--parser' is built upon startup to parse
201 ;; 201 ;;; the records.
202 ;; Normal operation is to transfer one line (record) from the data file, 202 ;;;
203 ;; split it into fields (into `forms--the-record-list'), and display it 203 ;;; Two exit functions exist: `forms-exit' and `forms-exit-no-save'.
204 ;; using the specs in `forms-format-list'. 204 ;;; `forms-exit' saves the data to the file, if modified.
205 ;; A format routine `forms--format' is built upon startup to format 205 ;;; `forms-exit-no-save` does not. However, if `forms-exit-no-save'
206 ;; the records according to `forms-format-list'. 206 ;;; is executed and the file buffer has been modified, Emacs will ask
207 ;; 207 ;;; questions anyway.
208 ;; When a form is changed the record is updated as soon as this form 208 ;;;
209 ;; is left. The contents of the form are parsed using information 209 ;;; Other functions provided by forms mode are:
210 ;; obtained from `forms-format-list', and the fields which are 210 ;;;
211 ;; deduced from the form are modified. Fields not shown on the forms 211 ;;; paging (forward, backward) by record
212 ;; retain their original values. The newly formed record then 212 ;;; jumping (first, last, random number)
213 ;; replaces the contents of the old record in `forms--file-buffer'. 213 ;;; searching
214 ;; A parse routine `forms--parser' is built upon startup to parse 214 ;;; creating and deleting records
215 ;; the records. 215 ;;; reverting the form (NOT the file buffer)
216 ;; 216 ;;; switching edit <-> view mode v.v.
217 ;; Two exit functions exist: `forms-exit' and `forms-exit-no-save'. 217 ;;; jumping from field to field
218 ;; `forms-exit' saves the data to the file, if modified. 218 ;;;
219 ;; `forms-exit-no-save` does not. However, if `forms-exit-no-save' 219 ;;; As an documented side-effect: jumping to the last record in the
220 ;; is executed and the file buffer has been modified, Emacs will ask 220 ;;; file (using forms-last-record) will adjust forms--total-records if
221 ;; questions anyway. 221 ;;; needed.
222 ;; 222 ;;;
223 ;; Other functions provided by forms mode are: 223 ;;; The forms buffer can be in on eof two modes: edit mode or view
224 ;; 224 ;;; mode. View mode is a read-only mode, you cannot modify the
225 ;; paging (forward, backward) by record 225 ;;; contents of the buffer.
226 ;; jumping (first, last, random number) 226 ;;;
227 ;; searching 227 ;;; Edit mode commands:
228 ;; creating and deleting records 228 ;;;
229 ;; reverting the form (NOT the file buffer) 229 ;;; TAB forms-next-field
230 ;; switching edit <-> view mode v.v. 230 ;;; \C-c TAB forms-next-field
231 ;; jumping from field to field 231 ;;; \C-c < forms-first-record
232 ;; 232 ;;; \C-c > forms-last-record
233 ;; As an documented side-effect: jumping to the last record in the 233 ;;; \C-c ? describe-mode
234 ;; file (using forms-last-record) will adjust forms--total-records if 234 ;;; \C-c \C-k forms-delete-record
235 ;; needed. 235 ;;; \C-c \C-q forms-toggle-read-only
236 ;; 236 ;;; \C-c \C-o forms-insert-record
237 ;; The forms buffer can be in on eof two modes: edit mode or view 237 ;;; \C-c \C-l forms-jump-record
238 ;; mode. View mode is a read-only mode, you cannot modify the 238 ;;; \C-c \C-n forms-next-record
239 ;; contents of the buffer. 239 ;;; \C-c \C-p forms-prev-record
240 ;; 240 ;;; \C-c \C-s forms-search
241 ;; Edit mode commands: 241 ;;; \C-c \C-x forms-exit
242 ;; 242 ;;;
243 ;; TAB forms-next-field 243 ;;; Read-only mode commands:
244 ;; \C-c TAB forms-next-field 244 ;;;
245 ;; \C-c < forms-first-record 245 ;;; SPC forms-next-record
246 ;; \C-c > forms-last-record 246 ;;; DEL forms-prev-record
247 ;; \C-c ? describe-mode 247 ;;; ? describe-mode
248 ;; \C-c \C-k forms-delete-record 248 ;;; \C-q forms-toggle-read-only
249 ;; \C-c \C-q forms-toggle-read-only 249 ;;; l forms-jump-record
250 ;; \C-c \C-o forms-insert-record 250 ;;; n forms-next-record
251 ;; \C-c \C-l forms-jump-record 251 ;;; p forms-prev-record
252 ;; \C-c \C-n forms-next-record 252 ;;; s forms-search
253 ;; \C-c \C-p forms-prev-record 253 ;;; x forms-exit
254 ;; \C-c \C-r forms-search-backward 254 ;;;
255 ;; \C-c \C-s forms-search-forward 255 ;;; Of course, it is also possible to use the \C-c prefix to obtain the
256 ;; \C-c \C-x forms-exit 256 ;;; same command keys as in edit mode.
257 ;; 257 ;;;
258 ;; Read-only mode commands: 258 ;;; The following bindings are available, independent of the mode:
259 ;; 259 ;;;
260 ;; SPC forms-next-record 260 ;;; [next] forms-next-record
261 ;; DEL forms-prev-record 261 ;;; [prior] forms-prev-record
262 ;; ? describe-mode 262 ;;; [begin] forms-first-record
263 ;; \C-q forms-toggle-read-only 263 ;;; [end] forms-last-record
264 ;; l forms-jump-record 264 ;;; [S-TAB] forms-prev-field
265 ;; n forms-next-record 265 ;;; [backtab] forms-prev-field
266 ;; p forms-prev-record 266 ;;;
267 ;; r forms-search-backward 267 ;;; For convenience, TAB is always bound to `forms-next-field', so you
268 ;; s forms-search-forward 268 ;;; don't need the C-c prefix for this command.
269 ;; x forms-exit 269 ;;;
270 ;; 270 ;;; As mentioned above (see `forms-forms-scroll' and `forms-forms-jump')
271 ;; Of course, it is also possible to use the \C-c prefix to obtain the 271 ;;; the bindings of standard functions `scroll-up', `scroll-down',
272 ;; same command keys as in edit mode. 272 ;;; `beginning-of-buffer' and `end-of-buffer' can be locally replaced with
273 ;; 273 ;;; forms mode functions next/prev record and first/last
274 ;; The following bindings are available, independent of the mode: 274 ;;; record.
275 ;; 275 ;;;
276 ;; [next] forms-next-record 276 ;;; `local-write-file hook' is defined to save the actual data file
277 ;; [prior] forms-prev-record 277 ;;; instead of the buffer data, `revert-file-hook' is defined to
278 ;; [begin] forms-first-record 278 ;;; revert a forms to original.
279 ;; [end] forms-last-record
280 ;; [S-TAB] forms-prev-field
281 ;; [backtab] forms-prev-field
282 ;;
283 ;; For convenience, TAB is always bound to `forms-next-field', so you
284 ;; don't need the C-c prefix for this command.
285 ;;
286 ;; As mentioned above (see `forms-forms-scroll' and `forms-forms-jump')
287 ;; the bindings of standard functions `scroll-up', `scroll-down',
288 ;; `beginning-of-buffer' and `end-of-buffer' can be locally replaced with
289 ;; forms mode functions next/prev record and first/last
290 ;; record.
291 ;;
292 ;; `local-write-file hook' is defined to save the actual data file
293 ;; instead of the buffer data, `revert-file-hook' is defined to
294 ;; revert a forms to original.
295 279
296 ;;; Code: 280 ;;; Code:
297 281
298 ;;; Global variables and constants: 282 ;;; Global variables and constants:
299 283
300 (provide 'forms) ;;; official 284 (provide 'forms) ;;; official
301 (provide 'forms-mode) ;;; for compatibility 285 (provide 'forms-mode) ;;; for compatibility
302 286
303 (defconst forms-version (substring "$Revision: 1.2 $" 11 -2) 287 (defconst forms-version (substring "!Revision: 2.10 !" 11 -2)
304 "The version number of forms-mode (as string). The complete RCS id is: 288 "The version number of forms-mode (as string). The complete RCS id is:
305 289
306 $Id: forms.el,v 1.2 1997/08/21 06:24:16 steve Exp $") 290 !Id: forms.el,v 2.10 1994/07/26 21:31:13 rms Exp !")
307 291
308 (defvar forms-mode-hooks nil 292 (defvar forms-mode-hooks nil
309 "Hook functions to be run upon entering Forms mode.") 293 "Hook functions to be run upon entering Forms mode.")
310 294
311 ;;; Mandatory variables - must be set by evaluating the control file. 295 ;;; Mandatory variables - must be set by evaluating the control file.
319 (defvar forms-number-of-fields nil 303 (defvar forms-number-of-fields nil
320 "Number of fields per record.") 304 "Number of fields per record.")
321 305
322 ;;; Optional variables with default values. 306 ;;; Optional variables with default values.
323 307
324 (defvar forms-check-number-of-fields t
325 "*If non-nil, warn about records with wrong number of fields.")
326
327 (defvar forms-field-sep "\t" 308 (defvar forms-field-sep "\t"
328 "Field separator character (default TAB).") 309 "Field separator character (default TAB).")
329 310
330 (defvar forms-read-only nil 311 (defvar forms-read-only nil
331 "Non-nil means: visit the file in view (read-only) mode. 312 "Non-nil means: visit the file in view (read-only) mode.
365 (defvar forms-use-extents (fboundp 'set-extent-property) ; XEmacs 19.9+ 346 (defvar forms-use-extents (fboundp 'set-extent-property) ; XEmacs 19.9+
366 "*Non-nil means: use XEmacs/Lucid Emacs extents. 347 "*Non-nil means: use XEmacs/Lucid Emacs extents.
367 Defaults to t if this emacs is capable of handling text properties.") 348 Defaults to t if this emacs is capable of handling text properties.")
368 349
369 (defvar forms-use-text-properties (and (fboundp 'set-text-properties) 350 (defvar forms-use-text-properties (and (fboundp 'set-text-properties)
370 (not forms-use-extents)) ; XEmacs 351 (not forms-use-extents))
371 "*Non-nil means: use emacs-19 text properties. 352 "*Non-nil means: use emacs-19 text properties.
372 Defaults to t if this emacs is capable of handling text properties.") 353 Defaults to t if this emacs is capable of handling text properties.")
373
374 (defvar forms-insert-after nil
375 "*Non-nil means: inserts of new records go after current record.
376 Also, initial position is at last record.")
377 354
378 (defvar forms-ro-face (if (string-match "XEmacs" emacs-version) 355 (defvar forms-ro-face (if (string-match "XEmacs" emacs-version)
379 'forms-label-face 356 'forms-label-face
380 'default) 357 'default)
381 "The face (a symbol) that is used to display read-only text on the screen.") 358 "The face (a symbol) that is used to display read-only text on the screen.")
413 390
414 (defvar forms--the-record-list nil 391 (defvar forms--the-record-list nil
415 "List of strings of the current record, as parsed from the file.") 392 "List of strings of the current record, as parsed from the file.")
416 393
417 (defvar forms--search-regexp nil 394 (defvar forms--search-regexp nil
418 "Last regexp used by forms-search functions.") 395 "Last regexp used by forms-search.")
419 396
420 (defvar forms--format nil 397 (defvar forms--format nil
421 "Formatting routine.") 398 "Formatting routine.")
422 399
423 (defvar forms--parser nil 400 (defvar forms--parser nil
436 (defvar forms--ro-face nil 413 (defvar forms--ro-face nil
437 "Face used to represent read-only data on the screen.") 414 "Face used to represent read-only data on the screen.")
438 415
439 (defvar forms--rw-face nil 416 (defvar forms--rw-face nil
440 "Face used to represent read-write data on the screen.") 417 "Face used to represent read-write data on the screen.")
418
441 419
442 ;;;###autoload 420 ;;;###autoload
443 (defun forms-mode (&optional primary) 421 (defun forms-mode (&optional primary)
444 "Major mode to visit files in a field-structured manner using a form. 422 "Major mode to visit files in a field-structured manner using a form.
445 423
446 Commands: Equivalent keys in read-only mode: 424 Commands: Equivalent keys in read-only mode:
447 TAB forms-next-field TAB 425
448 \\C-c TAB forms-next-field 426 TAB forms-next-field TAB
449 \\C-c < forms-first-record < 427 C-c TAB forms-next-field
450 \\C-c > forms-last-record > 428 C-c < forms-first-record <
451 \\C-c ? describe-mode ? 429 C-c > forms-last-record >
452 \\C-c \\C-k forms-delete-record 430 C-c ? describe-mode ?
453 \\C-c \\C-q forms-toggle-read-only q 431 C-c C-k forms-delete-record
454 \\C-c \\C-o forms-insert-record 432 C-c C-q forms-toggle-read-only q
455 \\C-c \\C-l forms-jump-record l 433 C-c C-o forms-insert-record
456 \\C-c \\C-n forms-next-record n 434 C-c C-l forms-jump-record l
457 \\C-c \\C-p forms-prev-record p 435 C-c C-n forms-next-record n
458 \\C-c \\C-r forms-search-backward r 436 C-c C-p forms-prev-record p
459 \\C-c \\C-s forms-search-forward s 437 C-c C-s forms-search s
460 \\C-c \\C-x forms-exit x 438 C-c C-x forms-exit x"
461 "
462 (interactive) 439 (interactive)
463 440
464 ;; This is not a simple major mode, as usual. Therefore, forms-mode 441 ;; This is not a simple major mode, as usual. Therefore, forms-mode
465 ;; takes an optional argument `primary' which is used for the 442 ;; takes an optional argument `primary' which is used for the
466 ;; initial set-up. Normal use would leave `primary' to nil. 443 ;; initial set-up. Normal use would leave `primary' to nil.
487 (make-local-variable 'forms-field-sep) 464 (make-local-variable 'forms-field-sep)
488 (make-local-variable 'forms-read-only) 465 (make-local-variable 'forms-read-only)
489 (make-local-variable 'forms-multi-line) 466 (make-local-variable 'forms-multi-line)
490 (make-local-variable 'forms-forms-scroll) 467 (make-local-variable 'forms-forms-scroll)
491 (make-local-variable 'forms-forms-jump) 468 (make-local-variable 'forms-forms-jump)
492 (make-local-variable 'forms-insert-after)
493 ;; (make-local-variable 'forms-use-text-properties) 469 ;; (make-local-variable 'forms-use-text-properties)
494 470
495 ;; Filter functions. 471 ;; Filter functions.
496 (make-local-variable 'forms-read-file-filter) 472 (make-local-variable 'forms-read-file-filter)
497 (make-local-variable 'forms-write-file-filter) 473 (make-local-variable 'forms-write-file-filter)
502 (setq forms-read-file-filter nil) 478 (setq forms-read-file-filter nil)
503 (setq forms-write-file-filter nil) 479 (setq forms-write-file-filter nil)
504 (setq forms-new-record-filter nil) 480 (setq forms-new-record-filter nil)
505 (setq forms-modified-record-filter nil) 481 (setq forms-modified-record-filter nil)
506 482
507 (if forms--lemacs-p ; XEmacs 483 (if forms--lemacs-p
508 (progn 484 (progn
509 ;; forms-field-face defaults to bold. 485 ;; forms-field-face defaults to bold.
510 ;; forms-label-face defaults to no attributes 486 ;; forms-label-face defaults to no attributes
511 ;; (inherit from default.) 487 ;; (inherit from default.)
512 (make-face 'forms-field-face) 488 (make-face 'forms-field-face)
600 576
601 ;; Dynamic text support. 577 ;; Dynamic text support.
602 (make-local-variable 'forms--dynamic-text) 578 (make-local-variable 'forms--dynamic-text)
603 579
604 ;; Prevent accidental overwrite of the control file and autosave. 580 ;; Prevent accidental overwrite of the control file and autosave.
605 (set-visited-file-name nil) 581 (setq buffer-file-name nil)
582 (auto-save-mode nil)
606 583
607 ;; Prepare this buffer for further processing. 584 ;; Prepare this buffer for further processing.
608 (setq buffer-read-only nil) 585 (setq buffer-read-only nil)
609 (erase-buffer) 586 (erase-buffer)
610 587
642 ;;(message "forms: building keymap...") 619 ;;(message "forms: building keymap...")
643 (forms--mode-commands) 620 (forms--mode-commands)
644 ;;(message "forms: building keymap... done.") 621 ;;(message "forms: building keymap... done.")
645 ) 622 )
646 623
647 ;; set the major mode indicator
648 (setq major-mode 'forms-mode)
649 (setq mode-name "Forms")
650
651 ;; find the data file 624 ;; find the data file
652 (setq forms--file-buffer (find-file-noselect forms-file)) 625 (setq forms--file-buffer (find-file-noselect forms-file))
653 626
654 ;; Pre-transform. 627 ;; Pre-transform.
655 (let ((read-file-filter forms-read-file-filter) 628 (let ((read-file-filter forms-read-file-filter)
656 (write-file-filter forms-write-file-filter)) 629 (write-file-filter forms-write-file-filter))
657 (if read-file-filter 630 (if read-file-filter
658 (save-excursion 631 (save-excursion
659 (set-buffer forms--file-buffer) 632 (set-buffer forms--file-buffer)
660 (let ((inhibit-read-only t) 633 (let ((inhibit-read-only t))
661 (file-modified (buffer-modified-p))) 634 (run-hooks 'read-file-filter))
662 (run-hooks 'read-file-filter) 635 (set-buffer-modified-p nil)
663 (if (not file-modified) (set-buffer-modified-p nil)))
664 (if write-file-filter 636 (if write-file-filter
665 (progn 637 (progn
666 (make-variable-buffer-local 'local-write-file-hooks) 638 (make-variable-buffer-local 'local-write-file-hooks)
667 (setq local-write-file-hooks (list write-file-filter))))) 639 (setq local-write-file-hooks (list write-file-filter)))))
668 (if write-file-filter 640 (if write-file-filter
669 (save-excursion 641 (save-excursion
670 (set-buffer forms--file-buffer) 642 (set-buffer forms--file-buffer)
671 (make-variable-buffer-local 'local-write-file-hooks) 643 (make-variable-buffer-local 'local-write-file-hooks)
672 ;; (setq local-write-file-hooks (list write-file-filter)))))) 644 (setq local-write-file-hooks write-file-filter)))))
673 (add-hook 'local-write-file-hooks 'write-file-filter)))))
674 645
675 ;; count the number of records, and set see if it may be modified 646 ;; count the number of records, and set see if it may be modified
676 (let (ro) 647 (let (ro)
677 (setq forms--total-records 648 (setq forms--total-records
678 (save-excursion 649 (save-excursion
687 ))) 658 )))
688 (if ro 659 (if ro
689 (setq forms-read-only t))) 660 (setq forms-read-only t)))
690 661
691 ;;(message "forms: proceeding setup...") 662 ;;(message "forms: proceeding setup...")
663 ;; set the major mode indicator
664 (setq major-mode 'forms-mode)
665 (setq mode-name "Forms")
692 666
693 ;; Since we aren't really implementing a minor mode, we hack the modeline 667 ;; Since we aren't really implementing a minor mode, we hack the modeline
694 ;; directly to get the text " View " into forms-read-only form buffers. For 668 ;; directly to get the text " View " into forms-read-only form buffers. For
695 ;; that reason, this variable must be buffer only. 669 ;; that reason, this variable must be buffer only.
696 (make-local-variable 'minor-mode-alist) 670 (make-local-variable 'minor-mode-alist)
708 ;;(message "forms: proceeding setup (new file)...") 682 ;;(message "forms: proceeding setup (new file)...")
709 (progn 683 (progn
710 (insert 684 (insert
711 "GNU Emacs Forms Mode version " forms-version "\n\n" 685 "GNU Emacs Forms Mode version " forms-version "\n\n"
712 (if (file-exists-p forms-file) 686 (if (file-exists-p forms-file)
713 (concat "No records available in file `" forms-file "'\n\n") 687 (concat "No records available in file \"" forms-file "\".\n\n")
714 (format "Creating new file `%s'\nwith %d field%s per record\n\n" 688 (format "Creating new file \"%s\"\nwith %d field%s per record.\n\n"
715 forms-file forms-number-of-fields 689 forms-file forms-number-of-fields
716 (if (= 1 forms-number-of-fields) "" "s"))) 690 (if (= 1 forms-number-of-fields) "" "s")))
717 "Use " (substitute-command-keys "\\[forms-insert-record]") 691 "Use " (substitute-command-keys "\\[forms-insert-record]")
718 " to create new records.\n") 692 " to create new records.\n")
719 (setq forms--current-record 1) 693 (setq forms--current-record 1)
723 ;; setup the first (or current) record to show 697 ;; setup the first (or current) record to show
724 (if (< forms--current-record 1) 698 (if (< forms--current-record 1)
725 (setq forms--current-record 1)) 699 (setq forms--current-record 1))
726 (forms-jump-record forms--current-record) 700 (forms-jump-record forms--current-record)
727 ) 701 )
728
729 (if forms-insert-after
730 (forms-last-record)
731 (forms-first-record))
732 702
733 ;; user customising 703 ;; user customising
734 ;;(message "forms: proceeding setup (user hooks)...") 704 ;;(message "forms: proceeding setup (user hooks)...")
735 (run-hooks 'forms-mode-hooks) 705 (run-hooks 'forms-mode-hooks)
736 ;;(message "forms: setting up... done.") 706 ;;(message "forms: setting up... done.")
745 ;; concatenated. 715 ;; concatenated.
746 ;; Array `forms--elements' is constructed that contains the order 716 ;; Array `forms--elements' is constructed that contains the order
747 ;; of the fields on the display. This array is used by 717 ;; of the fields on the display. This array is used by
748 ;; `forms--parser-using-text-properties' to extract the fields data 718 ;; `forms--parser-using-text-properties' to extract the fields data
749 ;; from the form on the screen. 719 ;; from the form on the screen.
750 ;; Upon completion, `forms-format-list' is guaranteed correct, so 720 ;; Upon completion, `forms-format-list' is garanteed correct, so
751 ;; `forms--make-format' and `forms--make-parser' do not need to perform 721 ;; `forms--make-format' and `forms--make-parser' do not need to perform
752 ;; any checks. 722 ;; any checks.
753 723
754 ;; Verify that `forms-format-list' is not nil. 724 ;; Verify that `forms-format-list' is not nil.
755 (or forms-format-list 725 (or forms-format-list
817 ((listp el) 787 ((listp el)
818 788
819 ;; Validate. 789 ;; Validate.
820 (or (fboundp (car-safe el)) 790 (or (fboundp (car-safe el))
821 (error (concat "Forms format error: " 791 (error (concat "Forms format error: "
822 "not a function %S") 792 "not a function "
823 (car-safe el))) 793 (prin1-to-string (car-safe el)))))
824 794
825 ;; Shift. 795 ;; Shift.
826 (if prev-item 796 (if prev-item
827 (setq forms-format-list 797 (setq forms-format-list
828 (append forms-format-list (list prev-item) nil))) 798 (append forms-format-list (list prev-item) nil)))
829 (setq prev-item el)) 799 (setq prev-item el))
830 800
831 ;; else 801 ;; else
832 (t 802 (t
833 (error (concat "Forms format error: " 803 (error (concat "Forms format error: "
834 "invalid element %S") 804 "invalid element "
835 el))) 805 (prin1-to-string el)))))
836 806
837 ;; Advance to next element of the list. 807 ;; Advance to next element of the list.
838 (setq the-list rem))) 808 (setq the-list rem)))
839 809
840 ;; Append last item. 810 ;; Append last item.
938 forms-format-list))) 908 forms-format-list)))
939 ;; Prevent insertion before the first text. 909 ;; Prevent insertion before the first text.
940 (,@ (if (numberp (car forms-format-list)) 910 (,@ (if (numberp (car forms-format-list))
941 nil 911 nil
942 '((add-text-properties (point-min) (1+ (point-min)) 912 '((add-text-properties (point-min) (1+ (point-min))
943 '(front-sticky (read-only intangible)))))) 913 '(front-sticky (read-only))))))
944 ;; Prevent insertion after the last text. 914 ;; Prevent insertion after the last text.
945 (remove-text-properties (1- (point)) (point) 915 (remove-text-properties (1- (point)) (point)
946 '(rear-nonsticky))) 916 '(rear-nonsticky)))
947 (setq forms--iif-start nil))) 917 (setq forms--iif-start nil)))
948 (if forms-use-extents ; XEmacs version 918 (if forms-use-extents
949 (` (lambda (arg) 919 (` (lambda (arg)
950 (,@ (apply 'append 920 (,@ (apply 'append
951 (mapcar 'forms--make-format-elt-using-extents 921 (mapcar 'forms--make-format-elt-using-extents
952 forms-format-list))) 922 forms-format-list)))
953 923
1066 (progn ; until after insertion 1036 (progn ; until after insertion
1067 (insert (, el)) 1037 (insert (, el))
1068 (point)) 1038 (point))
1069 (list 'face forms--ro-face ; read-only appearance 1039 (list 'face forms--ro-face ; read-only appearance
1070 'read-only (,@ (list (1+ forms--marker))) 1040 'read-only (,@ (list (1+ forms--marker)))
1071 'intangible t
1072 'insert-in-front-hooks '(forms--iif-hook) 1041 'insert-in-front-hooks '(forms--iif-hook)
1073 'rear-nonsticky '(face read-only insert-in-front-hooks 1042 'rear-nonsticky '(face read-only insert-in-front-hooks))))))
1074 intangible))))))
1075 1043
1076 ((numberp el) 1044 ((numberp el)
1077 (` ((let ((here (point))) 1045 (` ((let ((here (point)))
1078 (aset forms--markers 1046 (aset forms--markers
1079 (, (prog1 forms--marker 1047 (, (prog1 forms--marker
1095 (setq forms--dyntext (1+ forms--dyntext)))) 1063 (setq forms--dyntext (1+ forms--dyntext))))
1096 (, el))) 1064 (, el)))
1097 (point)) 1065 (point))
1098 (list 'face forms--ro-face 1066 (list 'face forms--ro-face
1099 'read-only (,@ (list (1+ forms--marker))) 1067 'read-only (,@ (list (1+ forms--marker)))
1100 'intangible t
1101 'insert-in-front-hooks '(forms--iif-hook) 1068 'insert-in-front-hooks '(forms--iif-hook)
1102 'rear-nonsticky '(read-only face insert-in-front-hooks 1069 'rear-nonsticky '(read-only face insert-in-front-hooks))))))
1103 intangible))))))
1104 1070
1105 ;; end of cond 1071 ;; end of cond
1106 )) 1072 ))
1107 1073
1108 ;; XEmacs
1109 (defun forms--make-format-elt-using-extents (el) 1074 (defun forms--make-format-elt-using-extents (el)
1110 "Helper routine to generate format function." 1075 "Helper routine to generate format function."
1111 1076
1112 ;; The format routine `forms--format' will look like 1077 ;; The format routine `forms--format' will look like
1113 ;; 1078 ;;
1271 (if (get-text-property here 'read-only) 1236 (if (get-text-property here 'read-only)
1272 (aset forms--recordv (aref forms--elements i) nil) 1237 (aset forms--recordv (aref forms--elements i) nil)
1273 (if (setq there 1238 (if (setq there
1274 (next-single-property-change here 'read-only)) 1239 (next-single-property-change here 'read-only))
1275 (aset forms--recordv (aref forms--elements i) 1240 (aset forms--recordv (aref forms--elements i)
1276 (buffer-substring-no-properties here there)) 1241 (buffer-substring here there))
1277 (aset forms--recordv (aref forms--elements i) 1242 (aset forms--recordv (aref forms--elements i)
1278 (buffer-substring-no-properties here (point-max))))) 1243 (buffer-substring here (point-max)))))
1279 (setq i (1+ i))))) 1244 (setq i (1+ i)))))
1280 1245
1281 (defun forms--make-parser-elt (el) 1246 (defun forms--make-parser-elt (el)
1282 "Helper routine to generate forms parser function." 1247 "Helper routine to generate forms parser function."
1283 1248
1295 ;; ;; 6 1260 ;; ;; 6
1296 ;; ;; "\nmore text: " 1261 ;; ;; "\nmore text: "
1297 ;; (setq here (point)) 1262 ;; (setq here (point))
1298 ;; (if (not (search-forward "\nmore text: " nil t nil)) 1263 ;; (if (not (search-forward "\nmore text: " nil t nil))
1299 ;; (error "Parse error: cannot find \"\\nmore text: \"")) 1264 ;; (error "Parse error: cannot find \"\\nmore text: \""))
1300 ;; (aset forms--recordv 5 (buffer-substring-no-properties here (- (point) 12))) 1265 ;; (aset forms--recordv 5 (buffer-substring here (- (point) 12)))
1301 ;; 1266 ;;
1302 ;; ;; (tocol 40) 1267 ;; ;; (tocol 40)
1303 ;; (let ((forms--dyntext (car-safe forms--dynamic-text))) 1268 ;; (let ((forms--dyntext (car-safe forms--dynamic-text)))
1304 ;; (if (not (looking-at (regexp-quote forms--dyntext))) 1269 ;; (if (not (looking-at (regexp-quote forms--dyntext)))
1305 ;; (error "Parse error: not looking at \"%s\"" forms--dyntext)) 1270 ;; (error "Parse error: not looking at \"%s\"" forms--dyntext))
1306 ;; (forward-char (length forms--dyntext)) 1271 ;; (forward-char (length forms--dyntext))
1307 ;; (setq forms--dynamic-text (cdr-safe forms--dynamic-text))) 1272 ;; (setq forms--dynamic-text (cdr-safe forms--dynamic-text)))
1308 ;; ... 1273 ;; ...
1309 ;; ;; final flush (due to terminator sentinel, see below) 1274 ;; ;; final flush (due to terminator sentinel, see below)
1310 ;; (aset forms--recordv 7 (buffer-substring-no-properties (point) (point-max))) 1275 ;; (aset forms--recordv 7 (buffer-substring (point) (point-max)))
1311 1276
1312 (cond 1277 (cond
1313 ((stringp el) 1278 ((stringp el)
1314 (prog1 1279 (prog1
1315 (if forms--field 1280 (if forms--field
1331 (setq forms--field el) 1296 (setq forms--field el)
1332 nil)) 1297 nil))
1333 ((null el) 1298 ((null el)
1334 (if forms--field 1299 (if forms--field
1335 (` ((aset forms--recordv (, (1- forms--field)) 1300 (` ((aset forms--recordv (, (1- forms--field))
1336 (buffer-substring-no-properties (point) (point-max))))))) 1301 (buffer-substring (point) (point-max)))))))
1337 ((listp el) 1302 ((listp el)
1338 (prog1 1303 (prog1
1339 (if forms--field 1304 (if forms--field
1340 (` ((let ((here (point)) 1305 (` ((let ((here (point))
1341 (forms--dyntext (aref forms--dyntexts (, forms--dyntext)))) 1306 (forms--dyntext (aref forms--dyntexts (, forms--dyntext))))
1342 (if (not (search-forward forms--dyntext nil t nil)) 1307 (if (not (search-forward forms--dyntext nil t nil))
1343 (error "Parse error: cannot find \"%s\"" forms--dyntext)) 1308 (error "Parse error: cannot find \"%s\"" forms--dyntext))
1344 (aset forms--recordv (, (1- forms--field)) 1309 (aset forms--recordv (, (1- forms--field))
1345 (buffer-substring-no-properties here 1310 (buffer-substring here
1346 (- (point) (length forms--dyntext))))))) 1311 (- (point) (length forms--dyntext)))))))
1347 (` ((let ((forms--dyntext (aref forms--dyntexts (, forms--dyntext)))) 1312 (` ((let ((forms--dyntext (aref forms--dyntexts (, forms--dyntext))))
1348 (if (not (looking-at (regexp-quote forms--dyntext))) 1313 (if (not (looking-at (regexp-quote forms--dyntext)))
1349 (error "Parse error: not looking at \"%s\"" forms--dyntext)) 1314 (error "Parse error: not looking at \"%s\"" forms--dyntext))
1350 (forward-char (length forms--dyntext)))))) 1315 (forward-char (length forms--dyntext))))))
1416 (define-key forms-mode-map "\C-q" 'forms-toggle-read-only) 1381 (define-key forms-mode-map "\C-q" 'forms-toggle-read-only)
1417 (define-key forms-mode-map "\C-o" 'forms-insert-record) 1382 (define-key forms-mode-map "\C-o" 'forms-insert-record)
1418 (define-key forms-mode-map "\C-l" 'forms-jump-record) 1383 (define-key forms-mode-map "\C-l" 'forms-jump-record)
1419 (define-key forms-mode-map "\C-n" 'forms-next-record) 1384 (define-key forms-mode-map "\C-n" 'forms-next-record)
1420 (define-key forms-mode-map "\C-p" 'forms-prev-record) 1385 (define-key forms-mode-map "\C-p" 'forms-prev-record)
1421 (define-key forms-mode-map "\C-r" 'forms-search-backward) 1386 (define-key forms-mode-map "\C-s" 'forms-search)
1422 (define-key forms-mode-map "\C-s" 'forms-search-forward)
1423 (define-key forms-mode-map "\C-x" 'forms-exit) 1387 (define-key forms-mode-map "\C-x" 'forms-exit)
1424 (define-key forms-mode-map "<" 'forms-first-record) 1388 (define-key forms-mode-map "<" 'forms-first-record)
1425 (define-key forms-mode-map ">" 'forms-last-record) 1389 (define-key forms-mode-map ">" 'forms-last-record)
1426 (define-key forms-mode-map "?" 'describe-mode) ; XEmacs 1390 (define-key forms-mode-map "?" 'describe-mode)
1427 (define-key forms-mode-map "\C-?" 'forms-prev-record) 1391 (define-key forms-mode-map "\C-?" 'forms-prev-record)
1428 1392
1429 ;; `forms-mode-ro-map' replaces the local map when in read-only mode. 1393 ;; `forms-mode-ro-map' replaces the local map when in read-only mode.
1430 (setq forms-mode-ro-map (make-keymap)) 1394 (setq forms-mode-ro-map (make-keymap))
1431 (suppress-keymap forms-mode-ro-map) 1395 (suppress-keymap forms-mode-ro-map)
1433 (define-key forms-mode-ro-map "\t" 'forms-next-field) 1397 (define-key forms-mode-ro-map "\t" 'forms-next-field)
1434 (define-key forms-mode-ro-map "q" 'forms-toggle-read-only) 1398 (define-key forms-mode-ro-map "q" 'forms-toggle-read-only)
1435 (define-key forms-mode-ro-map "l" 'forms-jump-record) 1399 (define-key forms-mode-ro-map "l" 'forms-jump-record)
1436 (define-key forms-mode-ro-map "n" 'forms-next-record) 1400 (define-key forms-mode-ro-map "n" 'forms-next-record)
1437 (define-key forms-mode-ro-map "p" 'forms-prev-record) 1401 (define-key forms-mode-ro-map "p" 'forms-prev-record)
1438 (define-key forms-mode-ro-map "r" 'forms-search-backward) 1402 (define-key forms-mode-ro-map "s" 'forms-search)
1439 (define-key forms-mode-ro-map "s" 'forms-search-forward)
1440 (define-key forms-mode-ro-map "x" 'forms-exit) 1403 (define-key forms-mode-ro-map "x" 'forms-exit)
1441 (define-key forms-mode-ro-map "<" 'forms-first-record) 1404 (define-key forms-mode-ro-map "<" 'forms-first-record)
1442 (define-key forms-mode-ro-map ">" 'forms-last-record) 1405 (define-key forms-mode-ro-map ">" 'forms-last-record)
1443 (define-key forms-mode-ro-map "?" 'describe-mode) 1406 (define-key forms-mode-ro-map "?" 'describe-mode)
1444 (define-key forms-mode-ro-map " " 'forms-next-record) 1407 (define-key forms-mode-ro-map " " 'forms-next-record)
1445 (forms--mode-commands1 forms-mode-ro-map) 1408 (forms--mode-commands1 forms-mode-ro-map)
1446 (forms--mode-menu-ro forms-mode-ro-map)
1447 1409
1448 ;; This is the normal, local map. 1410 ;; This is the normal, local map.
1449 (setq forms-mode-edit-map (make-keymap)) 1411 (setq forms-mode-edit-map (make-keymap))
1450 (define-key forms-mode-edit-map "\t" 'forms-next-field) 1412 (define-key forms-mode-edit-map "\t" 'forms-next-field)
1451 (define-key forms-mode-edit-map "\C-c" forms-mode-map) 1413 (define-key forms-mode-edit-map "\C-c" forms-mode-map)
1452 (forms--mode-commands1 forms-mode-edit-map) 1414 (forms--mode-commands1 forms-mode-edit-map)
1453 (forms--mode-menu-edit forms-mode-edit-map)
1454 ) 1415 )
1455 1416
1456 (defun forms--mode-menu-ro (map) 1417 (defun forms--mode-commands1 (map)
1457 ;;; Menu initialisation
1458 ; (define-key map [menu-bar] (make-sparse-keymap))
1459 (define-key map [menu-bar forms]
1460 (cons "Forms" (make-sparse-keymap "Forms")))
1461 (define-key map [menu-bar forms menu-forms-exit]
1462 '("Exit Forms Mode" . forms-exit))
1463 (define-key map [menu-bar forms menu-forms-sep1]
1464 '("----"))
1465 (define-key map [menu-bar forms menu-forms-save]
1466 '("Save Data" . forms-save-buffer))
1467 (define-key map [menu-bar forms menu-forms-print]
1468 '("Print Data" . forms-print))
1469 (define-key map [menu-bar forms menu-forms-describe]
1470 '("Describe Mode" . describe-mode))
1471 (define-key map [menu-bar forms menu-forms-toggle-ro]
1472 '("Toggle View/Edit" . forms-toggle-read-only))
1473 (define-key map [menu-bar forms menu-forms-jump-record]
1474 '("Jump" . forms-jump-record))
1475 (define-key map [menu-bar forms menu-forms-search-backward]
1476 '("Search Backward" . forms-search-backward))
1477 (define-key map [menu-bar forms menu-forms-search-forward]
1478 '("Search Forward" . forms-search-forward))
1479 (define-key map [menu-bar forms menu-forms-delete-record]
1480 '("Delete" . forms-delete-record))
1481 (define-key map [menu-bar forms menu-forms-insert-record]
1482 '("Insert" . forms-insert-record))
1483 (define-key map [menu-bar forms menu-forms-sep2]
1484 '("----"))
1485 (define-key map [menu-bar forms menu-forms-last-record]
1486 '("Last Record" . forms-last-record))
1487 (define-key map [menu-bar forms menu-forms-first-record]
1488 '("First Record" . forms-first-record))
1489 (define-key map [menu-bar forms menu-forms-prev-record]
1490 '("Previous Record" . forms-prev-record))
1491 (define-key map [menu-bar forms menu-forms-next-record]
1492 '("Next Record" . forms-next-record))
1493 (define-key map [menu-bar forms menu-forms-sep3]
1494 '("----"))
1495 (define-key map [menu-bar forms menu-forms-prev-field]
1496 '("Previous Field" . forms-prev-field))
1497 (define-key map [menu-bar forms menu-forms-next-field]
1498 '("Next Field" . forms-next-field))
1499 (put 'forms-insert-record 'menu-enable '(not forms-read-only))
1500 (put 'forms-delete-record 'menu-enable '(not forms-read-only))
1501 )
1502 (defun forms--mode-menu-edit (map)
1503 ;;; Menu initialisation
1504 ; (define-key map [menu-bar] (make-sparse-keymap))
1505 (define-key map [menu-bar forms]
1506 (cons "Forms" (make-sparse-keymap "Forms")))
1507 (define-key map [menu-bar forms menu-forms-edit--exit]
1508 '("Exit" . forms-exit))
1509 (define-key map [menu-bar forms menu-forms-edit-sep1]
1510 '("----"))
1511 (define-key map [menu-bar forms menu-forms-edit-save]
1512 '("Save Data" . forms-save-buffer))
1513 (define-key map [menu-bar forms menu-forms-edit-print]
1514 '("Print Data" . forms-print))
1515 (define-key map [menu-bar forms menu-forms-edit-describe]
1516 '("Describe Mode" . describe-mode))
1517 (define-key map [menu-bar forms menu-forms-edit-toggle-ro]
1518 '("Toggle View/Edit" . forms-toggle-read-only))
1519 (define-key map [menu-bar forms menu-forms-edit-jump-record]
1520 '("Jump" . forms-jump-record))
1521 (define-key map [menu-bar forms menu-forms-edit-search-backward]
1522 '("Search Backward" . forms-search-backward))
1523 (define-key map [menu-bar forms menu-forms-edit-search-forward]
1524 '("Search Forward" . forms-search-forward))
1525 (define-key map [menu-bar forms menu-forms-edit-delete-record]
1526 '("Delete" . forms-delete-record))
1527 (define-key map [menu-bar forms menu-forms-edit-insert-record]
1528 '("Insert" . forms-insert-record))
1529 (define-key map [menu-bar forms menu-forms-edit-sep2]
1530 '("----"))
1531 (define-key map [menu-bar forms menu-forms-edit-last-record]
1532 '("Last Record" . forms-last-record))
1533 (define-key map [menu-bar forms menu-forms-edit-first-record]
1534 '("First Record" . forms-first-record))
1535 (define-key map [menu-bar forms menu-forms-edit-prev-record]
1536 '("Previous Record" . forms-prev-record))
1537 (define-key map [menu-bar forms menu-forms-edit-next-record]
1538 '("Next Record" . forms-next-record))
1539 (define-key map [menu-bar forms menu-forms-edit-sep3]
1540 '("----"))
1541 (define-key map [menu-bar forms menu-forms-edit-prev-field]
1542 '("Previous Field" . forms-prev-field))
1543 (define-key map [menu-bar forms menu-forms-edit-next-field]
1544 '("Next Field" . forms-next-field))
1545 (put 'forms-insert-record 'menu-enable '(not forms-read-only))
1546 (put 'forms-delete-record 'menu-enable '(not forms-read-only))
1547 )
1548
1549 (defun forms--mode-commands1 (map)
1550 "Helper routine to define keys." 1418 "Helper routine to define keys."
1551 (if forms--lemacs-p ; XEmacs 1419 (if forms--lemacs-p
1552 (progn 1420 (progn
1553 (define-key map [tab] 'forms-next-field) 1421 (define-key map [tab] 'forms-next-field)
1554 (define-key map [(shift tab)] 'forms-prev-field)) 1422 (define-key map [(shift tab)] 'forms-prev-field))
1555 (define-key map [TAB] 'forms-next-field) 1423 (define-key map [TAB] 'forms-next-field)
1556 (define-key map [S-tab] 'forms-prev-field)) 1424 (define-key map [S-tab] 'forms-prev-field))
1570 ;; scroll-up -> forms-next-record 1438 ;; scroll-up -> forms-next-record
1571 (if forms-forms-scroll 1439 (if forms-forms-scroll
1572 (progn 1440 (progn
1573 (substitute-key-definition 'scroll-up 'forms-next-record 1441 (substitute-key-definition 'scroll-up 'forms-next-record
1574 (current-local-map) 1442 (current-local-map)
1575 ;;(current-global-map) ; FSF 1443 ;;(current-global-map)
1576 ) 1444 )
1577 (substitute-key-definition 'scroll-down 'forms-prev-record 1445 (substitute-key-definition 'scroll-down 'forms-prev-record
1578 (current-local-map) 1446 (current-local-map)
1579 ;;(current-global-map) ; FSF 1447 ;;(current-global-map)
1580 ))) 1448 )))
1581 ;; 1449 ;;
1582 ;; beginning-of-buffer -> forms-first-record 1450 ;; beginning-of-buffer -> forms-first-record
1583 ;; end-of-buffer -> forms-end-record 1451 ;; end-of-buffer -> forms-end-record
1584 (if forms-forms-jump 1452 (if forms-forms-jump
1585 (progn 1453 (progn
1586 (substitute-key-definition 'beginning-of-buffer 'forms-first-record 1454 (substitute-key-definition 'beginning-of-buffer 'forms-first-record
1587 (current-local-map) 1455 (current-local-map)
1588 ;;(current-global-map) ; FSF 1456 ;;(current-global-map)
1589 ) 1457 )
1590 (substitute-key-definition 'end-of-buffer 'forms-last-record 1458 (substitute-key-definition 'end-of-buffer 'forms-last-record
1591 (current-local-map) 1459 (current-local-map)
1592 ;;(current-global-map) ;FSF 1460 ;;(current-global-map)
1593 ))) 1461 )))
1594 ;; 1462 ;;
1595 ;; Save buffer 1463 ;; Save buffer
1596 (local-set-key "\C-x\C-s" 'forms-save-buffer) 1464 (local-set-key "\C-x\C-s" 'forms-save-buffer)
1597 ;; 1465 ;;
1601 1469
1602 t) 1470 t)
1603 1471
1604 (defun forms--help () 1472 (defun forms--help ()
1605 "Initial help for Forms mode." 1473 "Initial help for Forms mode."
1606 (message "%s" (substitute-command-keys (concat 1474 (message (substitute-command-keys (concat
1607 "\\[forms-next-record]:next" 1475 "\\[forms-next-record]:next"
1608 " \\[forms-prev-record]:prev" 1476 " \\[forms-prev-record]:prev"
1609 " \\[forms-first-record]:first" 1477 " \\[forms-first-record]:first"
1610 " \\[forms-last-record]:last" 1478 " \\[forms-last-record]:last"
1611 " \\[describe-mode]:help")))) 1479 " \\[describe-mode]:help"))))
1649 (or (bolp) 1517 (or (bolp)
1650 (beginning-of-line nil)) 1518 (beginning-of-line nil))
1651 (let ((here (point))) 1519 (let ((here (point)))
1652 (prog2 1520 (prog2
1653 (end-of-line) 1521 (end-of-line)
1654 (buffer-substring-no-properties here (point)) 1522 (buffer-substring here (point))
1655 (goto-char here)))) 1523 (goto-char here))))
1656 1524
1657 (defun forms--show-record (the-record) 1525 (defun forms--show-record (the-record)
1658 "Format THE-RECORD and display it in the current buffer." 1526 "Format THE-RECORD and display it in the current buffer."
1659 1527
1675 1543
1676 (setq buffer-read-only nil) 1544 (setq buffer-read-only nil)
1677 (if forms-use-text-properties 1545 (if forms-use-text-properties
1678 (let ((inhibit-read-only t)) 1546 (let ((inhibit-read-only t))
1679 (set-text-properties (point-min) (point-max) nil))) 1547 (set-text-properties (point-min) (point-max) nil)))
1680 (if forms-use-extents
1681 (map-extents '(lambda (x ignore) (detach-extent x) nil)))
1682 (erase-buffer) 1548 (erase-buffer)
1683 1549
1684 ;; Verify the number of fields, extend forms--the-record-list if needed. 1550 ;; Verify the number of fields, extend forms--the-record-list if needed.
1685 (if (= (length forms--the-record-list) forms-number-of-fields) 1551 (if (= (length forms--the-record-list) forms-number-of-fields)
1686 nil 1552 nil
1687 (if (null forms-check-number-of-fields) 1553 (beep)
1688 nil 1554 (message "Warning: this record has %d fields instead of %d"
1689 (beep) 1555 (length forms--the-record-list) forms-number-of-fields)
1690 (message "Warning: this record has %d fields instead of %d"
1691 (length forms--the-record-list) forms-number-of-fields))
1692 (if (< (length forms--the-record-list) forms-number-of-fields) 1556 (if (< (length forms--the-record-list) forms-number-of-fields)
1693 (setq forms--the-record-list 1557 (setq forms--the-record-list
1694 (append forms--the-record-list 1558 (append forms--the-record-list
1695 (make-list 1559 (make-list
1696 (- forms-number-of-fields 1560 (- forms-number-of-fields
1704 ;; Prepare. 1568 ;; Prepare.
1705 (goto-char (point-min)) 1569 (goto-char (point-min))
1706 (set-buffer-modified-p nil) 1570 (set-buffer-modified-p nil)
1707 (setq buffer-read-only forms-read-only) 1571 (setq buffer-read-only forms-read-only)
1708 (setq mode-line-process 1572 (setq mode-line-process
1709 (concat " " forms--current-record "/" forms--total-records))) 1573 (format " %d/%d" forms--current-record forms--total-records)))
1710 1574
1711 (defun forms--parse-form () 1575 (defun forms--parse-form ()
1712 "Parse contents of form into list of strings." 1576 "Parse contents of form into list of strings."
1713 ;; The contents of the form are parsed, and a new list of strings 1577 ;; The contents of the form are parsed, and a new list of strings
1714 ;; is constructed. 1578 ;; is constructed.
1895 ;;; Other commands 1759 ;;; Other commands
1896 1760
1897 (defun forms-toggle-read-only (arg) 1761 (defun forms-toggle-read-only (arg)
1898 "Toggles read-only mode of a forms mode buffer. 1762 "Toggles read-only mode of a forms mode buffer.
1899 With an argument, enables read-only mode if the argument is positive. 1763 With an argument, enables read-only mode if the argument is positive.
1900 Otherwise enables edit mode if the visited file is writable." 1764 Otherwise enables edit mode if the visited file is writeable."
1901 1765
1902 (interactive "P") 1766 (interactive "P")
1903 1767
1904 (if (if arg 1768 (if (if arg
1905 ;; Negative arg means switch it off. 1769 ;; Negative arg means switch it off.
1912 (if (save-excursion 1776 (if (save-excursion
1913 (set-buffer forms--file-buffer) 1777 (set-buffer forms--file-buffer)
1914 buffer-read-only) 1778 buffer-read-only)
1915 (progn 1779 (progn
1916 (setq forms-read-only t) 1780 (setq forms-read-only t)
1917 (message "No write access to `%s'" forms-file) 1781 (message "No write access to \"%s\"" forms-file)
1918 (beep)) 1782 (beep))
1919 (setq forms-read-only nil)) 1783 (setq forms-read-only nil))
1920 (if (equal ro forms-read-only) 1784 (if (equal ro forms-read-only)
1921 nil 1785 nil
1922 (forms-mode))) 1786 (forms-mode)))
1938 1802
1939 (defun forms-insert-record (arg) 1803 (defun forms-insert-record (arg)
1940 "Create a new record before the current one. 1804 "Create a new record before the current one.
1941 With ARG: store the record after the current one. 1805 With ARG: store the record after the current one.
1942 If `forms-new-record-filter' contains the name of a function, 1806 If `forms-new-record-filter' contains the name of a function,
1943 it is called to fill (some of) the fields with default values. 1807 it is called to fill (some of) the fields with default values."
1944 If `forms-insert-after is non-nil, the default behavior is to insert
1945 after the current record."
1946 1808
1947 (interactive "P") 1809 (interactive "P")
1948 1810
1949 (if forms-read-only 1811 (if forms-read-only
1950 (error "")) 1812 (error ""))
1951 1813
1952 (let (ln the-list the-record) 1814 (let ((ln (if arg (1+ forms--current-record) forms--current-record))
1953 1815 the-list the-record)
1954 (if (or (and arg forms-insert-after)
1955 (and (not arg) (not forms-insert-after)))
1956 (setq ln forms--current-record)
1957 (setq ln (1+ forms--current-record)))
1958 1816
1959 (forms--checkmod) 1817 (forms--checkmod)
1960 (if forms-new-record-filter 1818 (if forms-new-record-filter
1961 ;; As a service to the user, we add a zeroth element so she 1819 ;; As a service to the user, we add a zeroth element so she
1962 ;; can use the same indices as in the forms definition. 1820 ;; can use the same indices as in the forms definition.
2005 (if (> forms--current-record forms--total-records) 1863 (if (> forms--current-record forms--total-records)
2006 (setq forms--current-record forms--total-records)) 1864 (setq forms--current-record forms--total-records))
2007 (forms-jump-record forms--current-record))) 1865 (forms-jump-record forms--current-record)))
2008 (message "")) 1866 (message ""))
2009 1867
2010 (defun forms-search-forward (regexp) 1868 (defun forms-search (regexp)
2011 "Search forward for record containing REGEXP." 1869 "Search REGEXP in file buffer."
2012 (interactive 1870 (interactive
2013 (list (read-string (concat "Search forward for" 1871 (list (read-string (concat "Search for"
2014 (if forms--search-regexp 1872 (if forms--search-regexp
2015 (concat " (" 1873 (concat " ("
2016 forms--search-regexp 1874 forms--search-regexp
2017 ")")) 1875 ")"))
2018 ": ")))) 1876 ": "))))
2027 (setq here (point)) 1885 (setq here (point))
2028 (end-of-line) 1886 (end-of-line)
2029 (if (null (re-search-forward regexp nil t)) 1887 (if (null (re-search-forward regexp nil t))
2030 (progn 1888 (progn
2031 (goto-char here) 1889 (goto-char here)
2032 (message "\"%s\" not found" regexp) 1890 (message (concat "\"" regexp "\" not found."))
2033 nil)
2034 (setq the-record (forms--get-record))
2035 (setq the-line (1+ (count-lines (point-min) (point))))))
2036 (progn
2037 (setq forms--current-record the-line)
2038 (forms--show-record the-record)
2039 (re-search-forward regexp nil t))))
2040 (setq forms--search-regexp regexp))
2041
2042 (defun forms-search-backward (regexp)
2043 "Search backward for record containing REGEXP."
2044 (interactive
2045 (list (read-string (concat "Search backward for"
2046 (if forms--search-regexp
2047 (concat " ("
2048 forms--search-regexp
2049 ")"))
2050 ": "))))
2051 (if (equal "" regexp)
2052 (setq regexp forms--search-regexp))
2053 (forms--checkmod)
2054
2055 (let (the-line the-record here
2056 (fld-sep forms-field-sep))
2057 (if (save-excursion
2058 (set-buffer forms--file-buffer)
2059 (setq here (point))
2060 (beginning-of-line)
2061 (if (null (re-search-backward regexp nil t))
2062 (progn
2063 (goto-char here)
2064 (message "\"%s\" not found" regexp)
2065 nil) 1891 nil)
2066 (setq the-record (forms--get-record)) 1892 (setq the-record (forms--get-record))
2067 (setq the-line (1+ (count-lines (point-min) (point)))))) 1893 (setq the-line (1+ (count-lines (point-min) (point))))))
2068 (progn 1894 (progn
2069 (setq forms--current-record the-line) 1895 (setq forms--current-record the-line)
2101 (interactive "p") 1927 (interactive "p")
2102 1928
2103 (let ((i 0) 1929 (let ((i 0)
2104 (here (point)) 1930 (here (point))
2105 there 1931 there
2106 (cnt 0) 1932 (cnt 0))
2107 (inhibit-point-motion-hooks t))
2108 1933
2109 (if (zerop arg) 1934 (if (zerop arg)
2110 (setq cnt 1) 1935 (setq cnt 1)
2111 (setq cnt (+ cnt arg))) 1936 (setq cnt (+ cnt arg)))
2112 1937
2128 (interactive "p") 1953 (interactive "p")
2129 1954
2130 (let ((i (length forms--markers)) 1955 (let ((i (length forms--markers))
2131 (here (point)) 1956 (here (point))
2132 there 1957 there
2133 (cnt 0) 1958 (cnt 0))
2134 (inhibit-point-motion-hooks t))
2135 1959
2136 (if (zerop arg) 1960 (if (zerop arg)
2137 (setq cnt 1) 1961 (setq cnt 1)
2138 (setq cnt (+ cnt arg))) 1962 (setq cnt (+ cnt arg)))
2139 1963
2147 (progn 1971 (progn
2148 (goto-char there) 1972 (goto-char there)
2149 (throw 'done t)))))) 1973 (throw 'done t))))))
2150 nil 1974 nil
2151 (goto-char (aref forms--markers (1- (length forms--markers))))))) 1975 (goto-char (aref forms--markers (1- (length forms--markers)))))))
2152
2153 (defun forms-print ()
2154 "Send the records to the printer with 'print-buffer', one record per page."
2155 (interactive)
2156 (let ((inhibit-read-only t)
2157 (save-record forms--current-record)
2158 (nb-record 1)
2159 (record nil))
2160 (while (<= nb-record forms--total-records)
2161 (forms-jump-record nb-record)
2162 (setq record (buffer-string))
2163 (save-excursion
2164 (set-buffer (get-buffer-create "*forms-print*"))
2165 (goto-char (buffer-end 1))
2166 (insert record)
2167 (setq buffer-read-only nil)
2168 (if (< nb-record forms--total-records)
2169 (insert "\n \n")))
2170 (setq nb-record (1+ nb-record)))
2171 (save-excursion
2172 (set-buffer "*forms-print*")
2173 (print-buffer)
2174 (set-buffer-modified-p nil)
2175 (kill-buffer (current-buffer)))
2176 (forms-jump-record save-record)))
2177
2178 ;;; 1976 ;;;
2179 ;;; Special service 1977 ;;; Special service
2180 ;;; 1978 ;;;
2181 (defun forms-enumerate (the-fields) 1979 (defun forms-enumerate (the-fields)
2182 "Take a quoted list of symbols, and set their values to sequential numbers. 1980 "Take a quoted list of symbols, and set their values to sequential numbers.
2183 The first symbol gets number 1, the second 2 and so on. 1981 The first symbol gets number 1, the second 2 and so on.
2184 It returns the highest number. 1982 It returns the higest number.
2185 1983
2186 Usage: (setq forms-number-of-fields 1984 Usage: (setq forms-number-of-fields
2187 (forms-enumerate 1985 (forms-enumerate
2188 '(field1 field2 field2 ...)))" 1986 '(field1 field2 field2 ...)))"
2189 1987