]> code.delx.au - gnu-emacs/blobdiff - lisp/tmm.el
(ediff-even-diff-face-A): Fix spelling.
[gnu-emacs] / lisp / tmm.el
index 868b07b98a499e93d39cef58e9631e8d0d1f1fe5..7243818639118c4e23e292131e14efc51f8105d0 100644 (file)
@@ -1,6 +1,6 @@
 ;;; tmm.el --- text mode access to menu-bar
 
-;; Copyright (C) 1994, 1995 Free Software Foundation, Inc.
+;; Copyright (C) 1994, 1995, 1996 Free Software Foundation, Inc.
 
 ;; Author: Ilya Zakharevich <ilya@math.mps.ohio-state.edu>
 ;; Maintainer: FSF
 ;; GNU General Public License for more details.
 
 ;; You should have received a copy of the GNU General Public License
-;; along with GNU Emacs; see the file COPYING.  If not, write to
-;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
 
-;;; Commentary ============================================================
+;;; Commentary:
 
-;;; To use this package add 
+;; To use this package add 
 
-;;; (autoload 'tmm-menubar 'tmm "Text mode substitute for menubar" t) 
-;;; (global-set-key [f10] 'tmm-menubar)
+;; (autoload 'tmm-menubar 'tmm "Text mode substitute for menubar" t) 
+;; (global-set-key [f10] 'tmm-menubar)
+;; to your .emacs file. You can also add your own access to different
+;; menus available in Window System Emacs modeling definition after
+;; tmm-menubar.
 
-;;; to your .emacs file. You can also add your own access to different
-;;; menus available in Window System Emacs modelling definition after
-;;; tmm-menubar.
+;;; Code:
 
 (require 'electric)
 
+(defgroup tmm nil
+  "Text mode access to menu-bar."
+  :prefix "tmm-"
+  :group 'menu)
+
 ;;; The following will be localized, added only to pacify the compiler.
 (defvar tmm-short-cuts)
 (defvar tmm-old-mb-map nil)
 (defvar tmm-old-comp-map)
 (defvar tmm-c-prompt)
 (defvar tmm-km-list)
+(defvar tmm-next-shortcut-digit)
 (defvar tmm-table-undef)
 
 ;;;###autoload (define-key global-map "\M-`" 'tmm-menubar)
@@ -82,6 +90,7 @@ we make that menu bar item (the one at that position) the default choice."
          (setq menu-bar-item (car this-one))))
     (tmm-prompt menu-bar nil menu-bar-item)))
 
+;;;###autoload
 (defun tmm-menubar-mouse (event)
   "Text-mode emulation of looking and choosing from a menubar.
 This command is used when you click the mouse in the menubar
@@ -90,19 +99,43 @@ See the documentation for `tmm-prompt'."
   (interactive "e")
   (tmm-menubar (car (posn-x-y (event-start event)))))
 
-(defvar tmm-mid-prompt "==>"
-  "String to insert between shortcut and menu item or nil.")
+(defcustom tmm-mid-prompt "==>"
+  "*String to insert between shortcut and menu item. 
+If nil, there will be no shortcuts. It should not consist only of spaces,
+or else the correct item might not be found in the `*Completions*' buffer."
+  :type 'string
+  :group 'tmm)
 
 (defvar tmm-mb-map nil
   "A place to store minibuffer map.")
 
-(defvar tmm-completion-prompt 
+(defcustom tmm-completion-prompt 
   "Press PageUp Key to reach this buffer from the minibuffer.
 Alternatively, you can use Up/Down keys (or your History keys) to change
 the item in the minibuffer, and press RET when you are done, or press the 
 marked letters to pick up your choice.  Type C-g or ESC ESC ESC to cancel.
 "
-  "What insert on top of completion buffer.")
+  "*Help text to insert on the top of the completion buffer.
+To save space, you can set this to nil,
+in which case the standard introduction text is deleted too."
+  :type '(choice string (const nil))
+  :group 'tmm)
+
+(defcustom tmm-shortcut-style '(downcase upcase)
+  "*What letters to use as menu shortcuts. 
+Must be either one of the symbols `downcase' or `upcase', 
+or else a list of the two in the order you prefer."
+  :type '(choice (const downcase)
+                (const upcase)
+                (repeat (choice (const downcase) (const upcase))))
+  :group 'tmm)
+
+(defcustom tmm-shortcut-words 2
+  "*How many successive words to try for shortcuts, nil means all.
+If you use only one of `downcase' or `upcase' for `tmm-shortcut-style', 
+specify nil for this variable."
+  :type '(choice integer (const nil))
+  :group 'tmm)
 
 ;;;###autoload
 (defun tmm-prompt (menu &optional in-popup default-item)
@@ -143,6 +176,8 @@ Its value should be an event that has a binding in MENU."
        ;; This way we only ask the user one question,
        ;; for which element of that pane.
        (setq choice (cdr (car tmm-km-list)))
+      (unless tmm-km-list
+       (error "Empty menu reached"))
       (and tmm-km-list
           (let ((index-of-default 0))
             (if tmm-mid-prompt
@@ -166,27 +201,28 @@ Its value should be an event that has a binding in MENU."
             (setq history (append history history history history))
             (setq tmm-c-prompt (nth (- history-len 1 index-of-default) history))
             (add-hook 'minibuffer-setup-hook 'tmm-add-prompt)
-            (unwind-protect
-                (setq out
-                      (completing-read
-                       (concat gl-str " (up/down to change, PgUp to menu): ")
-                       tmm-km-list nil t nil
-                       (cons 'history (- (* 2 history-len) index-of-default))))
-              (save-excursion
-                (remove-hook 'minibuffer-setup-hook 'tmm-add-prompt)
-                (if (get-buffer "*Completions*")
-                    (progn
-                      (set-buffer "*Completions*")
-                      (use-local-map tmm-old-comp-map)
-                      (bury-buffer (current-buffer)))))
-              )))
+            (save-excursion
+              (unwind-protect
+                  (setq out
+                        (completing-read
+                         (concat gl-str " (up/down to change, PgUp to menu): ")
+                         tmm-km-list nil t nil
+                         (cons 'history (- (* 2 history-len) index-of-default))))
+                (save-excursion
+                  (remove-hook 'minibuffer-setup-hook 'tmm-add-prompt)
+                  (if (get-buffer "*Completions*")
+                      (progn
+                        (set-buffer "*Completions*")
+                        (use-local-map tmm-old-comp-map)
+                        (bury-buffer (current-buffer)))))
+                ))))
       (setq choice (cdr (assoc out tmm-km-list)))
       (and (null choice)
           (> (length out) (length tmm-c-prompt))
           (string= (substring out 0 (length tmm-c-prompt)) tmm-c-prompt)
           (setq out (substring out (length tmm-c-prompt))
                 choice (cdr (assoc out tmm-km-list))))
-      (and (null choice)
+      (and (null choice) out
           (setq out (try-completion out tmm-km-list)
                 choice (cdr (assoc  out tmm-km-list)))))
     ;; CHOICE is now (STRING . MEANING).  Separate the two parts.
@@ -211,78 +247,111 @@ Its value should be an event that has a binding in MENU."
          ;; We just handled a menu keymap and found a command.
          (choice
           (if chosen-string
-              (call-interactively choice)
+              (progn
+                (setq last-command-event chosen-string)
+                (call-interactively choice))
             choice)))))
 
-
 (defun tmm-add-shortcuts (list)
   "Adds shortcuts to cars of elements of the list.
 Takes a list of lists with a string as car, returns list with
 shortcuts added to these cars.
 Stores a list of all the shortcuts in the free variable `tmm-short-cuts'."
-  (let ((next-shortcut-number 0))
-    (mapcar (lambda (elt)
-             (let ((str (car elt)) f b)
-               (setq f (upcase (substring str 0 1)))
-               ;; If does not work, try beginning of the other word
-               (if (and (member f tmm-short-cuts)
-                        (string-match " \\([^ ]\\)" str))
-                   (setq f (upcase (substring
-                                    str
-                                    (setq b (match-beginning 1)) (1+ b)))))
-               ;; If we don't have an unique letter shortcut,
-               ;; pick a digit as a shortcut instead.
-               (if (member f tmm-short-cuts)
-                   (if (< next-shortcut-number 10)
-                       (setq f (format "%d" next-shortcut-number)
-                             next-shortcut-number (1+ next-shortcut-number))
-                     (setq f nil)))
-               (if (null f)
-                   elt
-                 (setq tmm-short-cuts (cons f tmm-short-cuts))
-                 (cons (concat f tmm-mid-prompt str) (cdr elt)))))
-           (reverse list))))
-
-(defun tmm-define-keys ()
-  (mapcar (lambda (str)
-           (define-key (current-local-map) str 'tmm-shortcut)
-           (define-key (current-local-map) (downcase str) 'tmm-shortcut))
-         tmm-short-cuts)
-  (define-key (current-local-map) [pageup] 'tmm-goto-completions)
-  (define-key (current-local-map) [prior] 'tmm-goto-completions)
-  (define-key (current-local-map) "\ev" 'tmm-goto-completions)
-  (define-key (current-local-map) "\C-n" 'next-history-element)
-  (define-key (current-local-map) "\C-p" 'previous-history-element))
+  (let ((tmm-next-shortcut-digit ?0))
+    (mapcar 'tmm-add-one-shortcut (reverse list))))
+
+(defsubst tmm-add-one-shortcut (elt)
+;; uses the free vars tmm-next-shortcut-digit and tmm-short-cuts
+  (let* ((str (car elt))
+        (paren (string-match "(" str))  
+        (pos 0) (word 0) char)
+    (catch 'done                        ; ??? is this slow?
+      (while (and (or (not tmm-shortcut-words) ; no limit on words
+                      (< word tmm-shortcut-words)) ; try n words
+                  (setq pos (string-match "\\w+" str pos)) ; get next word
+                  (not (and paren (> pos paren)))) ; don't go past "(binding.."
+        (if (or (= pos 0)
+                (/= (aref str (1- pos)) ?.)) ; avoid file extensions
+            (let ((shortcut-style                 
+                   (if (listp tmm-shortcut-style) ; convert to list
+                       tmm-shortcut-style
+                     (list tmm-shortcut-style))))
+              (while shortcut-style     ; try upcase and downcase variants
+                (setq char (funcall (car shortcut-style) (aref str pos)))
+                (if (not (memq char tmm-short-cuts)) (throw 'done char))
+                (setq shortcut-style (cdr shortcut-style)))))
+        (setq word (1+ word))
+        (setq pos (match-end 0)))
+      (while (<= tmm-next-shortcut-digit ?9) ; no letter shortcut, pick a digit
+        (setq char tmm-next-shortcut-digit)
+        (setq tmm-next-shortcut-digit (1+ tmm-next-shortcut-digit))
+        (if (not (memq char tmm-short-cuts)) (throw 'done char)))
+      (setq char nil))
+    (if char (setq tmm-short-cuts (cons char tmm-short-cuts)))
+    (cons (concat (if char (concat (char-to-string char) tmm-mid-prompt)
+                    ;; keep them lined up in columns
+                    (make-string (1+ (length tmm-mid-prompt)) ?\ ))
+                  str)
+          (cdr elt))))
+
+;; This returns the old map.
+(defun tmm-define-keys (minibuffer)
+  (let ((map (make-sparse-keymap)))
+    (suppress-keymap map t)
+    (mapcar
+     (function
+      (lambda (c)
+        (if (listp tmm-shortcut-style)
+            (define-key map (char-to-string c) 'tmm-shortcut)
+          ;; only one kind of letters are shortcuts, so map both upcase and
+          ;; downcase input to the same
+          (define-key map (char-to-string (downcase c)) 'tmm-shortcut)
+          (define-key map (char-to-string (upcase c)) 'tmm-shortcut))))
+     tmm-short-cuts)
+    (if minibuffer
+       (progn
+          (define-key map [pageup] 'tmm-goto-completions)
+          (define-key map [prior] 'tmm-goto-completions)
+          (define-key map "\ev" 'tmm-goto-completions)
+          (define-key map "\C-n" 'next-history-element)
+          (define-key map "\C-p" 'previous-history-element)))
+    (prog1 (current-local-map)
+      (use-local-map (append map (current-local-map))))))
+
+(defun tmm-completion-delete-prompt ()
+  (set-buffer standard-output)
+  (goto-char 1)
+  (delete-region 1 (search-forward "Possible completions are:\n")))
 
 (defun tmm-add-prompt ()
   (remove-hook 'minibuffer-setup-hook 'tmm-add-prompt)
   (make-local-hook 'minibuffer-exit-hook)
   (add-hook 'minibuffer-exit-hook 'tmm-delete-map nil t)
   (let ((win (selected-window)))
-    (setq tmm-old-mb-map (current-local-map))
-    (use-local-map (append (make-sparse-keymap) tmm-old-mb-map))
-    (tmm-define-keys)
+    (setq tmm-old-mb-map (tmm-define-keys t))
     ;; Get window and hide it for electric mode to get correct size
     (save-window-excursion 
       (let ((completions
             (mapcar 'car minibuffer-completion-table)))
+        (or tmm-completion-prompt
+            (add-hook 'completion-setup-hook
+                      'tmm-completion-delete-prompt 'append))
        (with-output-to-temp-buffer "*Completions*"
-         (display-completion-list completions)))
-      (set-buffer "*Completions*")
-      (goto-char 1)
-      (insert tmm-completion-prompt)
+         (display-completion-list completions))
+        (remove-hook 'completion-setup-hook 'tmm-completion-delete-prompt))
+      (if tmm-completion-prompt
+          (progn
+           (set-buffer "*Completions*")
+           (goto-char 1)
+            (insert tmm-completion-prompt)))
       )
-    (save-excursion
+    (save-selected-window
       (other-window 1)                 ; Electric-pop-up-window does
                                        ; not work in minibuffer
-      (set-buffer (window-buffer (Electric-pop-up-window "*Completions*")))
-      (setq tmm-old-comp-map (current-local-map))
-      (use-local-map (append (make-sparse-keymap) tmm-old-comp-map))
-      (tmm-define-keys)
-      (select-window win)              ; Cannot use
-                                       ; save-window-excursion, since
-                                       ; it restores the size
-      )
+      (Electric-pop-up-window "*Completions*")
+      (with-current-buffer "*Completions*"
+       (setq tmm-old-comp-map (tmm-define-keys nil))))
+
     (insert tmm-c-prompt)))
 
 (defun tmm-delete-map ()
@@ -293,13 +362,15 @@ Stores a list of all the shortcuts in the free variable `tmm-short-cuts'."
 (defun tmm-shortcut ()
   "Choose the shortcut that the user typed."
   (interactive)
-  (let ((c (upcase (char-to-string last-command-char))) s)
-    (if (member c tmm-short-cuts)
+  (let ((c last-command-char) s)
+    (if (symbolp tmm-shortcut-style)
+        (setq c (funcall tmm-shortcut-style c)))
+    (if (memq c tmm-short-cuts)
        (if (equal (buffer-name) "*Completions*")
            (progn
              (beginning-of-buffer)
              (re-search-forward
-              (concat "\\(^\\|[ \t]\\)" c tmm-mid-prompt))
+              (concat "\\(^\\|[ \t]\\)" (char-to-string c) tmm-mid-prompt))
              (choose-completion))
          (erase-buffer)                ; In minibuffer
          (mapcar (lambda (elt)
@@ -307,7 +378,7 @@ Stores a list of all the shortcuts in the free variable `tmm-short-cuts'."
                         (substring (car elt) 0 
                                    (min (1+ (length tmm-mid-prompt))
                                         (length (car elt))))
-                        (concat c tmm-mid-prompt))
+                        (concat (char-to-string c) tmm-mid-prompt))
                        (setq s (car elt))))
                  tmm-km-list)
          (insert s)
@@ -321,7 +392,6 @@ Stores a list of all the shortcuts in the free variable `tmm-short-cuts'."
   (search-forward tmm-c-prompt)
   (search-backward tmm-c-prompt))
 
-
 (defun tmm-get-keymap (elt &optional in-x-menu) 
   "Prepends (DOCSTRING EVENT BINDING) to free variable `tmm-km-list'.
 The values are deduced from the argument ELT, that should be an
@@ -329,57 +399,75 @@ element of keymap, an `x-popup-menu' argument, or an element of
 `x-popup-menu' argument (when IN-X-MENU is not-nil).
 This function adds the element only if it is not already present.
 It uses the free variable `tmm-table-undef' to keep undefined keys."
-  (let (km str cache (event (car elt)))
+  (let (km str cache plist filter (event (car elt)))
     (setq elt (cdr elt))
     (if (eq elt 'undefined)
        (setq tmm-table-undef (cons (cons event nil) tmm-table-undef))
-      (or
-       (assoc event tmm-table-undef)
-       (and (if (listp elt)
-               (keymapp elt)
-             (fboundp elt))
-           (setq km elt))
-       (and (if (listp (cdr-safe elt))
-               (keymapp (cdr-safe elt))
-             (fboundp (cdr-safe elt)))
-           (setq km (cdr elt))
-           (and (stringp (car elt)) (setq str (car elt))))
-       (and (if (listp (cdr-safe (cdr-safe elt)))
-               (keymapp (cdr-safe (cdr-safe elt)))
-             (fboundp (cdr-safe (cdr-safe elt))))
-           (setq km (cdr (cdr elt)))
-           (and (stringp (car elt)) (setq str (car elt)))
-           (or (and str
-                    (stringp (cdr (car (cdr elt)))) ; keyseq cache
-                    (setq cache (cdr (car (cdr elt))))
-                    cache (setq str (concat str cache))) str))
-       (and (if (listp (cdr-safe (cdr-safe (cdr-safe elt))))
-               (keymapp (cdr-safe (cdr-safe (cdr-safe elt))))
-             (fboundp (cdr-safe (cdr-safe (cdr-safe elt)))))
-                                       ; New style of easy-menu
-           (setq km (cdr (cdr (cdr elt))))
-           (and (stringp (car elt)) (setq str (car elt)))
-           (or (and str
-                    (stringp (cdr (car (cdr (cdr elt))))) ; keyseq cache
-                    (setq cache (cdr (car (cdr (cdr elt)))))
-                    cache (setq str (concat str cache)))
-               str))
-           (and (stringp event)        ; x-popup or x-popup element
-                (if (or in-x-menu (stringp (car-safe elt)))
-                    (setq str event event nil km elt)
-                  (setq str event event nil km (cons 'keymap elt))
-                  )))
+      (unless (assoc event tmm-table-undef)
+       (cond ((if (listp elt)
+                  (or (keymapp elt) (eq (car elt) 'lambda))
+                (fboundp elt))
+              (setq km elt))
+             ((if (listp (cdr-safe elt))
+                  (or (keymapp (cdr-safe elt))
+                      (eq (car (cdr-safe elt)) 'lambda))
+                (fboundp (cdr-safe elt)))
+              (setq km (cdr elt))
+              (and (stringp (car elt)) (setq str (car elt))))
+             ((if (listp (cdr-safe (cdr-safe elt)))
+                  (or (keymapp (cdr-safe (cdr-safe elt)))
+                      (eq (car (cdr-safe (cdr-safe elt))) 'lambda))
+                (fboundp (cdr-safe (cdr-safe elt))))
+              (setq km (cdr (cdr elt)))
+              (and (stringp (car elt)) (setq str (car elt)))
+              (and str
+                   (stringp (cdr (car (cdr elt)))) ; keyseq cache
+                   (setq cache (cdr (car (cdr elt))))
+                   cache (setq str (concat str cache))))
+             ((eq (car-safe elt) 'menu-item)
+              (setq plist (cdr-safe (cdr-safe (cdr-safe elt))))
+              (setq km (nth 2 elt))
+              (setq str (nth 1 elt))
+              (setq filter (plist-get plist :filter))
+              (if filter
+                  (setq km (funcall filter km)))
+              (and str
+                   (consp (nth 3 elt))
+                   (stringp (cdr (nth 3 elt))) ; keyseq cache
+                   (setq cache (cdr (nth 3 elt)))
+                   cache
+                   (setq str (concat str cache))))
+             ((if (listp (cdr-safe (cdr-safe (cdr-safe elt))))
+                  (or (keymapp (cdr-safe (cdr-safe (cdr-safe elt))))
+                      (eq (car (cdr-safe (cdr-safe (cdr-safe elt)))) 'lambda))
+                (fboundp (cdr-safe (cdr-safe (cdr-safe elt)))))
+                                        ; New style of easy-menu
+              (setq km (cdr (cdr (cdr elt))))
+              (and (stringp (car elt)) (setq str (car elt)))
+              (and str
+                   (stringp (cdr (car (cdr (cdr elt))))) ; keyseq cache
+                   (setq cache (cdr (car (cdr (cdr elt)))))
+                   cache (setq str (concat str cache))))
+             ((stringp event)          ; x-popup or x-popup element
+              (if (or in-x-menu (stringp (car-safe elt)))
+                  (setq str event event nil km elt)
+                (setq str event event nil km (cons 'keymap elt))
+                ))))
       (and km (stringp km) (setq str km))
+      ;; Verify that the command is enabled;
+      ;; if not, don't mention it.
+      (when (and km (symbolp km) (get km 'menu-enable))
+       (unless (eval (get km 'menu-enable))
+         (setq km nil)))
       (and km str
           (or (assoc str tmm-km-list)
               (setq tmm-km-list 
                     (cons (cons str (cons event km)) tmm-km-list)))
           ))))
 
-
 (defun tmm-get-keybind (keyseq)
   "Return the current binding of KEYSEQ, merging prefix definitions.
-If KEYSEQ is a prefix key that has local and gloibal bindings,
+If KEYSEQ is a prefix key that has local and global bindings,
 we merge them into a single keymap which shows the proper order of the menu.
 However, for the menu bar itself, the value does not take account
 of `menu-bar-final-items'."
@@ -411,8 +499,6 @@ of `menu-bar-final-items'."
 
 (add-hook 'calendar-load-hook (lambda () (require 'cal-menu)))
 
-
 (provide 'tmm)
 
-
 ;;; tmm.el ends here