;; Minor mode keymap for Emacs

;; Use minor-set-key to set a key in the minor keymap for a given
;; minor mode. 
;;
;; Use unbind-minor-mode to remove all key definitions for a given
;; minor mode.

;; COPYLEFT

;; Created 1987 by Per Abrahamsen at University of Aalborg, Denmark.
;; Updated 1991 by Per Abrahamsen at University of Aalborg, Denmark.
;; This file is not part of GNU emacs (Yet... ;-)

;; Everyone is granted permission to copy, modify and redistribute
;; this file, this includes the right to remove this message.

;; HOW MINOR-MAP WORKS!

;; GNU Emacs minor modes may not change the keymap.  This is why modes
;; like outline-mode and indented-text-mode are major modes.
;; Minor modes could of cause just change the local key binding on entry,
;; and then restore it on exit.  But this will fail, if some other
;; minor mode changes the keybinding meanwhile.
;;
;; The idea behind minor-map is to keep a list of which minor mode has
;; what binding for a particular key.  The list is sorted
;; chronologically, with the most recently enabled minor mode first,
;; and the major mode last.  The key will always be bound according to
;; the head of the list, that is, the last enabled minor mode.  When a
;; minor mode is disabled, it will be removed from the list, and if it
;; was at the head of the list the key will be bound according to the
;; next entry in the list. 
;;
;; When a minor mode is enabled, it should bind all its keys with
;; minor-set-key, and, when disabled, unbound them with
;; unbind-minor-mode. 
;;
;; The minor mode keymaps are contained in a local variable
;; minor-keymap, described in the documentation of make-minor-keymap.

;; RESTRICTIONS

;; The minor maps might fail, if the major mode (or the user) change
;; the local key bindings after the first call to minor-set-key.
;;
;; Minor maps should support shared keymaps for prefix keys.

;; DOES MINOR-MAP WORK?

;; Please report any bug reports (or a formal proof of correctness) to
;; abraham@iesd.auc.dk

(provide 'minor-map)

(make-variable-buffer-local 'minor-keymap) ;Documentation?
(setq-default minor-keymap nil)

(make-variable-buffer-local 'minor-local-keymap)
(setq-default minor-local-keymap nil)

(defun make-minor-keymap ()
  "Construct a keymap for minor modes if none exists.

The keymap is an alist of (KEY . MODE-LIST) where MODE-LIST is a list
of (NAME . COMMAND).

Each KEY represents an entry to the alist.  If a key sequence have no
entry, it mean that the KEY is bound to the default value in the
major mode map.

NAME identify a minor mode, that have bound KEY to COMMAND.
The head of MODE-LIST represent the newest, and currently active,
binding.  The NAME of the tail is the major-mode, and COMMAND is
the default binding. The MODE-LIST always contains at least the
major-mode entry.

Initially the alist is nil."
  
  ;Try to make keymap buffer-local, *not* mode-local
  (if (current-local-map)
      (setq minor-local-keymap (copy-keymap (current-local-map)))
    (setq minor-local-keymap (make-sparse-keymap)))
  (use-local-map minor-local-keymap))

(defun minor-set-key (key command name)
  "Bind KEY to COMMAND in the minor keymap used by the minor mode NAME.
NAME is a symbol that must identify the minor mode."

  (if (null minor-local-keymap)
      (make-minor-keymap))
  (let ((map-entry (assoc key minor-keymap)))
    (if (null map-entry)
	(progn 
	  (setq map-entry (list key
				(cons major-mode
				      (local-key-binding key))))
	  (setq minor-keymap (cons map-entry
				   minor-keymap))))
    (rplacd map-entry (cons (cons name command)
			    (cdr map-entry))))
  (local-set-key key command))

(defun unbind-minor-mode (name)
  "Remove all references to minor mode NAME from the minor keymap.
Key bindings will be updated to reflect the new content of the minor
keymap."

  (mapcar 'unbind-minor-entry minor-keymap))

(defun unbind-minor-entry (entry)
  "Remove any references to a minor mode from ENTRY. ENTRY have the
form (KEY . MODE-LIST) where MODE-LIST is a list of (NAME . COMMAND).
NAME is compared with the value of the variable \"name\", and the
conscell is removed if equal.  If the first entry in MODE-LIST is
removed, KEY is rebound to the COMMAND of the next cell."

  (let ((current (cdr entry))
	(previous entry))
    (if (eq name (car (car current)))
	(local-set-key (car entry) (cdr (car (cdr current)))))
    (while current
      (if (eq name (car (car current)))
	    (rplacd previous (cdr current))
	(setq previous current))
      (setq current (cdr current)))))
