Initial revision
authorChris Hanson <org/chris-hanson/cph>
Mon, 28 Feb 2000 22:50:55 +0000 (22:50 +0000)
committerChris Hanson <org/chris-hanson/cph>
Mon, 28 Feb 2000 22:50:55 +0000 (22:50 +0000)
v7/src/edwin/abbrev.scm [new file with mode: 0644]

diff --git a/v7/src/edwin/abbrev.scm b/v7/src/edwin/abbrev.scm
new file mode 100644 (file)
index 0000000..e5c0c06
--- /dev/null
@@ -0,0 +1,627 @@
+;;; -*-Scheme-*-
+;;;
+;;; $Id: abbrev.scm,v 1.1 2000/02/28 22:50:55 cph Exp $
+;;;
+;;; Copyright (c) 2000 Massachusetts Institute of Technology
+;;;
+;;; This program is free software; you can redistribute it and/or
+;;; modify it under the terms of the GNU General Public License as
+;;; published by the Free Software Foundation; either version 2 of the
+;;; License, or (at your option) any later version.
+;;;
+;;; This program is distributed in the hope that it will be useful,
+;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+;;; General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with this program; if not, write to the Free Software
+;;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+;;;; Abbrev Mode
+
+(declare (usual-integrations))
+\f
+;;;; Low-level data structures
+
+(define make-abbrev-table make-string-hash-table)
+(define abbrev-table? hash-table?)
+
+(define-structure (abbrev-entry (type-descriptor abbrev-entry-rtd))
+  (expansion #f read-only #t)
+  (hook #f read-only #t)
+  (count 0))
+
+(define (clear-abbrev-table table)
+  (set! abbrevs-changed? #t)
+  (hash-table/clear! table))
+
+(define (define-abbrev table abbrev expansion #!optional hook count)
+  (if (not (abbrev-table? table))
+      (error:wrong-type-argument table "abbrev table" 'DEFINE-ABBREV))
+  (if (not (string? abbrev))
+      (error:wrong-type-argument abbrev "string" 'DEFINE-ABBREV))
+  (if (not (string? expansion))
+      (error:wrong-type-argument expansion "string" 'DEFINE-ABBREV))
+  (if (not (or (default-object? hook) (not hook) (symbol? hook)))
+      (error:wrong-type-argument hook "symbol" 'DEFINE-ABBREV))
+  (if (not (or (default-object? count) (exact-nonnegative-integer? count)))
+      (error:wrong-type-argument count
+                                "exact non-negative integer"
+                                'DEFINE-ABBREV))
+  (set! abbrevs-changed? #t)
+  (hash-table/put! table
+                  (string-downcase abbrev)
+                  (make-abbrev-entry
+                   expansion
+                   (if (default-object? hook) #f hook)
+                   (if (default-object? count) 0 count))))
+
+(define (define-global-abbrev abbrev expansion)
+  (define-abbrev (ref-variable global-abbrev-table #f) abbrev expansion))
+
+(define (define-mode-abbrev abbrev expansion)
+  (let ((table (ref-variable local-abbrev-table)))
+    (if (not table)
+       (error "Major mode has no abbrev table."))
+    (define-abbrev table abbrev expansion)))
+
+(define (undefine-abbrev table abbrev)
+  (if (not (abbrev-table? table))
+      (error:wrong-type-argument table "abbrev table" 'UNDEFINE-ABBREV))
+  (if (not (string? abbrev))
+      (error:wrong-type-argument abbrev "string" 'UNDEFINE-ABBREV))
+  (set! abbrevs-changed? #t)
+  (hash-table/remove! table (string-downcase abbrev)))
+
+(define (abbrev-entry abbrev where)
+  (let ((abbrev
+        (string-downcase
+         (cond ((string? abbrev) abbrev)
+               ((symbol? abbrev) (symbol->string abbrev))
+               (else
+                (error:wrong-type-argument abbrev "string"
+                                           'ABBREV-EXPANSION))))))
+    (if (abbrev-table? where)
+       (hash-table/get where abbrev #f)
+       (let ((buffer (if (not where) (selected-buffer) where)))
+         (or (let ((table (ref-variable local-abbrev-table buffer)))
+               (and table
+                    (hash-table/get table abbrev #f)))
+             (hash-table/get (ref-variable global-abbrev-table #f)
+                             abbrev
+                             #f))))))
+
+(define (abbrev-expansion abbrev where)
+  (let ((entry (abbrev-entry abbrev where)))
+    (and entry
+        (abbrev-entry-expansion entry))))
+\f
+(define (define-abbrev-table name definitions)
+  (let ((table (make-abbrev-table)))
+    (for-each
+     (lambda (definition)
+       (if (not (and (list? definition)
+                    (= 4 (length definition))
+                    (string? (car definition))
+                    (string? (cadr definition))
+                    (symbol? (caddr definition))
+                    (exact-nonnegative-integer? (cadddr definition))))
+          (error "Malformed abbrev definition:" definition))
+       (define-abbrev table
+        (car definition)
+        (cadr definition)
+        (if (eq? 'NIL (caddr definition)) #f (caddr definition))
+        (cadddr definition)))
+     definitions)
+    (set-variable-default-value! (name->variable name 'INTERN) table)
+    (let ((names (ref-variable abbrev-table-name-list #f)))
+      (if (not (memq name names))
+         (set-variable! abbrev-table-name-list (cons name names) #f)))))
+
+(define (get-named-abbrev-table name)
+  (let ((table (variable-default-value (name->variable name 'ERROR))))
+    (if (not (abbrev-table? table))
+       (error:bad-range-argument name 'GET-NAMED-ABBREV-TABLE))
+    table))
+\f
+;;;; Variables
+
+(define-variable abbrev-table-name-list
+  "List of symbols whose values are abbrev tables."
+  (list 'FUNDAMENTAL-MODE-ABBREV-TABLE 'GLOBAL-ABBREV-TABLE)
+  (lambda (object) (list-of-type? object symbol?)))
+
+(define-variable global-abbrev-table
+  "The abbrev table whose abbrevs affect all buffers.
+Each buffer may also have a local abbrev table.
+If it does, the local table overrides the global one
+for any particular abbrev defined in both."
+  (make-abbrev-table)
+  abbrev-table?)
+
+(define-variable fundamental-mode-abbrev-table
+  "The abbrev table of mode-specific abbrevs for Fundamental Mode."
+  (make-abbrev-table)
+  abbrev-table?)
+
+(define-variable-per-buffer local-abbrev-table
+  "Local (mode-specific) abbrev table of current buffer."
+  #f
+  (lambda (object)
+    (or (not object)
+       (abbrev-table? object))))
+
+(define-variable abbrev-all-caps
+  "Set true means expand multi-word abbrevs all caps if abbrev was so."
+  #f
+  boolean?)
+
+(define-variable save-abbrevs
+  "True means save word abbrevs too when files are saved.
+Loading an abbrev file sets this to #t."
+  #f
+  boolean?)
+
+(define-variable only-global-abbrevs
+  "True means user plans to use global abbrevs only.
+This makes the commands that normally define mode-specific abbrevs
+define global abbrevs instead."
+  #f
+  boolean?)
+
+(define-variable abbrev-file-name
+  "Default name of file to read abbrevs from."
+  (os/abbrev-file-name)
+  string?)
+
+(define-variable pre-abbrev-expand-hook
+  "An event distributor that is invoked prior to expanding an abbrev.
+The events are called with a single argument: the current point marker."
+  (make-event-distributor))
+
+(define abbrevs-changed? #f)
+\f
+;;;; Abbrev definition
+
+(define-command add-mode-abbrev
+  "Define mode-specific abbrev for last word(s) before point.
+Argument is how many words before point form the expansion;
+or zero means the region is the expansion.
+A negative argument means to undefine the specified abbrev.
+Reads the abbreviation in the minibuffer.
+
+Don't use this procedure in a Scheme program; use `define-abbrev' instead."
+  "p"
+  (lambda (n)
+    (add-abbrev (if (ref-variable only-global-abbrevs)
+                   (ref-variable global-abbrev-table #f)
+                   (or (ref-variable local-abbrev-table)
+                       (editor-error "No per-mode abbrev table.")))
+               "Mode"
+               n)))
+
+(define-command add-global-abbrev
+  "Define global (all modes) abbrev for last word(s) before point.
+The prefix argument specifies the number of words before point that form the
+expansion; or zero means the region is the expansion.
+A negative argument means to undefine the specified abbrev.
+This command uses the minibuffer to read the abbreviation.
+
+Don't use this procedure in a Scheme program; use `define-abbrev' instead."
+  "p"
+  (lambda (n)
+    (add-abbrev (ref-variable global-abbrev-table #f) "Global" n)))
+
+(define-command inverse-add-mode-abbrev
+  "Define last word before point as a mode-specific abbrev.
+With prefix argument N, defines the Nth word before point.
+This command uses the minibuffer to read the expansion.
+Expands the abbreviation after defining it."
+  "p"
+  (lambda (n)
+    (inverse-add-abbrev (if (ref-variable only-global-abbrevs)
+                           (ref-variable global-abbrev-table #f)
+                           (or (ref-variable local-abbrev-table)
+                               (editor-error "No per-mode abbrev table.")))
+                       "Mode"
+                       n)))
+
+(define-command inverse-add-global-abbrev
+  "Define last word before point as a global (mode-independent) abbrev.
+With prefix argument N, defines the Nth word before point.
+This command uses the minibuffer to read the expansion.
+Expands the abbreviation after defining it."
+  "p"
+  (lambda (n)
+    (inverse-add-abbrev (ref-variable global-abbrev-table #f)
+                       "Global"
+                       n)))
+\f
+(define (add-abbrev table type n)
+  (let ((expansion
+        (and (>= n 0)
+             (extract-string
+              (if (= n 0)
+                  (current-mark)
+                  (backward-word (current-point) n 'LIMIT))))))
+    (let ((name
+          (prompt-for-string
+           (if expansion
+               (string-append type " abbrev for \"" expansion "\"")
+               (string-append "Undefine " type " abbrev"))
+           #f)))
+      (if expansion
+         (conditionally-define-abbrev table name expansion)
+         (undefine-abbrev table name)))))
+
+(define (inverse-add-abbrev table type n)
+  (let* ((m (backward-word (current-point) n 'LIMIT))
+        (location (forward-word m 1 'LIMIT))
+        (name (extract-string m location))
+        (expansion
+         (prompt-for-string
+          (string-append type " expansion for \"" name "\"")
+          #f)))
+    (if (conditionally-define-abbrev table name expansion)
+       ((ref-command expand-abbrev) location))))
+
+(define (conditionally-define-abbrev table name expansion)
+  (let ((do-it?
+        (let ((expansion (abbrev-expansion name table)))
+          (or (not expansion)
+              (prompt-for-confirmation?
+               (string-append name
+                              " expands to \""
+                              expansion
+                              "\"; redefine"))))))
+    (if do-it?
+       (define-abbrev table name expansion))
+    do-it?))
+\f
+;;;; Abbrev expansion
+
+(define-command expand-abbrev
+  "Expand the abbrev before point, if there is an abbrev there.
+Effective when explicitly called even when `abbrev-mode' is nil.
+Returns the abbrev symbol, if expansion took place."
+  "d"
+  (lambda (mark)
+    (event-distributor/invoke! (ref-variable pre-abbrev-expand-hook) mark)
+    (let* ((start
+           (let ((buffer (selected-buffer)))
+             (let ((start (buffer-get buffer 'ABBREV-START-LOCATION #f)))
+               (if start
+                   (begin
+                     (buffer-remove! buffer 'ABBREV-START-LOCATION)
+                     (if (eqv? #\- (extract-right-char start))
+                         (delete-right-char start))
+                     (mark-temporary! start)
+                     start)
+                   (backward-word mark 1 #f)))))
+          (end
+           (and start
+                (let ((end (forward-word start 1 'LIMIT)))
+                  (if (mark> end mark)
+                      mark
+                      end)))))
+      (and start
+          (mark< start end)
+          (let* ((abbrev (extract-string start end))
+                 (entry (abbrev-entry abbrev #f)))
+            (and entry
+                 (begin
+                   ;; Add an undo boundary, in case we are doing
+                   ;; this for a self-inserting command which has
+                   ;; avoided making one so far.
+                   (undo-boundary! end)
+                   (buffer-put! (selected-buffer)
+                                'LAST-ABBREV
+                                (let ((expansion
+                                       (abbrev-entry-expansion entry)))
+                                  (and expansion
+                                       (vector abbrev expansion start))))
+                   (set-abbrev-entry-count!
+                    entry
+                    (+ (abbrev-entry-count entry) 1))
+                   (let ((expansion (abbrev-entry-expansion entry)))
+                     (if (string? expansion)
+                         (let ((start (mark-right-inserting-copy start))
+                               (end (mark-left-inserting-copy end)))
+                           (let ((r (make-region start end)))
+                             (region-delete! r)
+                             (insert-string expansion start)
+                             (cond ((string-upper-case? abbrev)
+                                    (if (or (ref-variable abbrev-all-caps)
+                                            (= (count-words-region r) 1))
+                                        (upcase-region r)
+                                        (capitalize-region r)))
+                                   ((and (not (string-lower-case? abbrev))
+                                         (let ((m (forward-to-word start #f)))
+                                           (and m
+                                                (mark< m end)
+                                                m)))
+                                    => (lambda (m)
+                                         (upcase-region
+                                          (make-region m (mark1+ m)))))))
+                           (mark-temporary! end)
+                           (mark-temporary! start))))
+                   (let ((hook (abbrev-entry-hook entry)))
+                     (cond ((symbol? hook)
+                            ((eval hook (->environment '(EDWIN)))))
+                           (hook
+                            (error "Abbrev hook must be symbol:" hook))))
+                   abbrev)))))))
+\f
+(define-command unexpand-abbrev
+  "Undo the expansion of the last abbrev that expanded.
+This differs from ordinary undo in that other editing done since then
+is not undone."
+  ()
+  (lambda ()
+    (let ((last (buffer-get (selected-buffer) 'LAST-ABBREV #f)))
+      (if last
+         (let ((abbrev (vector-ref last 0))
+               (expansion (vector-ref last 1))
+               (start (vector-ref last 2)))
+           (let ((end (mark+ start (string-length expansion))))
+             (if (not (string-ci=? (extract-string start end) expansion))
+                 (editor-error "Can't expand abbrev; contents changed."))
+             (delete-string start end)
+             (insert-string abbrev start))
+           (buffer-remove! (selected-buffer) 'LAST-ABBREV))))))
+
+(define-command abbrev-prefix-mark
+  "Mark current point as the beginning of an abbrev.
+Abbrev to be expanded starts here rather than at beginning of word.
+This way, you can expand an abbrev with a prefix: insert the prefix,
+use this command, then insert the abbrev."
+  "P"
+  (lambda (argument)
+    (if (not argument)
+       ((ref-command expand-abbrev) (current-point)))
+    (buffer-put! (selected-buffer)
+                'ABBREV-START-LOCATION
+                (mark-right-inserting-copy (current-point)))
+    (insert-string "-")))
+
+(define-command expand-region-abbrevs
+  "For abbrev occurrence in the region, offer to expand it.
+The user is asked to type y or n for each occurrence.
+A prefix argument means don't query; expand all abbrevs."
+  "r\nP"
+  (lambda (region no-query?)
+    (let ((end (region-end region)))
+      (let loop ((start (region-start region)))
+       (let ((ws (forward-to-word start #f)))
+         (if (and ws (mark< ws end))
+             (let ((we (forward-word ws 1 'LIMIT)))
+               (if (and (mark<= we end)
+                        (let ((word (extract-string ws we)))
+                          (and (abbrev-expansion word #f)
+                               (or no-query?
+                                   (prompt-for-confirmation?
+                                    (string-append "Expand \"" word "\""))))))
+                   (let ((we (mark-left-inserting-copy we)))
+                     ((ref-command expand-abbrev) we)
+                     (mark-temporary! we)
+                     (loop we))
+                   (loop we)))))))))
+
+(define-minor-mode abbrev "Abbrev"
+  "Minor mode in which abbrevs are expanded.")
+
+(define-command abbrev-mode
+  "Toggle abbrev mode.
+With argument ARG, turn abbrev mode on iff ARG is positive.
+In abbrev mode, inserting an abbreviation causes it to expand
+and be replaced by its expansion."
+  "P"
+  (lambda (argument)
+    (let ((mode (ref-mode-object abbrev)))
+      (if (if argument
+             (> (command-argument-value argument) 0)
+             (not (current-minor-mode? mode)))
+         (enable-current-minor-mode! mode)
+         (disable-current-minor-mode! mode)))))
+\f
+;;;; Editing abbrevs
+
+(define-command edit-abbrevs
+  "Alter abbrev definitions by editing a list of them.
+Selects a buffer containing a list of abbrev definitions.
+You can edit them and type \\<edit-abbrevs>\\[edit-abbrevs-redefine] to redefine abbrevs
+according to your editing.
+Buffer contains a header line for each abbrev table,
+ which is the abbrev table name in parentheses.
+This is followed by one line per abbrev in that table:
+NAME   USECOUNT   EXPANSION   HOOK
+where NAME and EXPANSION are strings with quotes,
+USECOUNT is an integer, and HOOK is any valid function
+or may be omitted (it is usually omitted)."
+  ()
+  (lambda () (select-buffer (prepare-abbrev-list-buffer))))
+
+(define-command list-abbrevs
+  "Display a list of all defined abbrevs."
+  ()
+  (lambda () (pop-up-buffer (prepare-abbrev-list-buffer) #f #f)))
+
+(define (prepare-abbrev-list-buffer)
+  (let ((buffer (find-or-create-buffer "*Abbrevs*")))
+    (buffer-reset! buffer)
+    (insert-abbrev-table-descriptions)
+    (buffer-not-modified! buffer)
+    (set-buffer-point! buffer (buffer-start buffer))
+    (set-buffer-major-mode! buffer (ref-mode-object edit-abbrevs))
+    buffer))
+
+(define-command insert-abbrevs
+  "Insert after point a description of all defined abbrevs.
+Mark is set after the inserted text."
+  ()
+  (lambda ()
+    (insert-abbrev-table-descriptions)
+    (set-current-mark! (current-point))))
+
+(define (insert-abbrev-table-descriptions)
+  (for-each
+   (lambda (name)
+     (let ((table (get-named-abbrev-table name)))
+       (insert-string "(")
+       (insert-string (symbol->string name))
+       (insert-string ")\n\n")
+       (hash-table/for-each table
+        (lambda (abbrev entry)
+          (if (abbrev-entry-expansion entry)
+              (begin
+                (insert-string abbrev)
+                (indent-to 15 1)
+                (insert-string (number->string (abbrev-entry-count entry)))
+                (indent-to 20 1)
+                (insert-string (abbrev-entry-expansion entry))
+                (if (abbrev-entry-hook entry)
+                    (begin
+                      (indent-to 45 1)
+                      (insert-string (abbrev-entry-hook entry))))
+                (insert-newline)))))
+       (insert-string "\n\n")))
+   (ref-variable abbrev-table-name-list #f)))
+\f
+(define-major-mode edit-abbrevs fundamental "Edit-Abbrevs"
+  "Major mode for editing the list of abbrev definitions."
+  (lambda (buffer)
+    buffer
+    unspecific))
+
+(define-key 'edit-abbrevs '(#\C-x #\C-s) 'edit-abbrevs-redefine)
+(define-key 'edit-abbrevs '(#\C-x #\C-c) 'edit-abbrevs-redefine)
+
+(define-command edit-abbrevs-redefine
+  "Redefine abbrevs according to current buffer contents."
+  ()
+  (lambda ()
+    ((ref-command define-abbrevs) #t)
+    (buffer-not-modified! (selected-buffer))))
+
+(define-command define-abbrevs
+  "Define abbrevs according to current visible buffer contents.
+See documentation of \\[edit-abbrevs] for info on the format of the
+text you must have in the buffer.
+With argument, eliminate all abbrev definitions except
+the ones defined from the buffer now."
+  "P"
+  (lambda (argument)
+    (if argument ((ref-command kill-all-abbrevs)))
+    (let ((buffer (selected-buffer)))
+      (let read-tables ((start (buffer-start buffer)))
+       (let ((m (re-search-forward "^(" start)))
+         (define (read-expr)
+           (with-input-from-mark m read
+             (lambda (expr m*)
+               (set! m m*)
+               expr)))
+         (if m
+             (let ((name (read-expr)))
+               (set! m (forward-line m 1 'LIMIT))
+               (define-abbrev-table name
+                 (let loop ()
+                   (set! m (forward-line m 1 'LIMIT))
+                   (if (line-end? m)
+                       '()
+                       (let* ((abbrev (read-expr))
+                              (count (read-expr))
+                              (expansion (read-expr)))
+                         (set! m (skip-chars-backward " \t\n\f" m))
+                         (let ((hook
+                                (and (not (line-end? m))
+                                     (read-expr))))
+                           (set! m (skip-chars-backward " \t\n\f" m))
+                           (cons (list abbrev expansion hook count)
+                                 (loop)))))))
+               (read-tables m))))))))
+
+(define-command kill-all-abbrevs
+  "Undefine all defined abbrevs."
+  ()
+  (lambda ()
+    (for-each (lambda (name)
+               (clear-abbrev-table (get-named-abbrev-table name)))
+             (ref-variable abbrev-table-name-list #f))
+    (set-variable! abbrev-table-name-list '() #f)))
+\f
+;;;; Abbrev-file I/O
+
+(define-command read-abbrev-file
+  "Read abbrev definitions from file written with `write-abbrev-file'.
+Argument FILENAME is the name of the file to read;
+it defaults to the value of `abbrev-file-name'."
+  (lambda ()
+    (list
+     (prompt-for-existing-file "Read abbrev file"
+                              (ref-variable abbrev-file-name #f))))
+  (lambda (filename)
+    (let ((filename (abbrev-file/filename filename)))
+      (message "Loading " filename "...")
+      (quietly-read-abbrev-file filename)
+      (append-message "done"))))
+
+(define (quietly-read-abbrev-file #!optional filename)
+  (let ((filename
+        (abbrev-file/filename
+         (if (default-object? filename) #f filename))))
+    (load-edwin-file filename '(EDWIN) #f)
+    (set-variable! save-abbrevs #t #f)
+    (set! abbrevs-changed? #f)
+    unspecific))
+
+(define-command write-abbrev-file
+  "Write all abbrev definitions to a file of Lisp code.
+The file written can be loaded in another session to define the same abbrevs.
+The argument FILENAME is the file name to write."
+  (lambda ()
+    (list
+     (prompt-for-file "Write abbrev file"
+                     (merge-pathnames (ref-variable abbrev-file-name #f)))))
+  (lambda (filename)
+    (let ((filename (abbrev-file/filename filename)))
+      (call-with-output-file filename
+       (lambda (port)
+         (for-each
+          (lambda (name)
+            (let ((table (get-named-abbrev-table name)))
+              (write-string "(define-abbrev-table '" port)
+              (write name port)
+              (write-string " '(" port)
+              (newline port)
+              (hash-table/for-each table
+                (lambda (abbrev entry)
+                  (if (abbrev-entry-expansion entry)
+                      (begin
+                        (write-string "    " port)
+                        (write (list abbrev
+                                     (abbrev-entry-expansion entry)
+                                     (or (abbrev-entry-hook entry) 'NIL)
+                                     (abbrev-entry-count entry))
+                               port)
+                        (newline port)))))
+              (write-string "    ))" port)
+              (newline port)
+              (newline port)))
+          (ref-variable abbrev-table-name-list #f)))))))
+
+(define (abbrev-file/filename filename)
+  (or filename (ref-variable abbrev-file-name #f)))
+
+(define (maybe-save-abbrevs no-confirmation?)
+  (and (ref-variable save-abbrevs #f)
+       abbrevs-changed?
+       (begin
+        (if (or no-confirmation?
+                (prompt-for-confirmation?
+                 (string-append "Save abbrevs in "
+                                (ref-variable abbrev-file-name #f))))
+            ((ref-command write-abbrev-file) #f))
+        ;; Don't keep bothering user if he says no.
+        (set! abbrevs-changed? #f)
+        #t)))
\ No newline at end of file