mirror of
https://git.savannah.gnu.org/git/emacs.git
synced 2024-11-24 07:20:37 +00:00
1357 lines
41 KiB
EmacsLisp
1357 lines
41 KiB
EmacsLisp
;;; forms.el -- Forms Mode - A GNU Emacs Major Mode
|
||
;;; SCCS Status : @(#)@ forms 1.2.7
|
||
;;; Author : Johan Vromans
|
||
;;; Created On : 1989
|
||
;;; Last Modified By: Johan Vromans
|
||
;;; Last Modified On: Mon Jul 1 14:13:20 1991
|
||
;;; Update Count : 15
|
||
;;; Status : OK
|
||
|
||
;;; This file is part of GNU Emacs.
|
||
;;; GNU Emacs is distributed in the hope that it will be useful,
|
||
;;; but WITHOUT ANY WARRANTY. No author or distributor
|
||
;;; accepts responsibility to anyone for the consequences of using it
|
||
;;; or for whether it serves any particular purpose or works at all,
|
||
;;; unless he says so in writing. Refer to the GNU Emacs General Public
|
||
;;; License for full details.
|
||
|
||
;;; Everyone is granted permission to copy, modify and redistribute
|
||
;;; GNU Emacs, but only under the conditions described in the
|
||
;;; GNU Emacs General Public License. A copy of this license is
|
||
;;; supposed to have been given to you along with GNU Emacs so you
|
||
;;; can know your rights and responsibilities.
|
||
;;; If you don't have this copy, write to the Free Software
|
||
;;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||
;;;
|
||
|
||
;;; HISTORY
|
||
;;; 1-Jul-1991 Johan Vromans
|
||
;;; Normalized error messages.
|
||
;;; 30-Jun-1991 Johan Vromans
|
||
;;; Add support for forms-modified-record-filter.
|
||
;;; Allow the filter functions to be the name of a function.
|
||
;;; Fix: parse--format used forms--dynamic-text destructively.
|
||
;;; Internally optimized the forms-format-list.
|
||
;;; Added support for debugging.
|
||
;;; Stripped duplicate documentation.
|
||
;;;
|
||
;;; 29-Jun-1991 Johan Vromans
|
||
;;; Add support for functions and lisp symbols in forms-format-list.
|
||
;;; Add function forms-enumerate.
|
||
|
||
(provide 'forms-mode)
|
||
|
||
;;; Visit a file using a form.
|
||
;;;
|
||
;;; === Naming conventions
|
||
;;;
|
||
;;; The names of all variables and functions start with 'form-'.
|
||
;;; Names which start with 'form--' are intended for internal use, and
|
||
;;; should *NOT* be used from the outside.
|
||
;;;
|
||
;;; All variables are buffer-local, to enable multiple forms visits
|
||
;;; simultaneously.
|
||
;;; Variable 'forms--mode-setup' is local to *ALL* buffers, for it
|
||
;;; controls if forms-mode has been enabled in a buffer.
|
||
;;;
|
||
;;; === How it works ===
|
||
;;;
|
||
;;; Forms mode means visiting a data file which is supposed to consist
|
||
;;; of records each containing a number of fields. The records are
|
||
;;; separated by a newline, the fields are separated by a user-defined
|
||
;;; field separater (default: TAB).
|
||
;;; When shown, a record is transferred to an emacs buffer and
|
||
;;; presented using a user-defined form. One record is shown at a
|
||
;;; time.
|
||
;;;
|
||
;;; Forms mode is a composite mode. It involves two files, and two
|
||
;;; buffers.
|
||
;;; The first file, called the control file, defines the name of the
|
||
;;; data file and the forms format. This file buffer will be used to
|
||
;;; present the forms.
|
||
;;; The second file holds the actual data. The buffer of this file
|
||
;;; will be buried, for it is never accessed directly.
|
||
;;;
|
||
;;; Forms mode is invoked using "forms-find-file control-file".
|
||
;;; Alternativily forms-find-file-other-window can be used.
|
||
;;;
|
||
;;; You may also visit the control file, and switch to forms mode by hand
|
||
;;; with M-x forms-mode .
|
||
;;;
|
||
;;; Automatic mode switching is supported, so you may use "find-file"
|
||
;;; if you specify "-*- forms -*-" in the first line of the control file.
|
||
;;;
|
||
;;; The control file is visited, evaluated using
|
||
;;; eval-current-buffer, and should set at least the following
|
||
;;; variables:
|
||
;;;
|
||
;;; forms-file [string] the name of the data file.
|
||
;;;
|
||
;;; forms-number-of-fields [integer]
|
||
;;; The number of fields in each record.
|
||
;;;
|
||
;;; forms-format-list [list] formatting instructions.
|
||
;;;
|
||
;;; The forms-format-list should be a list, each element containing
|
||
;;;
|
||
;;; - a string, e.g. "hello" (which is inserted \"as is\"),
|
||
;;;
|
||
;;; - an integer, denoting a field number. The contents of the field
|
||
;;; are inserted at this point.
|
||
;;; The first field has number one.
|
||
;;;
|
||
;;; - a function call, e.g. (insert "text"). This function call is
|
||
;;; dynamically evaluated and should return a string. It should *NOT*
|
||
;;; have side-effects on the forms being constructed.
|
||
;;; The current fields are available to the function in the variable
|
||
;;; forms-fields, they should *NOT* be modified.
|
||
;;;
|
||
;;; - a lisp symbol, that must evaluate to one of the above.
|
||
;;;
|
||
;;; Optional variables which may be set in the control file:
|
||
;;;
|
||
;;; forms-field-sep [string, default TAB]
|
||
;;; The field separator used to separate the
|
||
;;; fields in the data file. It may be a string.
|
||
;;;
|
||
;;; forms-read-only [bool, default nil]
|
||
;;; 't' means that the data file is visited read-only.
|
||
;;; If no write access to the data file is
|
||
;;; possible, read-only mode is enforced.
|
||
;;;
|
||
;;; forms-multi-line [string, default "^K"]
|
||
;;; If non-null the records of the data file may
|
||
;;; contain fields which span multiple lines in
|
||
;;; the form.
|
||
;;; This variable denoted the separator character
|
||
;;; to be used for this purpose. Upon display, all
|
||
;;; occurrencies of this character are translated
|
||
;;; to newlines. Upon storage they are translated
|
||
;;; back to the separator.
|
||
;;;
|
||
;;; forms-forms-scroll [bool, default t]
|
||
;;; If non-nil: redefine scroll-up/down to perform
|
||
;;; forms-next/prev-field if in forms mode.
|
||
;;;
|
||
;;; forms-forms-jump [bool, default t]
|
||
;;; If non-nil: redefine beginning/end-of-buffer
|
||
;;; to performs forms-first/last-field if in
|
||
;;; forms mode.
|
||
;;;
|
||
;;; forms-new-record-filter [symbol, no default]
|
||
;;; If defined: this should be the name of a
|
||
;;; function that is called when a new
|
||
;;; record is created. It can be used to fill in
|
||
;;; the new record with default fields, for example.
|
||
;;; Instead of the name of the function, it may
|
||
;;; be the function itself.
|
||
;;;
|
||
;;; forms-modified-record-filter [symbol, no default]
|
||
;;; If defined: this should be the name of a
|
||
;;; function that is called when a record has
|
||
;;; been modified. It is called after the fields
|
||
;;; are parsed. It can be used to register
|
||
;;; modification dates, for example.
|
||
;;; Instead of the name of the function, it may
|
||
;;; be the function itself.
|
||
;;;
|
||
;;; After evaluating the control file, its buffer is cleared and used
|
||
;;; for further processing.
|
||
;;; The data file (as designated by "forms-file") is visited in a buffer
|
||
;;; (forms--file-buffer) which will not normally be shown.
|
||
;;; Great malfunctioning may be expected if this file/buffer is modified
|
||
;;; outside of this package while it's being visited!
|
||
;;;
|
||
;;; A record from the data file is transferred from the data file,
|
||
;;; split into fields (into forms--the-record-list), and displayed using
|
||
;;; the specs in forms-format-list.
|
||
;;; A format routine 'forms--format' is built upon startup to format
|
||
;;; the records.
|
||
;;;
|
||
;;; When a form is changed the record is updated as soon as this form
|
||
;;; is left. The contents of the form are parsed using forms-format-list,
|
||
;;; and the fields which are deduced from the form are modified. So,
|
||
;;; fields not shown on the forms retain their origional values.
|
||
;;; The newly formed record and replaces the contents of the
|
||
;;; old record in forms--file-buffer.
|
||
;;; A parse routine 'forms--parser' is built upon startup to parse
|
||
;;; the records.
|
||
;;;
|
||
;;; Two exit functions exist: forms-exit (which saves) and forms-exit-no-save
|
||
;;; (which doesn't). However, if forms-exit-no-save is executed and the file
|
||
;;; buffer has been modified, emacs will ask questions.
|
||
;;;
|
||
;;; Other functions are:
|
||
;;;
|
||
;;; paging (forward, backward) by record
|
||
;;; jumping (first, last, random number)
|
||
;;; searching
|
||
;;; creating and deleting records
|
||
;;; reverting the form (NOT the file buffer)
|
||
;;; switching edit <-> view mode v.v.
|
||
;;; jumping from field to field
|
||
;;;
|
||
;;; As an documented side-effect: jumping to the last record in the
|
||
;;; file (using forms-last-record) will adjust forms--total-records if
|
||
;;; needed.
|
||
;;;
|
||
;;; Commands and keymaps:
|
||
;;;
|
||
;;; A local keymap 'forms-mode-map' is used in the forms buffer.
|
||
;;; As conventional, this map can be accessed with C-c prefix.
|
||
;;; In read-only mode, the C-c prefix must be omitted.
|
||
;;;
|
||
;;; Default bindings:
|
||
;;;
|
||
;;; \C-c forms-mode-map
|
||
;;; TAB forms-next-field
|
||
;;; SPC forms-next-record
|
||
;;; < forms-first-record
|
||
;;; > forms-last-record
|
||
;;; ? describe-mode
|
||
;;; d forms-delete-record
|
||
;;; e forms-edit-mode
|
||
;;; i forms-insert-record
|
||
;;; j forms-jump-record
|
||
;;; n forms-next-record
|
||
;;; p forms-prev-record
|
||
;;; q forms-exit
|
||
;;; s forms-search
|
||
;;; v forms-view-mode
|
||
;;; x forms-exit-no-save
|
||
;;; DEL forms-prev-record
|
||
;;;
|
||
;;; Standard functions scroll-up, scroll-down, beginning-of-buffer and
|
||
;;; end-of-buffer are wrapped with re-definitions, which map them to
|
||
;;; next/prev record and first/last record.
|
||
;;; Buffer-local variables forms-forms-scroll and forms-forms-jump
|
||
;;; may be used to control these redefinitions.
|
||
;;;
|
||
;;; Function save-buffer is also wrapped to perform a sensible action.
|
||
;;; A revert-file-hook is defined to revert a forms to original.
|
||
;;;
|
||
;;; For convenience, TAB is always bound to forms-next-field, so you
|
||
;;; don't need the C-c prefix for this command.
|
||
;;;
|
||
;;; Global variables and constants
|
||
|
||
(defconst forms-version "1.2.7"
|
||
"Version of forms-mode implementation")
|
||
|
||
(defvar forms-forms-scrolls t
|
||
"If non-null: redefine scroll-up/down to be used with forms-mode.")
|
||
|
||
(defvar forms-forms-jumps t
|
||
"If non-null: redefine beginning/end-of-buffer to be used with forms-mode.")
|
||
|
||
(defvar forms-mode-hooks nil
|
||
"Hook functions to be run upon entering forms mode.")
|
||
;;;
|
||
;;; Mandatory variables - must be set by evaluating the control file
|
||
|
||
(defvar forms-file nil
|
||
"Name of the file holding the data.")
|
||
|
||
(defvar forms-format-list nil
|
||
"List of formatting specifications.")
|
||
|
||
(defvar forms-number-of-fields nil
|
||
"Number of fields per record.")
|
||
|
||
;;;
|
||
;;; Optional variables with default values
|
||
|
||
(defvar forms-field-sep "\t"
|
||
"Field separator character (default TAB)")
|
||
|
||
(defvar forms-read-only nil
|
||
"Read-only mode (defaults to the write access on the data file).")
|
||
|
||
(defvar forms-multi-line "\C-k"
|
||
"Character to separate multi-line fields (default ^K)")
|
||
|
||
(defvar forms-forms-scroll t
|
||
"Redefine scroll-up/down to perform forms-next/prev-record when in
|
||
forms mode.")
|
||
|
||
(defvar forms-forms-jump t
|
||
"Redefine beginning/end-of-buffer to perform forms-first/last-record
|
||
when in forms mode.")
|
||
|
||
;;;
|
||
;;; Internal variables.
|
||
|
||
(defvar forms--file-buffer nil
|
||
"Buffer which holds the file data")
|
||
|
||
(defvar forms--total-records 0
|
||
"Total number of records in the data file.")
|
||
|
||
(defvar forms--current-record 0
|
||
"Number of the record currently on the screen.")
|
||
|
||
(defvar forms-mode-map nil ; yes - this one is global
|
||
"Keymap for form buffer.")
|
||
|
||
(defvar forms--markers nil
|
||
"Field markers in the screen.")
|
||
|
||
(defvar forms--number-of-markers 0
|
||
"Number of fields on screen.")
|
||
|
||
(defvar forms--the-record-list nil
|
||
"List of strings of the current record, as parsed from the file.")
|
||
|
||
(defvar forms--search-regexp nil
|
||
"Last regexp used by forms-search.")
|
||
|
||
(defvar forms--format nil
|
||
"Formatting routine.")
|
||
|
||
(defvar forms--parser nil
|
||
"Forms parser routine.")
|
||
|
||
(defvar forms--mode-setup nil
|
||
"Internal - keeps track of forms-mode being set-up.")
|
||
(make-variable-buffer-local 'forms--mode-setup)
|
||
|
||
(defvar forms--new-record-filter nil
|
||
"Internal - set if a new record filter has been defined.")
|
||
|
||
(defvar forms--modified-record-filter nil
|
||
"Internal - set if a modified record filter has been defined.")
|
||
|
||
(defvar forms--dynamic-text nil
|
||
"Internal - holds dynamic text to insert between fields.")
|
||
|
||
(defvar forms-fields nil
|
||
"List with fields of the current forms. First field has number 1.")
|
||
|
||
;;;
|
||
;;; forms-mode
|
||
;;;
|
||
;;; This is not a simple major mode, as usual. Therefore, forms-mode
|
||
;;; takes an optional argument 'primary' which is used for the initial
|
||
;;; set-up. Normal use would leave 'primary' to nil.
|
||
;;;
|
||
;;; A global buffer-local variable 'forms--mode-setup' has the same effect
|
||
;;; but makes it possible to auto-invoke forms-mode using find-file.
|
||
;;;
|
||
;;; Note: although it seems logical to have (make-local-variable) executed
|
||
;;; where the variable is first needed, I deliberately placed all calls
|
||
;;; in the forms-mode function.
|
||
|
||
(defun forms-mode (&optional primary)
|
||
"Major mode to visit files in a field-structured manner using a form.
|
||
|
||
Commands (prefix with C-c if not in read-only mode):
|
||
\\{forms-mode-map}"
|
||
|
||
(interactive) ; no - 'primary' is not prefix arg
|
||
|
||
;; Primary set-up: evaluate buffer and check if the mandatory
|
||
;; variables have been set.
|
||
(if (or primary (not forms--mode-setup))
|
||
(progn
|
||
(kill-all-local-variables)
|
||
|
||
;; make mandatory variables
|
||
(make-local-variable 'forms-file)
|
||
(make-local-variable 'forms-number-of-fields)
|
||
(make-local-variable 'forms-format-list)
|
||
|
||
;; make optional variables
|
||
(make-local-variable 'forms-field-sep)
|
||
(make-local-variable 'forms-read-only)
|
||
(make-local-variable 'forms-multi-line)
|
||
(make-local-variable 'forms-forms-scroll)
|
||
(make-local-variable 'forms-forms-jump)
|
||
(fmakunbound 'forms-new-record-filter)
|
||
|
||
;; eval the buffer, should set variables
|
||
(eval-current-buffer)
|
||
|
||
;; check if the mandatory variables make sense.
|
||
(or forms-file
|
||
(error "'forms-file' has not been set"))
|
||
(or forms-number-of-fields
|
||
(error "'forms-number-of-fields' has not been set"))
|
||
(or (> forms-number-of-fields 0)
|
||
(error "'forms-number-of-fields' must be > 0")
|
||
(or (stringp forms-field-sep))
|
||
(error "'forms-field-sep' is not a string"))
|
||
(if forms-multi-line
|
||
(if (and (stringp forms-multi-line)
|
||
(eq (length forms-multi-line) 1))
|
||
(if (string= forms-multi-line forms-field-sep)
|
||
(error "'forms-multi-line' is equal to 'forms-field-sep'"))
|
||
(error "'forms-multi-line' must be nil or a one-character string")))
|
||
|
||
;; validate and process forms-format-list
|
||
(make-local-variable 'forms--number-of-markers)
|
||
(make-local-variable 'forms--markers)
|
||
(forms--process-format-list)
|
||
|
||
;; build the formatter and parser
|
||
(make-local-variable 'forms--format)
|
||
(forms--make-format)
|
||
(make-local-variable 'forms--parser)
|
||
(forms--make-parser)
|
||
|
||
;; check if record filters are defined
|
||
(make-local-variable 'forms--new-record-filter)
|
||
(setq forms--new-record-filter
|
||
(cond
|
||
((fboundp 'forms-new-record-filter)
|
||
(symbol-function 'forms-new-record-filter))
|
||
((and (boundp 'forms-new-record-filter)
|
||
(fboundp forms-new-record-filter))
|
||
forms-new-record-filter)))
|
||
(fmakunbound 'forms-new-record-filter)
|
||
(make-local-variable 'forms--modified-record-filter)
|
||
(setq forms--modified-record-filter
|
||
(cond
|
||
((fboundp 'forms-modified-record-filter)
|
||
(symbol-function 'forms-modified-record-filter))
|
||
((and (boundp 'forms-modified-record-filter)
|
||
(fboundp forms-modified-record-filter))
|
||
forms-modified-record-filter)))
|
||
(fmakunbound 'forms-modified-record-filter)
|
||
|
||
;; dynamic text support
|
||
(make-local-variable 'forms--dynamic-text)
|
||
(make-local-variable 'forms-fields)
|
||
|
||
;; prepare this buffer for further processing
|
||
(setq buffer-read-only nil)
|
||
|
||
;; prevent accidental overwrite of the control file and autosave
|
||
(setq buffer-file-name nil)
|
||
(auto-save-mode nil)
|
||
|
||
;; and clean it
|
||
(erase-buffer)))
|
||
|
||
;; make local variables
|
||
(make-local-variable 'forms--file-buffer)
|
||
(make-local-variable 'forms--total-records)
|
||
(make-local-variable 'forms--current-record)
|
||
(make-local-variable 'forms--the-record-list)
|
||
(make-local-variable 'forms--search-rexexp)
|
||
|
||
;; A bug in the current Emacs release prevents a keymap
|
||
;; which is buffer-local from being used by 'describe-mode'.
|
||
;; Hence we'll leave it global.
|
||
;;(make-local-variable 'forms-mode-map)
|
||
(if forms-mode-map ; already defined
|
||
nil
|
||
(setq forms-mode-map (make-keymap))
|
||
(forms--mode-commands forms-mode-map)
|
||
(forms--change-commands))
|
||
|
||
;; find the data file
|
||
(setq forms--file-buffer (find-file-noselect forms-file))
|
||
|
||
;; count the number of records, and set see if it may be modified
|
||
(let (ro)
|
||
(setq forms--total-records
|
||
(save-excursion
|
||
(set-buffer forms--file-buffer)
|
||
(bury-buffer (current-buffer))
|
||
(setq ro buffer-read-only)
|
||
(count-lines (point-min) (point-max))))
|
||
(if ro
|
||
(setq forms-read-only t)))
|
||
|
||
;; set the major mode indicator
|
||
(setq major-mode 'forms-mode)
|
||
(setq mode-name "Forms")
|
||
(make-local-variable 'minor-mode-alist) ; needed?
|
||
(forms--set-minor-mode)
|
||
(forms--set-keymaps)
|
||
|
||
(set-buffer-modified-p nil)
|
||
|
||
;; We have our own revert function - use it
|
||
(make-local-variable 'revert-buffer-function)
|
||
(setq revert-buffer-function 'forms-revert-buffer)
|
||
|
||
;; setup the first (or current) record to show
|
||
(if (< forms--current-record 1)
|
||
(setq forms--current-record 1))
|
||
(forms-jump-record forms--current-record)
|
||
|
||
;; user customising
|
||
(run-hooks 'forms-mode-hooks)
|
||
|
||
;; be helpful
|
||
(forms--help)
|
||
|
||
;; initialization done
|
||
(setq forms--mode-setup t))
|
||
|
||
;;;
|
||
;;; forms-process-format-list
|
||
;;;
|
||
;;; Validates forms-format-list.
|
||
;;;
|
||
;;; Sets forms--number-of-markers and forms--markers.
|
||
|
||
(defun forms--process-format-list ()
|
||
"Validate forms-format-list and set some global variables."
|
||
|
||
(forms--debug "forms-forms-list before 1st pass:\n"
|
||
'forms-format-list)
|
||
|
||
;; it must be non-nil
|
||
(or forms-format-list
|
||
(error "'forms-format-list' has not been set"))
|
||
;; it must be a list ...
|
||
(or (listp forms-format-list)
|
||
(error "'forms-format-list' is not a list"))
|
||
|
||
(setq forms--number-of-markers 0)
|
||
|
||
(let ((the-list forms-format-list) ; the list of format elements
|
||
(this-item 0) ; element in list
|
||
(field-num 0)) ; highest field number
|
||
|
||
(setq forms-format-list nil) ; gonna rebuild
|
||
|
||
(while the-list
|
||
|
||
(let ((el (car-safe the-list))
|
||
(rem (cdr-safe the-list)))
|
||
|
||
;; if it is a symbol, eval it first
|
||
(if (and (symbolp el)
|
||
(boundp el))
|
||
(setq el (eval el)))
|
||
|
||
(cond
|
||
|
||
;; try string ...
|
||
((stringp el)) ; string is OK
|
||
|
||
;; try numeric ...
|
||
((numberp el)
|
||
|
||
(if (or (<= el 0)
|
||
(> el forms-number-of-fields))
|
||
(error
|
||
"Forms error: field number %d out of range 1..%d"
|
||
el forms-number-of-fields))
|
||
|
||
(setq forms--number-of-markers (1+ forms--number-of-markers))
|
||
(if (> el field-num)
|
||
(setq field-num el)))
|
||
|
||
;; try function
|
||
((listp el)
|
||
(or (fboundp (car-safe el))
|
||
(error
|
||
"Forms error: not a function: %s"
|
||
(prin1-to-string (car-safe el)))))
|
||
|
||
;; else
|
||
(t
|
||
(error "Invalid element in 'forms-format-list': %s"
|
||
(prin1-to-string el))))
|
||
|
||
;; advance to next element of the list
|
||
(setq the-list rem)
|
||
(setq forms-format-list
|
||
(append forms-format-list (list el) nil)))))
|
||
|
||
(forms--debug "forms-forms-list after 1st pass:\n"
|
||
'forms-format-list)
|
||
|
||
;; concat adjacent strings
|
||
(setq forms-format-list (forms--concat-adjacent forms-format-list))
|
||
|
||
(forms--debug "forms-forms-list after 2nd pass:\n"
|
||
'forms-format-list
|
||
'forms--number-of-markers)
|
||
|
||
(setq forms--markers (make-vector forms--number-of-markers nil)))
|
||
|
||
|
||
;;;
|
||
;;; Build the format routine from forms-format-list.
|
||
;;;
|
||
;;; The format routine (forms--format) will look like
|
||
;;;
|
||
;;; (lambda (arg)
|
||
;;; (setq forms--dynamic-text nil)
|
||
;;; ;; "text: "
|
||
;;; (insert "text: ")
|
||
;;; ;; 6
|
||
;;; (aset forms--markers 0 (point-marker))
|
||
;;; (insert (elt arg 5))
|
||
;;; ;; "\nmore text: "
|
||
;;; (insert "\nmore text: ")
|
||
;;; ;; (tocol 40)
|
||
;;; (let ((the-dyntext (tocol 40)))
|
||
;;; (insert the-dyntext)
|
||
;;; (setq forms--dynamic-text (append forms--dynamic-text
|
||
;;; (list the-dyntext))))
|
||
;;; ;; 9
|
||
;;; (aset forms--markers 1 (point-marker))
|
||
;;; (insert (elt arg 8))
|
||
;;;
|
||
;;; ... )
|
||
;;;
|
||
|
||
(defun forms--make-format ()
|
||
"Generate format function for forms"
|
||
(setq forms--format (forms--format-maker forms-format-list))
|
||
(forms--debug 'forms--format))
|
||
|
||
(defun forms--format-maker (the-format-list)
|
||
"Returns the parser function for forms"
|
||
(let ((the-marker 0))
|
||
(` (lambda (arg)
|
||
(setq forms--dynamic-text nil)
|
||
(,@ (apply 'append
|
||
(mapcar 'forms--make-format-elt the-format-list)))))))
|
||
|
||
(defun forms--make-format-elt (el)
|
||
(cond ((stringp el)
|
||
(` ((insert (, el)))))
|
||
((numberp el)
|
||
(prog1
|
||
(` ((aset forms--markers (, the-marker) (point-marker))
|
||
(insert (elt arg (, (1- el))))))
|
||
(setq the-marker (1+ the-marker))))
|
||
((listp el)
|
||
(prog1
|
||
(` ((let ((the-dyntext (, el)))
|
||
(insert the-dyntext)
|
||
(setq forms--dynamic-text (append forms--dynamic-text
|
||
(list the-dyntext)))))
|
||
)))
|
||
))
|
||
|
||
|
||
(defun forms--concat-adjacent (the-list)
|
||
"Concatenate adjacent strings in the-list and return the resulting list"
|
||
(if (consp the-list)
|
||
(let ((the-rest (forms--concat-adjacent (cdr the-list))))
|
||
(if (and (stringp (car the-list)) (stringp (car the-rest)))
|
||
(cons (concat (car the-list) (car the-rest))
|
||
(cdr the-rest))
|
||
(cons (car the-list) the-rest)))
|
||
the-list))
|
||
;;;
|
||
;;; forms--make-parser.
|
||
;;;
|
||
;;; Generate parse routine from forms-format-list.
|
||
;;;
|
||
;;; The parse routine (forms--parser) will look like (give or take
|
||
;;; a few " " .
|
||
;;;
|
||
;;; (lambda nil
|
||
;;; (let (here)
|
||
;;; (goto-char (point-min))
|
||
;;;
|
||
;;; ;; "text: "
|
||
;;; (if (not (looking-at "text: "))
|
||
;;; (error "Parse error: cannot find \"text: \""))
|
||
;;; (forward-char 6) ; past "text: "
|
||
;;;
|
||
;;; ;; 6
|
||
;;; ;; "\nmore text: "
|
||
;;; (setq here (point))
|
||
;;; (if (not (search-forward "\nmore text: " nil t nil))
|
||
;;; (error "Parse error: cannot find \"\\nmore text: \""))
|
||
;;; (aset the-recordv 5 (buffer-substring here (- (point) 12)))
|
||
;;;
|
||
;;; ;; (tocol 40)
|
||
;;; (let ((the-dyntext (car-safe forms--dynamic-text)))
|
||
;;; (if (not (looking-at (regexp-quote the-dyntext)))
|
||
;;; (error "Parse error: not looking at \"%s\"" the-dyntext))
|
||
;;; (forward-char (length the-dyntext))
|
||
;;; (setq forms--dynamic-text (cdr-safe forms--dynamic-text)))
|
||
;;; ...
|
||
;;; ;; final flush (due to terminator sentinel, see below)
|
||
;;; (aset the-recordv 7 (buffer-substring (point) (point-max)))
|
||
;;;
|
||
|
||
(defun forms--make-parser ()
|
||
"Generate parser function for forms"
|
||
(setq forms--parser (forms--parser-maker forms-format-list))
|
||
(forms--debug 'forms--parser))
|
||
|
||
(defun forms--parser-maker (the-format-list)
|
||
"Returns the parser function for forms"
|
||
(let ((the-field nil)
|
||
(seen-text nil)
|
||
the--format-list)
|
||
;; add a terminator sentinel
|
||
(setq the--format-list (append the-format-list (list nil)))
|
||
(` (lambda nil
|
||
(let (here)
|
||
(goto-char (point-min))
|
||
(,@ (apply 'append
|
||
(mapcar 'forms--make-parser-elt the--format-list))))))))
|
||
|
||
(defun forms--make-parser-elt (el)
|
||
(cond
|
||
((stringp el)
|
||
(prog1
|
||
(if the-field
|
||
(` ((setq here (point))
|
||
(if (not (search-forward (, el) nil t nil))
|
||
(error "Parse error: cannot find \"%s\"" (, el)))
|
||
(aset the-recordv (, (1- the-field))
|
||
(buffer-substring here
|
||
(- (point) (, (length el)))))))
|
||
(` ((if (not (looking-at (, (regexp-quote el))))
|
||
(error "Parse error: not looking at \"%s\"" (, el)))
|
||
(forward-char (, (length el))))))
|
||
(setq seen-text t)
|
||
(setq the-field nil)))
|
||
((numberp el)
|
||
(if the-field
|
||
(error "Cannot parse adjacent fields %d and %d"
|
||
the-field el)
|
||
(setq the-field el)
|
||
nil))
|
||
((null el)
|
||
(if the-field
|
||
(` ((aset the-recordv (, (1- the-field))
|
||
(buffer-substring (point) (point-max)))))))
|
||
((listp el)
|
||
(prog1
|
||
(if the-field
|
||
(` ((let ((here (point))
|
||
(the-dyntext (car-safe forms--dynamic-text)))
|
||
(if (not (search-forward the-dyntext nil t nil))
|
||
(error "Parse error: cannot find \"%s\"" the-dyntext))
|
||
(aset the-recordv (, (1- the-field))
|
||
(buffer-substring here
|
||
(- (point) (length the-dyntext))))
|
||
(setq forms--dynamic-text (cdr-safe forms--dynamic-text)))))
|
||
(` ((let ((the-dyntext (car-safe forms--dynamic-text)))
|
||
(if (not (looking-at (regexp-quote the-dyntext)))
|
||
(error "Parse error: not looking at \"%s\"" the-dyntext))
|
||
(forward-char (length the-dyntext))
|
||
(setq forms--dynamic-text (cdr-safe forms--dynamic-text))))))
|
||
(setq seen-text t)
|
||
(setq the-field nil)))
|
||
))
|
||
;;;
|
||
|
||
(defun forms--set-minor-mode ()
|
||
(setq minor-mode-alist
|
||
(if forms-read-only
|
||
" View"
|
||
nil)))
|
||
|
||
(defun forms--set-keymaps ()
|
||
"Set the keymaps used in this mode."
|
||
|
||
(if forms-read-only
|
||
(use-local-map forms-mode-map)
|
||
(use-local-map (make-sparse-keymap))
|
||
(define-key (current-local-map) "\C-c" forms-mode-map)
|
||
(define-key (current-local-map) "\t" 'forms-next-field)))
|
||
|
||
(defun forms--mode-commands (map)
|
||
"Fill map with all commands."
|
||
(define-key map "\t" 'forms-next-field)
|
||
(define-key map " " 'forms-next-record)
|
||
(define-key map "d" 'forms-delete-record)
|
||
(define-key map "e" 'forms-edit-mode)
|
||
(define-key map "i" 'forms-insert-record)
|
||
(define-key map "j" 'forms-jump-record)
|
||
(define-key map "n" 'forms-next-record)
|
||
(define-key map "p" 'forms-prev-record)
|
||
(define-key map "q" 'forms-exit)
|
||
(define-key map "s" 'forms-search)
|
||
(define-key map "v" 'forms-view-mode)
|
||
(define-key map "x" 'forms-exit-no-save)
|
||
(define-key map "<" 'forms-first-record)
|
||
(define-key map ">" 'forms-last-record)
|
||
(define-key map "?" 'describe-mode)
|
||
(define-key map "\177" 'forms-prev-record)
|
||
; (define-key map "\C-c" map)
|
||
(define-key map "\e" 'ESC-prefix)
|
||
(define-key map "\C-x" ctl-x-map)
|
||
(define-key map "\C-u" 'universal-argument)
|
||
(define-key map "\C-h" help-map)
|
||
)
|
||
;;;
|
||
;;; Changed functions
|
||
;;;
|
||
;;; Emacs (as of 18.55) lacks the functionality of buffer-local
|
||
;;; funtions. Therefore we save the original meaning of some handy
|
||
;;; functions, and replace them with a wrapper.
|
||
|
||
(defun forms--change-commands ()
|
||
"Localize some commands."
|
||
;;
|
||
;; scroll-down -> forms-prev-record
|
||
;;
|
||
(if (fboundp 'forms--scroll-down)
|
||
nil
|
||
(fset 'forms--scroll-down (symbol-function 'scroll-down))
|
||
(fset 'scroll-down
|
||
'(lambda (&optional arg)
|
||
(interactive "P")
|
||
(if (and forms--mode-setup
|
||
forms-forms-scroll)
|
||
(forms-prev-record arg)
|
||
(forms--scroll-down arg)))))
|
||
;;
|
||
;; scroll-up -> forms-next-record
|
||
;;
|
||
(if (fboundp 'forms--scroll-up)
|
||
nil
|
||
(fset 'forms--scroll-up (symbol-function 'scroll-up))
|
||
(fset 'scroll-up
|
||
'(lambda (&optional arg)
|
||
(interactive "P")
|
||
(if (and forms--mode-setup
|
||
forms-forms-scroll)
|
||
(forms-next-record arg)
|
||
(forms--scroll-up arg)))))
|
||
;;
|
||
;; beginning-of-buffer -> forms-first-record
|
||
;;
|
||
(if (fboundp 'forms--beginning-of-buffer)
|
||
nil
|
||
(fset 'forms--beginning-of-buffer (symbol-function 'beginning-of-buffer))
|
||
(fset 'beginning-of-buffer
|
||
'(lambda ()
|
||
(interactive)
|
||
(if (and forms--mode-setup
|
||
forms-forms-jump)
|
||
(forms-first-record)
|
||
(forms--beginning-of-buffer)))))
|
||
;;
|
||
;; end-of-buffer -> forms-end-record
|
||
;;
|
||
(if (fboundp 'forms--end-of-buffer)
|
||
nil
|
||
(fset 'forms--end-of-buffer (symbol-function 'end-of-buffer))
|
||
(fset 'end-of-buffer
|
||
'(lambda ()
|
||
(interactive)
|
||
(if (and forms--mode-setup
|
||
forms-forms-jump)
|
||
(forms-last-record)
|
||
(forms--end-of-buffer)))))
|
||
;;
|
||
;; save-buffer -> forms--save-buffer
|
||
;;
|
||
(if (fboundp 'forms--save-buffer)
|
||
nil
|
||
(fset 'forms--save-buffer (symbol-function 'save-buffer))
|
||
(fset 'save-buffer
|
||
'(lambda (&optional arg)
|
||
(interactive "p")
|
||
(if forms--mode-setup
|
||
(progn
|
||
(forms--checkmod)
|
||
(save-excursion
|
||
(set-buffer forms--file-buffer)
|
||
(forms--save-buffer arg)))
|
||
(forms--save-buffer arg)))))
|
||
;;
|
||
)
|
||
|
||
(defun forms--help ()
|
||
"Initial help."
|
||
;; We should use
|
||
;;(message (substitute-command-keys (concat
|
||
;;"\\[forms-next-record]:next"
|
||
;;" \\[forms-prev-record]:prev"
|
||
;;" \\[forms-first-record]:first"
|
||
;;" \\[forms-last-record]:last"
|
||
;;" \\[describe-mode]:help"
|
||
;;" \\[forms-exit]:exit")))
|
||
;; but it's too slow ....
|
||
(if forms-read-only
|
||
(message "SPC:next DEL:prev <:first >:last ?:help q:exit")
|
||
(message "C-c n:next C-c p:prev C-c <:first C-c >:last C-c ?:help C-c q:exit")))
|
||
|
||
(defun forms--trans (subj arg rep)
|
||
"Translate in SUBJ all chars ARG into char REP. ARG and REP should
|
||
be single-char strings."
|
||
(let ((i 0)
|
||
(x (length subj))
|
||
(re (regexp-quote arg))
|
||
(k (string-to-char rep)))
|
||
(while (setq i (string-match re subj i))
|
||
(aset subj i k)
|
||
(setq i (1+ i)))))
|
||
|
||
(defun forms--exit (query &optional save)
|
||
(let ((buf (buffer-name forms--file-buffer)))
|
||
(forms--checkmod)
|
||
(if (and save
|
||
(buffer-modified-p forms--file-buffer))
|
||
(save-excursion
|
||
(set-buffer forms--file-buffer)
|
||
(save-buffer)))
|
||
(save-excursion
|
||
(set-buffer forms--file-buffer)
|
||
(delete-auto-save-file-if-necessary)
|
||
(kill-buffer (current-buffer)))
|
||
(if (get-buffer buf) ; not killed???
|
||
(if save
|
||
(progn
|
||
(beep)
|
||
(message "Problem saving buffers?")))
|
||
(delete-auto-save-file-if-necessary)
|
||
(kill-buffer (current-buffer)))))
|
||
|
||
(defun forms--get-record ()
|
||
"Fetch the current record from the file buffer."
|
||
;;
|
||
;; This function is executed in the context of the forms--file-buffer.
|
||
;;
|
||
(or (bolp)
|
||
(beginning-of-line nil))
|
||
(let ((here (point)))
|
||
(prog2
|
||
(end-of-line)
|
||
(buffer-substring here (point))
|
||
(goto-char here))))
|
||
|
||
(defun forms--show-record (the-record)
|
||
"Format THE-RECORD according to forms-format-list,
|
||
and display it in the current buffer."
|
||
|
||
;; split the-record
|
||
(let (the-result
|
||
(start-pos 0)
|
||
found-pos
|
||
(field-sep-length (length forms-field-sep)))
|
||
(if forms-multi-line
|
||
(forms--trans the-record forms-multi-line "\n"))
|
||
;; add an extra separator (makes splitting easy)
|
||
(setq the-record (concat the-record forms-field-sep))
|
||
(while (setq found-pos (string-match forms-field-sep the-record start-pos))
|
||
(let ((ent (substring the-record start-pos found-pos)))
|
||
(setq the-result
|
||
(append the-result (list ent)))
|
||
(setq start-pos (+ field-sep-length found-pos))))
|
||
(setq forms--the-record-list the-result))
|
||
|
||
(setq buffer-read-only nil)
|
||
(erase-buffer)
|
||
|
||
;; verify the number of fields, extend forms--the-record-list if needed
|
||
(if (= (length forms--the-record-list) forms-number-of-fields)
|
||
nil
|
||
(beep)
|
||
(message "Record has %d fields instead of %d."
|
||
(length forms--the-record-list) forms-number-of-fields)
|
||
(if (< (length forms--the-record-list) forms-number-of-fields)
|
||
(setq forms--the-record-list
|
||
(append forms--the-record-list
|
||
(make-list
|
||
(- forms-number-of-fields
|
||
(length forms--the-record-list))
|
||
"")))))
|
||
|
||
;; call the formatter function
|
||
(setq forms-fields (append (list nil) forms--the-record-list nil))
|
||
(funcall forms--format forms--the-record-list)
|
||
|
||
;; prepare
|
||
(goto-char (point-min))
|
||
(set-buffer-modified-p nil)
|
||
(setq buffer-read-only forms-read-only)
|
||
(setq mode-line-process
|
||
(concat " " forms--current-record "/" forms--total-records)))
|
||
|
||
(defun forms--parse-form ()
|
||
"Parse contents of form into list of strings."
|
||
;; The contents of the form are parsed, and a new list of strings
|
||
;; is constructed.
|
||
;; A vector with the strings from the original record is
|
||
;; constructed, which is updated with the new contents. Therefore
|
||
;; fields which were not in the form are not modified.
|
||
;; Finally, the vector is transformed into a list for further processing.
|
||
|
||
(let (the-recordv)
|
||
|
||
;; build the vector
|
||
(setq the-recordv (vconcat forms--the-record-list))
|
||
|
||
;; parse the form and update the vector
|
||
(let ((forms--dynamic-text forms--dynamic-text))
|
||
(funcall forms--parser))
|
||
|
||
(if forms--modified-record-filter
|
||
;; As a service to the user, we add a zeroth element so she
|
||
;; can use the same indices as in the forms definition.
|
||
(let ((the-fields (vconcat [nil] the-recordv)))
|
||
(setq the-fields (funcall forms--modified-record-filter the-fields))
|
||
(cdr (append the-fields nil)))
|
||
|
||
;; transform to a list and return
|
||
(append the-recordv nil))))
|
||
|
||
(defun forms--update ()
|
||
"Update current record with contents of form. As a side effect: sets
|
||
forms--the-record-list ."
|
||
(if forms-read-only
|
||
(progn
|
||
(message "Read-only buffer!")
|
||
(beep))
|
||
|
||
(let (the-record)
|
||
;; build new record
|
||
(setq forms--the-record-list (forms--parse-form))
|
||
(setq the-record
|
||
(mapconcat 'identity forms--the-record-list forms-field-sep))
|
||
|
||
;; handle multi-line fields, if allowed
|
||
(if forms-multi-line
|
||
(forms--trans the-record "\n" forms-multi-line))
|
||
|
||
;; a final sanity check before updating
|
||
(if (string-match "\n" the-record)
|
||
(progn
|
||
(message "Multi-line fields in this record - update refused!")
|
||
(beep))
|
||
|
||
(save-excursion
|
||
(set-buffer forms--file-buffer)
|
||
;; Insert something before kill-line is called. See kill-line
|
||
;; doc. Bugfix provided by Ignatios Souvatzis.
|
||
(insert "*")
|
||
(beginning-of-line)
|
||
(kill-line nil)
|
||
(insert the-record)
|
||
(beginning-of-line))))))
|
||
|
||
(defun forms--checkmod ()
|
||
"Check if this form has been modified, and call forms--update if so."
|
||
(if (buffer-modified-p nil)
|
||
(let ((here (point)))
|
||
(forms--update)
|
||
(set-buffer-modified-p nil)
|
||
(goto-char here))))
|
||
|
||
;;;
|
||
;;; Start and exit
|
||
(defun forms-find-file (fn)
|
||
"Visit file FN in forms mode"
|
||
(interactive "fForms file: ")
|
||
(find-file-read-only fn)
|
||
(or forms--mode-setup (forms-mode t)))
|
||
|
||
(defun forms-find-file-other-window (fn)
|
||
"Visit file FN in form mode in other window"
|
||
(interactive "fFbrowse file in other window: ")
|
||
(find-file-other-window fn)
|
||
(eval-current-buffer)
|
||
(or forms--mode-setup (forms-mode t)))
|
||
|
||
(defun forms-exit (query)
|
||
"Normal exit. Modified buffers are saved."
|
||
(interactive "P")
|
||
(forms--exit query t))
|
||
|
||
(defun forms-exit-no-save (query)
|
||
"Exit without saving buffers."
|
||
(interactive "P")
|
||
(forms--exit query nil))
|
||
|
||
;;;
|
||
;;; Navigating commands
|
||
|
||
(defun forms-next-record (arg)
|
||
"Advance to the ARGth following record."
|
||
(interactive "P")
|
||
(forms-jump-record (+ forms--current-record (prefix-numeric-value arg)) t))
|
||
|
||
(defun forms-prev-record (arg)
|
||
"Advance to the ARGth previous record."
|
||
(interactive "P")
|
||
(forms-jump-record (- forms--current-record (prefix-numeric-value arg)) t))
|
||
|
||
(defun forms-jump-record (arg &optional relative)
|
||
"Jump to a random record."
|
||
(interactive "NRecord number: ")
|
||
|
||
;; verify that the record number is within range
|
||
(if (or (> arg forms--total-records)
|
||
(<= arg 0))
|
||
(progn
|
||
(beep)
|
||
;; don't give the message if just paging
|
||
(if (not relative)
|
||
(message "Record number %d out of range 1..%d"
|
||
arg forms--total-records))
|
||
)
|
||
|
||
;; flush
|
||
(forms--checkmod)
|
||
|
||
;; calculate displacement
|
||
(let ((disp (- arg forms--current-record))
|
||
(cur forms--current-record))
|
||
|
||
;; forms--show-record needs it now
|
||
(setq forms--current-record arg)
|
||
|
||
;; get the record and show it
|
||
(forms--show-record
|
||
(save-excursion
|
||
(set-buffer forms--file-buffer)
|
||
(beginning-of-line)
|
||
|
||
;; move, and adjust the amount if needed (shouldn't happen)
|
||
(if relative
|
||
(if (zerop disp)
|
||
nil
|
||
(setq cur (+ cur disp (- (forward-line disp)))))
|
||
(setq cur (+ cur disp (- (goto-line arg)))))
|
||
|
||
(forms--get-record)))
|
||
|
||
;; this shouldn't happen
|
||
(if (/= forms--current-record cur)
|
||
(progn
|
||
(setq forms--current-record cur)
|
||
(beep)
|
||
(message "Stuck at record %d." cur))))))
|
||
|
||
(defun forms-first-record ()
|
||
"Jump to first record."
|
||
(interactive)
|
||
(forms-jump-record 1))
|
||
|
||
(defun forms-last-record ()
|
||
"Jump to last record. As a side effect: re-calculates the number
|
||
of records in the data file."
|
||
(interactive)
|
||
(let
|
||
((numrec
|
||
(save-excursion
|
||
(set-buffer forms--file-buffer)
|
||
(count-lines (point-min) (point-max)))))
|
||
(if (= numrec forms--total-records)
|
||
nil
|
||
(beep)
|
||
(setq forms--total-records numrec)
|
||
(message "Number of records reset to %d." forms--total-records)))
|
||
(forms-jump-record forms--total-records))
|
||
|
||
;;;
|
||
;;; Other commands
|
||
(defun forms-view-mode ()
|
||
"Visit buffer read-only."
|
||
(interactive)
|
||
(if forms-read-only
|
||
nil
|
||
(forms--checkmod) ; sync
|
||
(setq forms-read-only t)
|
||
(forms-mode)))
|
||
|
||
(defun forms-edit-mode ()
|
||
"Make form suitable for editing, if possible."
|
||
(interactive)
|
||
(let ((ro forms-read-only))
|
||
(if (save-excursion
|
||
(set-buffer forms--file-buffer)
|
||
buffer-read-only)
|
||
(progn
|
||
(setq forms-read-only t)
|
||
(message "No write access to \"%s\"" forms-file)
|
||
(beep))
|
||
(setq forms-read-only nil))
|
||
(if (equal ro forms-read-only)
|
||
nil
|
||
(forms-mode))))
|
||
|
||
;; Sample:
|
||
;; (defun my-new-record-filter (the-fields)
|
||
;; ;; numbers are relative to 1
|
||
;; (aset the-fields 4 (current-time-string))
|
||
;; (aset the-fields 6 (user-login-name))
|
||
;; the-list)
|
||
;; (setq forms-new-record-filter 'my-new-record-filter)
|
||
|
||
(defun forms-insert-record (arg)
|
||
"Create a new record before the current one. With ARG: store the
|
||
record after the current one.
|
||
If a function forms-new-record-filter is defined, or forms-new-record-filter
|
||
contains the name of a function, it is called to
|
||
fill (some of) the fields with default values."
|
||
; The above doc is not true, but for documentary purposes only
|
||
|
||
(interactive "P")
|
||
|
||
(let ((ln (if arg (1+ forms--current-record) forms--current-record))
|
||
the-list the-record)
|
||
|
||
(forms--checkmod)
|
||
(if forms--new-record-filter
|
||
;; As a service to the user, we add a zeroth element so she
|
||
;; can use the same indices as in the forms definition.
|
||
(let ((the-fields (make-vector (1+ forms-number-of-fields) "")))
|
||
(setq the-fields (funcall forms--new-record-filter the-fields))
|
||
(setq the-list (cdr (append the-fields nil))))
|
||
(setq the-list (make-list forms-number-of-fields "")))
|
||
|
||
(setq the-record
|
||
(mapconcat
|
||
'identity
|
||
the-list
|
||
forms-field-sep))
|
||
|
||
(save-excursion
|
||
(set-buffer forms--file-buffer)
|
||
(goto-line ln)
|
||
(open-line 1)
|
||
(insert the-record)
|
||
(beginning-of-line))
|
||
|
||
(setq forms--current-record ln))
|
||
|
||
(setq forms--total-records (1+ forms--total-records))
|
||
(forms-jump-record forms--current-record))
|
||
|
||
(defun forms-delete-record (arg)
|
||
"Deletes a record. With ARG: don't ask."
|
||
(interactive "P")
|
||
(forms--checkmod)
|
||
(if (or arg
|
||
(y-or-n-p "Really delete this record? "))
|
||
(let ((ln forms--current-record))
|
||
(save-excursion
|
||
(set-buffer forms--file-buffer)
|
||
(goto-line ln)
|
||
(kill-line 1))
|
||
(setq forms--total-records (1- forms--total-records))
|
||
(if (> forms--current-record forms--total-records)
|
||
(setq forms--current-record forms--total-records))
|
||
(forms-jump-record forms--current-record)))
|
||
(message ""))
|
||
|
||
(defun forms-search (regexp)
|
||
"Search REGEXP in file buffer."
|
||
(interactive
|
||
(list (read-string (concat "Search for"
|
||
(if forms--search-regexp
|
||
(concat " ("
|
||
forms--search-regexp
|
||
")"))
|
||
": "))))
|
||
(if (equal "" regexp)
|
||
(setq regexp forms--search-regexp))
|
||
(forms--checkmod)
|
||
|
||
(let (the-line the-record here
|
||
(fld-sep forms-field-sep))
|
||
(if (save-excursion
|
||
(set-buffer forms--file-buffer)
|
||
(setq here (point))
|
||
(end-of-line)
|
||
(if (null (re-search-forward regexp nil t))
|
||
(progn
|
||
(goto-char here)
|
||
(message (concat "\"" regexp "\" not found."))
|
||
nil)
|
||
(setq the-record (forms--get-record))
|
||
(setq the-line (1+ (count-lines (point-min) (point))))))
|
||
(progn
|
||
(setq forms--current-record the-line)
|
||
(forms--show-record the-record)
|
||
(re-search-forward regexp nil t))))
|
||
(setq forms--search-regexp regexp))
|
||
|
||
(defun forms-revert-buffer (&optional arg noconfirm)
|
||
"Reverts current form to un-modified."
|
||
(interactive "P")
|
||
(if (or noconfirm
|
||
(yes-or-no-p "Revert form to unmodified? "))
|
||
(progn
|
||
(set-buffer-modified-p nil)
|
||
(forms-jump-record forms--current-record))))
|
||
|
||
(defun forms-next-field (arg)
|
||
"Jump to ARG-th next field."
|
||
(interactive "p")
|
||
|
||
(let ((i 0)
|
||
(here (point))
|
||
there
|
||
(cnt 0))
|
||
|
||
(if (zerop arg)
|
||
(setq cnt 1)
|
||
(setq cnt (+ cnt arg)))
|
||
|
||
(if (catch 'done
|
||
(while (< i forms--number-of-markers)
|
||
(if (or (null (setq there (aref forms--markers i)))
|
||
(<= there here))
|
||
nil
|
||
(if (<= (setq cnt (1- cnt)) 0)
|
||
(progn
|
||
(goto-char there)
|
||
(throw 'done t))))
|
||
(setq i (1+ i))))
|
||
nil
|
||
(goto-char (aref forms--markers 0)))))
|
||
|
||
;;;
|
||
;;; Special service
|
||
;;;
|
||
(defun forms-enumerate (the-fields)
|
||
"Take a quoted list of symbols, and set their values to the numbers
|
||
1, 2 and so on. Returns the higest number.
|
||
|
||
Usage: (setq forms-number-of-fields
|
||
(forms-enumerate
|
||
'(field1 field2 field2 ...)))"
|
||
|
||
(let ((the-index 0))
|
||
(while the-fields
|
||
(setq the-index (1+ the-index))
|
||
(let ((el (car-safe the-fields)))
|
||
(setq the-fields (cdr-safe the-fields))
|
||
(set el the-index)))
|
||
the-index))
|
||
|
||
;;;
|
||
;;; Debugging
|
||
;;;
|
||
(defvar forms--debug nil
|
||
"*Enables forms-mode debugging if not nil.")
|
||
|
||
(defun forms--debug (&rest args)
|
||
"Internal - debugging routine"
|
||
(if forms--debug
|
||
(let ((ret nil))
|
||
(while args
|
||
(let ((el (car-safe args)))
|
||
(setq args (cdr-safe args))
|
||
(if (stringp el)
|
||
(setq ret (concat ret el))
|
||
(setq ret (concat ret (prin1-to-string el) " = "))
|
||
(if (boundp el)
|
||
(let ((vel (eval el)))
|
||
(setq ret (concat ret (prin1-to-string vel) "\n")))
|
||
(setq ret (concat ret "<unbound>" "\n")))
|
||
(if (fboundp el)
|
||
(setq ret (concat ret (prin1-to-string (symbol-function el))
|
||
"\n"))))))
|
||
(save-excursion
|
||
(set-buffer (get-buffer-create "*forms-mode debug*"))
|
||
(goto-char (point-max))
|
||
(insert ret)))))
|
||
|
||
;;; Local Variables:
|
||
;;; eval: (headers)
|
||
;;; eval: (setq comment-start ";;; ")
|
||
;;; End:
|