; -*-mode:lisp; package:steve; readtable:cl; -*-

;Copyright (c) July 1983 by Christopher Eliot
; and Massachusetts Institute of Technology.
;Permission to copy all or part of this material is granted, provided
; that the copies are not made or distributed for resale, the MIT
; copyright notice and reference to the source file and the software
; distribution version appear, and that notice is given that copying
; is by permission of Massachusetts Institute of Technology.

;New binding scheme.
;There is still one issue to deal with:
; How to store Control-X commands so they may be bound in specific modes,
; and still be properly inherited from superior modes.
;

;;list of all binding tables, used for invalidating them.
(defvar *all-binding-tables* nil)

;;When non-nil any reference to a binding  table will cause them all to
;;be invalidated.
(defvar invalidate-binding-tables-on-reference nil)

(defflavor binding-table (cache bit-vector bit-vector-valid? alist 
			  superior prefix-alist buffer-local?)
  ()
  ;;Cache is a vector referenced by:
  ;; (sgvref cache (coerse-to-9bit-charp char)) => the binding.
  ;;Bit-vector is 1 for every self-inserting character. It should
  ;; be changed to LOGAND self-insertion and graphic-charness.
  ;; Its use is for ECHOIN.
  ;;Bit-vector-valid? is used internally.
  ;;Alist is used internally. It is the alist of local binding definitions.
  ;; These are cached with the inherited definitions in CACHE.
  ;;Superior is the superior binding-table, or NIL.
  ;;Prefix-alist is an alist of Gensym atoms and binding tables.
  ;; A prefix-character binding contains a reference to a gensym atom,
  ;; and this is used to get to the binding table in the alist.
  ;; If the binding-table is not found in the alist the superiors are searched.
  ;; If it is found in a superior then that is the superior of the new
  ;; binding-table. Otherwise the new binding-table has no superior.
  ;; This is complicated, but I think the simplest robust scheme for
  ;; extensible and inheritable prefix-character bindings.
  ;; Notice in particular that 
  ;; (editor-bind-key FOO (internal-editor-lookup-key #\control-x))
  ;; will work, and be inheritable. Also the binding of control-x could
  ;; be deleted or changed (even to be a new prefix-character).
  :ordered-instance-variables
  :initable-instance-variables
  )

(defmethod (binding-table :invalidate) ()
  (loop for i from 0 below 256
	do (setf (sgvref cache i) nil)
	do (setf (sgvref cache (+& i 256)) nil) ;9bit character set.
	do (setf (bit bit-vector i) 0))
  (setq bit-vector-valid? nil))

(defmethod (binding-table :define-binding) (key binding)
  ;;By binding something to nil you may cause it to be unbound
  ;;even when it is still bound in a superior binding table.
  ;;Note that in all cases it is less efficieint to lookup an
  ;;unbound key than a bound one.
  (setq invalidate-binding-tables-on-reference t)
  ;;Shadow superior binding definitions.
  (cond ((assq key alist) (setf (cadr (assq key alist)) binding))
	(t (push (list key binding) alist))))

(defmethod (binding-table :undefine-binding) (key)
  (setq invalidate-binding-tables-on-reference t)
  (delq (assq key alist) alist))

(defmethod (binding-table :lookup-primitive) (key)
  (or (assq key alist)
      (and (not (null superior))
	   (send superior :lookup-primitive key))))

(defmacro check-binding-validity ()
  '(when invalidate-binding-tables-on-reference
     (invalidate-all-binding-tables)))

(defun invalidate-all-binding-tables ()
  (loop for table in *all-binding-tables*
	do (send table :invalidate))
  (setq invalidate-binding-tables-on-reference nil))

(defun coerse-to-9bit-charp (character)
  ;;Return an integer < 512 to represent the character.
  (unless (and (<& (char-code character) 128)
	       (<& (char-bits character) 4)
	       (0p (char-font character)))
    (ed-lose "Invalid character for keybinding ~a" character))
  (+& (char-code character) (*& (char-bits character) 128)))

(defmethod (binding-table :lookup) (key)
  (unless (characterp key)
    (ed-lose "Key not character ~a" key))
  (check-binding-validity)
  (let ((s-key (coerse-to-9bit-charp key)))
    (or (sgvref cache s-key)
	(let ((binding (send self :lookup-primitive key)))
	  (if (not (null binding))
	      (progn (setf (sgvref cache s-key) (cadr binding))
		     (cadr binding)))))))

(defmethod (binding-table :get-echoin-bit-vector) ()
  (unless bit-vector-valid?
    (send self :calculate-bit-vector))
  bit-vector)

(defmethod (binding-table :calculate-bit-vector) ()
  (loop for i from 0 below 256
	for char = (int-char i)
	do (setf (bit bit-vector i)
		 (if (and (graphic-charp char)
			  (not (of-syntax char paren-close))
			  (eq (or (send self :lookup char)
				  (send self :lookup (char-upcase char)))
			      'self-insert))
		     1 0)))
  (setq bit-vector-valid? t))

(defun make-binding-table (&optional (superior nil))
  (let ((table (make-instance 'binding-table
			      :cache (make-vector 512 :initial-element nil)
			      :bit-vector (make-bits 256)
			      :alist nil
			      :superior superior
			      :bit-vector-valid? nil
			      :prefix-alist nil
			      :buffer-local? nil)))
    (push table *all-binding-tables*)
    table))

;Two compatibility functions.
(defun make-empty-key-binding-table ()
  (make-binding-table))

(defun derive-bindings (superior-bindings)
  (make-binding-table superior-bindings))

;
;Key binding.
;

(defmethod (binding-table :get-prefix-table) (char)
  ;;This use of :lookup-primitive is slower at run time, but 
  ;;using :lookup would force every prefix-character sub-command binding
  ;;to invalidate all of the binding-tables. This would be very very
  ;;slow. linking the editor would be painfull.
  ;;There could be a message for :get-prefix-table-for-binding
  ;;or a flag argument. It probably is not worth the trouble either way though,
  ;;since control-x commands are several orders of magnitude less important
  ;;than self-insert. In fact it is guaranteed that terminal i/o will be
  ;;done immediately after this lookup (to get the subcommand dispatch char)
  ;;so we have all the time in the world to find the table.
  (let ((binding (cadr (send self :lookup-primitive char))))
    (and (consp binding)
	 (eq (car binding) 'prefix-character)
	 (or ;;Table already there?
	     (cadr (assq (caddr binding) prefix-alist))
	     ;;Find it and inherit from intermeditate superiors.
	     (send self :get-prefix-table-primitive (caddr binding))
	     (let ((new (make-binding-table)))
	       (push (list (caddr binding) new) prefix-alist)
	       new)))))

(defmethod (binding-table :get-prefix-table-primitive) (tag &aux table)
  (cond ((cadr (assq tag prefix-alist)))
	((null superior) nil)
	((setq table (send superior :get-prefix-table-primitive tag))
	 (setq table (make-binding-table table))
	 (push (list tag table) prefix-alist)
	 table)
	(t nil)))

;Why clutter up the m-X namespace with all these???

(defun internal-bindery (key-seq binding &optional (mode nil))
; (when (symbolp binding)
;   (defcommand (string binding) meta-x-command-table binding))
  (%internal-bindery key-seq binding
		     (if (null mode)
			 *editor-bindings*
			 (mode-keys (lookup-mode mode)))))

(defun %internal-bindery (key-seq binding table)
  (cond ((consp key-seq)
	 (let ((x-table (send table :get-prefix-table (car key-seq))))
	   (when (null x-table)
	     (ed-lose "Bad prefix character: ~a" key-seq))
	   (send x-table :define-binding (cadr key-seq) binding)))
	(t (send table :define-binding key-seq binding)))
  binding)

(defun lookup-key-in-superior-tables (key table)
  (send table :lookup key))

(defun %internal-lookup (key-seq table)
  (if (consp key-seq)
      (let ((x-table (send table :get-prefix-table (car key-seq))))
	(when (null x-table)
	  (ed-lose "Bad prefix-character in ~a" key-seq))
	(lookup-key-in-table (cadr key-seq) x-table))
      (lookup-key-in-table key-seq table)))

(defun internal-editor-lookup-key (key-seq)
  (if (consp key-seq)
      (let ((x-table (send *editor-bindings* :get-prefix-table (car key-seq))))
	(when (null x-table)
	  (ed-lose "Bad prefix-character in ~a" key-seq))
	(lookup-key-in-table (cadr key-seq) x-table))
      (lookup-key-in-table key-seq *editor-bindings*)))

;The prefix-character scheme is rather complex.
; It includes many levels of indirection to implement two principle features.
; (1) The binding definition of a prefix-character can be bound to any
;     other key and it will be the same prefix-character (using the same
;     subtable.) With some restriction this can be done even if the new
;     binding is done in a different mode. (The restriction is a scoping
;     restriction. The new binding must be in the original binding-table, or
;     one of its inferiors.) With this restriction it is unambiguous how
;     the subtable should inherit: it always inherits from some superior,
;     just as the original binding-table would.
; (2) Redefinining the binding of a two character command is always
;     scoped right. An inferior mode definition will shadow a superior
;     definition and a superior definition will be inherited by an inferior
;     binding-table unless it is shadowed. Again this is exactly the behavior
;     of single character binding definitions.
;
;Implementation.
;	The binding definition of a prefix character includes a gensym atom.
;	This identifies the original sub-table for that prefix-character.
;	Using this as a tag the sub-table is found by searching the 
;	current binding-table and its superiors. The instance-variable
;	prefix-alist is used to store the sub-table. If the prefix-table
;	is found in a superior binding-table, then a new sub-table is
;	generated and added to the prefix-alist of the current binding-table.
;	The newly generated sub-table will have the original one as its 
;	ultimate superior, so inheritance and shadowing can occur.
;
;      Binding a sub-command is done by finding or creating the correct
;	sub-table (as described above) and then binding the secondary
;	dispatch character in the sub-table.
;
;      Lookup is done by finding or creating the correct sub-table and
;	looking up the secondary dispatch character in the sub-table.
;

(defun define-prefix-char (char &optional (prompt-string ""))
  (internal-bindery char `(prefix-character
			   ,prompt-string
			   ,(gensym))))

(defun prefix-character (command-char echo-string tag)
 (let ((char)
       (x-table (send *editor-bindings* :get-prefix-table command-char)))
  (or (null *display-prefixes*)
      (type-ahead-p)
      (null echo-string)
      (with-prefix-echo-line
       (send terminal-io :oustr echo-string 0 (string-length echo-string))
       (peek-char&save terminal-io)))
  (setq char (canonicalize-control-characters (read-char&save terminal-io)))
  (values (list command-char char)
	  (or (send x-table :lookup char)
	      (send x-table :lookup (char-upcase char))))))


;
;Local binding
;


(defmethod (binding-table :buffer-local?) (&optional (value buffer-local?))
  (setq buffer-local? value)
  buffer-local?)

(defun bind-key-in-buffer (buffer key-seq binding)
  (let ((binds (buffer-key-bindings buffer)))
    (when (not (send binds :buffer-local?))
      (setq binds (make-binding-table binds))
      (send binds :buffer-local? t)
      (bind-in-buffer buffer '*binding-table* binds))
    (%internal-bindery key-seq binding binds))
  t)

(defun buffer-local-value (buffer variable)
  (let ((env (buffer-environment buffer)))
    (if (null env)
	(values nil nil)
	(loop for var in (load-environment-binding-vars env)
	      for val in (load-environment-new-values env)
	      if (eq var variable) return (values val t)
	      finally (return (values nil nil))))))

(defun buffer-default-major-mode (buffer)
  (let ((temp (buffer-file-name buffer)))
    (or (and temp
	     (setq temp (send temp :type))
	     (default-major-mode temp))
	"Fundamental")))

(defun buffer-key-bindings (buffer)
  (or (buffer-local-value buffer '*editor-bindings*)
      (mode-keys (lookup-mode (buffer-default-major-mode buffer)))))

;Kludging key bindings.

(defmacro with-key-binding (key binding &body forms)
  (let ((table (gensym))
	(old-binding (gensym)))
    `(let ((,table *editor-bindings*))
       (let ((,old-binding (%internal-lookup ,key ,table)))
	 (unwind-protect
	  (progn (%internal-bindery ,key ,binding ,table)
		 ,@forms)
	  (%internal-bindery ,key ,old-binding ,table))))))

(defmacro with-many-key-bindings (alist &body forms)
  (let ((old-bindings (gensym))
	(table (gensym))
	(x (gensym))
	(y (gensym))
	(v-alist (gensym)))
    `(let ((,table *editor-bindings*)
	   (,v-alist ,alist))
       (when (not (listp (car ,v-alist)))
	 (setq ,table (car ,v-alist))
	 (setq ,v-alist (cdr ,v-alist)))
       (let ((,old-bindings
	      (mapcar #'(lambda (,x) (%internal-lookup (car ,x) ,table))
		      ,v-alist)))
	 (unwind-protect
	  (progn (mapc #'(lambda (,x)
			   (%internal-bindery (car ,x) (cadr ,x) ,table))
		       ,v-alist)
		 ,@forms)
	  (mapc #'(lambda (,x ,y)
		    (%internal-bindery (car ,x) ,y ,table))
		,v-alist ,old-bindings))))))

(defmacro with-many-key-bindings-named ((var alist) &body forms)
  ;;This form is very powerfull. It binds several keys temporarilly,
  ;;inside an unwind-protect so they are certain to be rebound on exit.
  ;;However, the VARIABLE given as the first parameter is used to store
  ;;the old key-bindings. They could be changed this way, but this is not
  ;;reccommended and is considered to be incorrect. It can be used as
  ;;an argument to this form so one may temorarrily revert to the old 
  ;;bindings. It has been hacked so it also stores the binding-table to
  ;;use there.
  (let ((old-bindings var)
	(table (gensym))
	(x (gensym))
	(v-alist (gensym)))
    `(let ((,table *editor-bindings*)
	   (,v-alist ,alist))
       (when (not (listp (car ,v-alist)))
	 (setq ,table (car ,v-alist))
	 (setq ,v-alist (cdr ,v-alist)))
       (let ((,old-bindings
	      (cons ,table
		    (mapcar #'(lambda (,x) (list (car ,x)
						 (%internal-lookup
						  (car ,x) ,table)))
			    ,v-alist))))
	 (unwind-protect
	  (progn (mapc #'(lambda (,x)
			   (%internal-bindery (car ,x) (cadr ,x) ,table))
		       ,v-alist)
		 ,@forms)
	  (mapc #'(lambda (,x)
		    (%internal-bindery (car ,x) (cadr ,x) ,table))
		(cdr ,old-bindings)))))))



;
;NEW binding functions.
;These are now the correct way to do these things.
;They provide for proper separation between editor names and lisp names
;and also require documentation strings. (I am facist, I don't make
;documentation optional...But I know the sub-primitives to avoid this, ha ha.)
;

;(define-binding key-seq binding mode-list documentation args)

;(define-extended name binding documentation args)

(defvar function-documentation nil)

(defun %define-extended (name binding documentation args)
  (defcommand (string name) meta-x-command-table binding)
  (setq function-documentation
	(nconc function-documentation
	       (delq (assq (intern name 'steve) function-documentation)
		     function-documentation)
	       (list (list* binding documentation args))))
  name)

(defun %define-binding (key-seq binding m-x-name modes documentation args)
  (mapc #'(lambda (mode)
	    (%internal-bindery key-seq binding
			       (mode-keys (lookup-mode mode))))
	modes)
  (when m-x-name
    (%define-extended m-x-name binding documentation args))
  (or m-x-name binding key-seq))

;User level suger.

(defmacro define-extended (name binding documentation &rest doc-args)
  `(%define-extended name ',binding documentation ',doc-args))

(defmacro define-binding (key-seq binding m-x-name modes documentation
				  &rest args)
  `(%define-binding ,key-seq ',binding ,m-x-name
		    ,(if modes
			 (list 'quote modes)
			 '("Fundamental"))
		    ',documentation ',args))
