Mercurial > hg > xemacs-beta
diff lisp/dired/gmhist.el @ 0:376386a54a3c r19-14
Import from CVS: tag r19-14
author | cvs |
---|---|
date | Mon, 13 Aug 2007 08:45:50 +0200 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/dired/gmhist.el Mon Aug 13 08:45:50 2007 +0200 @@ -0,0 +1,1071 @@ +;;;; gmhist.el - Provide generic minibuffer history for commands + +(defconst gmhist-version (substring "!Revision: 4.27 !" 11 -2) + "Id: gmhist.el,v 4.27 1992/04/20 17:17:47 sk RelBeta +Report bugs to Sebastian Kremer <sk@thp.uni-koeln.de>.") + +;; Copyright (C) 1990 by Sebastian Kremer <sk@thp.uni-koeln.de> + +;; 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 1, 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. + +;; LISPDIR ENTRY for the Elisp Archive =============================== +;; LCD Archive Entry: +;; gmhist|Sebastian Kremer|sk@thp.uni-koeln.de +;; |Generic minibuffer history package. +;; |Date: 1992/04/20 17:17:47 |Revision: 4.27 | + +;; INSTALLATION ====================================================== +;; +;; Put this file into your load-path and the following in your +;; ~/.emacs: +;; +;; (autoload 'read-with-history-in "gmhist") +;; (autoload 'read-file-name-with-history-in "gmhist") +;; (autoload 'completing-read-with-history-in "gmhist") +;; (autoload 'gmhist-make-magic "gmhist" nil t) + +;; USAGE ============================================================= +;; +;; - as an Elisp programmer: use functions read-with-history-in, +;; completing-read-with-history-in, read-file-name-with-history-in or +;; gmhist-interactive inside the interactive clause of your functions +;; instead of a string specification. See the examples at the end of +;; the file. +;; +;; - as an Emacs user: To provide `simple' functions with history, +;; just type M-x gmhist-make-magic and enter the name of the +;; function, e.g., `eval-expression'. This function's arguments +;; are then remembered across calls and are available by typing +;; M-p to the minibuffer prompt of the function. More history +;; commands are mentioned in the documentation of variable +;; gmhist-map. +;; +;; Type M-x gmhist-remove-magic to restore the function's old +;; interactive behaviour. +;; +;; `Simple' functions are those that prompt for strings, file +;; names or lisp objects and perhaps use prefix args and the +;; region. See the file gmhist-app.el for examples with simple +;; and other functions. + +;; I'd like to thank Jamie Zawinski, Piet van Oostrum and Mike +;; Williams for very helpful feedback and ideas. + + +(provide 'gmhist) + +;; Emacs 19 has s-expr interactive's on some functions (sometimes to +;; emulate functionality gmhist would give). So we sometimes have to +;; test this to avoid letting gmhist-make-magic bombing on non-string +;; interactive specifications: +;; XEmacs fix: +(defvar gmhist-emacs-19-p (not (equal (substring emacs-version 0 2) "18"))) + +(defvar gmhist-default-format "[%s] " ; saves screen space, too + "Format used by gmhist to indicate the presence of a default value. +Set this to \"(default %s) \" to get the standard format.") + +(defvar gmhist-search-history nil "History of history searches.") + +(defun read-with-history-in (GMHIST-SYMBOL rwhi-prompt &optional + GMHIST-INITIAL GMHIST-READ) + ;; We have to be careful about dynamical scoping here so as not to + ;; shadow other lisp code that depends on fluid vars like `prompt + ;; (notorious in minibuffer code, e.g. electric-replace). + ;; That's why our own fluid vars have upper-case names starting with + ;; GMHIST- and why `rwhi-prompt' instead of `prompt' is used as + ;; formal argument. Similar below. + "\ +Read a string, maintaining minibuffer history across calls in GMHIST-SYMBOL, + prompting with PROMPT, with optional GMHIST-INITIAL as initial contents. +If optional fourth arg GMHIST-READ is non-nil, then interpret the + result as a lisp object and return that object. +See variable gmhist-map for history commands available during edit. +Example: + (defun foo-command (cmd) + (interactive (list (read-with-history-in 'foo-history \"Foo: \" ))) + (message \"Fooing %s...\" cmd)) + +See function gmhist-make-magic on how to give an existing function +history. + +These properties (see function put) of GMHIST-SYMBOL are supported: + +cursor-end Put cursor at end of a newly retrieved history line. +cursor-pos A regexp to put the cursor on. +keep-dups If t, duplicate commands are remembered, too. +initial-hist Initial value of the history list. +hist-ignore Regexp of commands that are not to be added to the history. +backup If t, backup in the history list (as if user had typed + M-p as first thing). Can also be an integer to backup + more than one history item. +default An empty string as input will default to the last + command (whether the last command was added to the + history or not). The default is stored in this + property, thus its initial value is the first default. +dangerous Commands matching this regexp will never be the default. +no-default If you don't want defaults at all, set this to t. + +Use the following only if you know what you are doing: + +hist-function Name of a function to call instead of doing normal + history processing. read-with-history-in becomes + effectively an alias for this function. + +These will be flushed soon (use let-binding minibuffer-completion-table +etc. instead): + +hist-map Minibuffer key map to use instead of gmhist-map. +completion-table +completion-predicate + Used in completion on history strings, when the hist-map + property has gmhist-completion-map as value. + The special value `t' for the table means to use the + current history list. + Thus, to get completion on history items just do: + (put 'foo-history 'hist-map gmhist-completion-map) + (put 'foo-history 'completion-table t) + +Hooks: + gmhist-after-insert-hook is run after a history item is + inserted into the minibuffer. + gmhist-load-hook is run after this package is loaded. + gmhist-hook is run as first thing inside read-with-history-in. + gmhist-before-move-hook is run before history motion takes place. + Function gmhist-remember-zero is a candidate for that hook. +" + ;; We don't use property names prefixed with 'ghmist-' because the + ;; caller has freedom to use anything for GMHIST-SYMBOL. + ;; The history list is never truncated, but I don't think this will + ;; cause problems. All histories together have at most a few k. + ;; On the other hand, some people run an Emacs session for weeks. + ;; Could use gmhist-hook to truncate the current history list. + ;; You can use 'initial-hist to save (part of) the history in a file + ;; and provide it at next startup. [Is there an exit-emacs-hook?] + ;; You can use 'hist-function to implement a completely different + ;; history mechanism, e.g., a ring instead of a list, without having + ;; to modify existing gmhist applications. + (run-hooks 'gmhist-hook) + (let ((hist-function (get GMHIST-SYMBOL 'hist-function))) + (if (fboundp hist-function) ; hist-function must be a symbol + (funcall hist-function ; not lambda + GMHIST-SYMBOL rwhi-prompt GMHIST-INITIAL GMHIST-READ) + (or (boundp GMHIST-SYMBOL) ; history list defaults to nil + (set GMHIST-SYMBOL (get GMHIST-SYMBOL 'initial-hist))) + ;; else do the usual history processing simply using lists: + (let* ((history (symbol-value GMHIST-SYMBOL)) + (minibuffer-completion-table (let ((table + (get GMHIST-SYMBOL + 'completion-table))) + (if (eq t table) + (mapcar (function list) + history) + table))) + (minibuffer-completion-predicate (get GMHIST-SYMBOL + 'completion-predicate)) + (minibuffer-history-symbol GMHIST-SYMBOL)) + (gmhist-new-read-from-minibuffer rwhi-prompt + GMHIST-INITIAL + (or (get GMHIST-SYMBOL 'hist-map) + gmhist-map) + GMHIST-READ))))) + +(defun completing-read-with-history-in (crwhi-hist-sym &rest args) + "Like completing-read, but with additional first arg HISTORY-SYMBOL." + (let ((minibuffer-history-symbol crwhi-hist-sym)) + (apply 'gmhist-completing-read args))) + +(defun gmhist-completing-read (crwhi-prompt table + &optional predicate + mustmatch initial) + "Like completing-read, but see minibuffer-history-symbol." + (let ((minibuffer-completion-confirm (if (eq mustmatch t) nil t)) + (minibuffer-completion-table table) + (minibuffer-completion-predicate predicate)) + (gmhist-new-read-from-minibuffer crwhi-prompt + initial + (gmhist-lookup-keymap + (if mustmatch + gmhist-must-match-map + gmhist-completion-map))))) + + +(defun read-file-name-with-history-in (crwhi-hist-sym &rest args) + "Like read-file-name, but with additional first arg HISTORY-SYMBOL." + (let ((file-history-symbol crwhi-hist-sym)) + (apply 'gmhist-read-file-name args))) + +(defvar file-history-symbol 'file-history + "*If non-nil, it is the name (a symbol) of a variable on which to cons +filenames entered in the minibuffer. +You may let-bind this to another symbol around calls to read-file-name.") + +(defun gmhist-read-file-name + (grfn-prompt &optional dir default mustmatch initial) + "Args: PROMPT &optional DIR DEFAULT MUSTMATCH INITIAL. +Read file name, maintaining history in file-history-symbol, prompting + with PROMPT, with optional INITIAL input and completing in directory DIR. +Value is not expanded! You must call expand-file-name yourself. +Default name to arg DEFAULT if user enters a null string (or, if + INITIAL was given, leaves it unchanged). +MUSTMATCH non-nil means require existing file's name. + Non-nil and non-t means also require confirmation after completion. +DIR defaults to current buffer's default-directory. + +This function differs from read-file-name in providing a history of +filenames bound to file-history-symbol and (for pre-Emacs 19) in +providing an argument INITIAL not present in Emacs 18's read-file-name." + (setq dir (or dir default-directory) + default (or default buffer-file-name)) + (if file-history-symbol + (progn (put file-history-symbol 'cursor-end t) + (put file-history-symbol 'no-default t))) + ;; $'s should be quoted (against substitute-in-file-name) in file + ;; names inserted here + (if initial + (setq initial (gmhist-quote-dollars (gmhist-unexpand-home initial))) + (if insert-default-directory + (setq initial (gmhist-quote-dollars (gmhist-unexpand-home dir))))) + (let* ((minibuffer-completion-confirm (if (eq mustmatch t) nil t)) + (minibuffer-completion-table 'read-file-name-internal) + (minibuffer-completion-predicate dir) + (minibuffer-history-symbol file-history-symbol) + (val (gmhist-new-read-from-minibuffer + grfn-prompt initial (gmhist-lookup-keymap + (if mustmatch + gmhist-filename-must-match-map + gmhist-filename-completion-map))))) + + (or (and (or (and (stringp initial) + (string= initial val)) + (and (null initial) + (zerop (length val)))) + default) + (substitute-in-file-name val)))) + +(defun gmhist-unexpand-home (file) + ;; Make prompt look nicer by un-expanding home dir. + ;; read-file-name does this, too. + ;; Avoid clobbering match-data with string-match. + (let* ((home (expand-file-name "~/")) + (home-len (length home)) + (file-len (length file))) + (if (and home + (stringp file) + (>= file-len home-len) + (string= home (substring file 0 home-len)) + (eq ?/ (aref file (1- home-len)))) + (concat "~/" (substring file home-len)) + file))) + +; (defun gmhist-quote-dollars (file) +; "Quote `$' as `$$' in FILE to get it past function `substitute-in-file-name.'" +; (apply (function concat) +; (mapcar (function +; (lambda (char) +; (if (= char ?$) +; "$$" +; (vector char)))) +; file))) +;; 10000 iterations of (gmhist-quote-dollars "foo") took 19 seconds +;; and *lots* of garbage collections (about a dozen or more) + +;; This version does not cons and is much faster in the usual case +;; without $ present: +;; 10000 iterations of (gmhist-quote-dollars "foo") took 4 seconds and +;; not a single garbage collection. +(defun gmhist-quote-dollars (file) + "Quote `$' as `$$' in FILE to get it past function `substitute-in-file-name.'" + (let ((pos 0)) + (while (setq pos (string-match "\\$" file pos)) + (setq file (concat (substring file 0 pos) + "$";; precede by escape character (also a $) + (substring file pos)) + ;; add 2 instead 1 since another $ was inserted + pos (+ 2 pos))) + file)) + + + +(defun read-buffer-with-history-in (rbwhi-hist-sym &rest args) + "Like read-buffer, but with additional first arg HISTORY-SYMBOL." + (let ((buffer-history-symbol rbwhi-hist-sym)) + (apply 'gmhist-read-buffer args))) + +(defvar buffer-history-symbol 'buffer-history + "*If non-nil, it is the name (a symbol) of a variable on which to cons +buffer names entered in the minibuffer.") + +(defun gmhist-read-buffer (grb-prompt &optional default existing) + "Read a buffer name, maintaining history in buffer-history-symbol and return as string. +Args PROMPT &optional DEFAULT EXISTING. +Optional arg EXISTING means an existing buffer must be entered." + (if (bufferp default);; want string in prompt, not buffer object + (setq default (buffer-name default))) + (if buffer-history-symbol + (put buffer-history-symbol 'default default)) ; also if nil + (let* ((minibuffer-history-symbol buffer-history-symbol) + (name (gmhist-completing-read + grb-prompt + ;;(function (lambda (buf) (list (buffer-name buf)))) + ;; convert to alist in format (BUF-NAME . BUF-OBJ) + (mapcar + (function (lambda (arg) (cons (buffer-name arg) arg))) + (buffer-list)) + (function (lambda (elt) (get-buffer (car elt)))) + existing))) + (if (equal "" name) + default + name))) + +(defvar minibuffer-history-symbol 'minibuffer-history + "*If non-nil, it is the name (a symbol) of a variable on which to cons +the string entered in the minibuffer. +Input is stored as string, even for e.g. `read-buffer'.") + +(defvar minibuffer-history nil + "List of strings entered using the minibuffer, most recent first.") + +(put 'minibuffer-history 'no-default t) + +(defvar minibuffer-history-read-only nil + "If non-nil, nothing will be stored on `minibuffer-history-symbol'. +History motions commands are still available in the minibuffer.") + +(defvar minibuffer-history-position nil + "If currently reading the minibuffer, the history position.") + +(defvar minibuffer-initial-contents nil + "If currently reading the minibuffer, the initial contents.") + +;; Save the subr, we need it inside the redefined version: +(or (fboundp 'gmhist-old-read-from-minibuffer) + (fset 'gmhist-old-read-from-minibuffer + (symbol-function 'read-from-minibuffer))) + +(defun gmhist-new-read-from-minibuffer + (gnrfm-prompt &optional initial-contents keymap read position) + "Read a string from the minibuffer, prompting with string PROMPT. +If optional second arg INITIAL-CONTENTS is non-nil, it is a string + to be inserted into the minibuffer before reading input. +Third arg KEYMAP is a keymap to use whilst reading; + if omitted or nil, the default is `minibuffer-local-map'. +If fourth arg READ is non-nil, then interpret the result as a lisp object + and return that object: + in other words, do `(car (read-from-string INPUT-STRING))' +Fifth arg POSITION, if non-nil, is where to put point + in the minibuffer after inserting INITIAL-CONTENTS. + +The ambient value of `minibuffer-history-symbol' (q.v.) is used and set. + +*** This is the gmhist version.***" + (if (null minibuffer-history-symbol) + (if gmhist-emacs-19-p + (gmhist-old-read-from-minibuffer + gnrfm-prompt initial-contents keymap read position) + (gmhist-old-read-from-minibuffer gnrfm-prompt initial-contents + keymap read)) + (gmhist-read-from-minibuffer + gnrfm-prompt initial-contents keymap read position))) + +(defun gmhist-read-from-minibuffer (grfm-prompt + &optional + initial-contents keymap read position) + (or keymap (setq keymap minibuffer-local-map)) + (or minibuffer-history-read-only + (boundp minibuffer-history-symbol) ; history list defaults to nil + ;; create history list if not already done + (set minibuffer-history-symbol + (get minibuffer-history-symbol 'initial-hist))) + (let* ((minibuffer-history-position 0) ; fluid var for motion commands + (minibuffer-initial-contents initial-contents) ; ditto + (history (symbol-value minibuffer-history-symbol)) + ;; Command is an s-exp when read->t. In this case, + ;; cannot have empty input: + (no-default (or read + (get minibuffer-history-symbol 'no-default))) + (dangerous (if no-default + nil + (get minibuffer-history-symbol 'dangerous))) + ;; Idea for 'backup feature by Mike Williams + (backup (get minibuffer-history-symbol 'backup)) + (default (if no-default + nil + (get minibuffer-history-symbol 'default))) + (the-prompt (if default + (concat grfm-prompt (format gmhist-default-format + default)) + grfm-prompt)) + (the-initial (if (or minibuffer-initial-contents + (not backup)) + minibuffer-initial-contents + ;; else we must backup in the history list + (setq backup (min (max 0 (or (and (integerp backup) + backup) + 1)) + (length history))) + (if (zerop (setq minibuffer-history-position backup)) + nil + ;; else backup is at least 1 + (let ((backup-input (nth (1- backup) history))) + (if read + (prin1-to-string backup-input) + backup-input))))) + command) + ;; Read the command from minibuffer, providing history motion + ;; key map and minibuffer completion + (setq command + (if position + ;; avoid passing POSITION arg unless given (presumably + ;; we are in Emacs 19 then) + (gmhist-old-read-from-minibuffer the-prompt the-initial keymap + position) + (gmhist-old-read-from-minibuffer the-prompt the-initial keymap))) + ;; Care about default values unless forbidden: + (or no-default + (setq command (gmhist-handle-default command default dangerous))) + (if minibuffer-history-read-only + nil + (let (ignore) + ;; Add to history if first command, or not a dup, or not to be ignored + (or (and history + (or (if (get minibuffer-history-symbol 'keep-dups) + nil + (equal command (car history))) + (if (stringp (setq ignore (get minibuffer-history-symbol + 'hist-ignore))) + (string-match ignore + (gmhist-stringify (car history)))))) + (set minibuffer-history-symbol (cons command history))))) + ;; Return command's value to caller: + (if read + (car (read-from-string command)) + command))) + +(defun gmhist-handle-default (command default dangerous) + (if (string= "" command) + (if default (setq command default))) + ;; Set default value unless it is dangerous. + (or (and (stringp dangerous) + ;; Should actually save match-data as we call string-match + (string-match dangerous (gmhist-stringify command))) + (put minibuffer-history-symbol 'default command)) + ;; Return the prefrobnicated command: + command) + + +;; Minibuffer key maps to implement history + +(defvar gmhist-define-keys-hook nil + "Hook run inside function `gmhist-define-keys' (q.v.), after the +standard gmhist bindings.") + +(or (fboundp 'gmhist-define-keys) + (defun gmhist-define-keys (map) + "Bind the standard history commands in MAP, a key map. + +When gmhist is loaded, this function is only defined if you have not +already defined it, so that you can customize it without worrying +about load order. +You can also use `gmhist-define-keys-hook' if you just want to add to +existing bindings." + (define-key map "\M-p" 'gmhist-previous) + (define-key map "\M-n" 'gmhist-next) + (define-key map "\M-r" 'gmhist-search-backward) + (define-key map "\M-s" 'gmhist-search-forward) + ;;(define-key map "\M-<" 'gmhist-beginning) + ;;(define-key map "\M-<" 'gmhist-beginning) + ;; Last two for bash/readline compatibility. Better M-a and M-e ? + ;; In query-replace, multi-line text together with next-line's + ;; misfeature of adding blank lines really lets you lose without M-< + ;; and M->. + ;;(define-key map "\M-a" 'gmhist-beginning) + ;;(define-key map "\M-e" 'gmhist-end) + ;; M-a is already used in electric replace + ;; Try this as general purpose mover: + (define-key map "\M-g" 'gmhist-toggle) + (define-key map "\M-G" 'gmhist-switch-history) + (define-key map "\M-?" 'gmhist-show) + (run-hooks 'gmhist-define-keys-hook))) + +(defun gmhist-lookup-keymap (map) + (if (keymapp map) + map + (gmhist-lookup-keymap (symbol-value map)))) + +(defvar gmhist-map nil + "Key map for generic minibuffer history. +\\<gmhist-map>\\[gmhist-previous], \\[gmhist-next], \ +\\[gmhist-beginning], \\[gmhist-end] move through, \ +\\[gmhist-search-backward] and \\[gmhist-search-forward] search, +\\[gmhist-show] displays the history: +\\{gmhist-map}") + +(if gmhist-map + nil + (setq gmhist-map (copy-keymap minibuffer-local-map)) + (gmhist-define-keys gmhist-map)) + +(defvar gmhist-completion-map nil + "Key map for generic minibuffer history with completion, see gmhist-map.") + +(if gmhist-completion-map + nil + ;; If you have loaded D. Gillespie's complete.el or Christopher + ;; McConnell's completer.el *before* gmhist, you get it in gmhist, + ;; too: + (setq gmhist-completion-map (copy-keymap minibuffer-local-completion-map)) + (gmhist-define-keys gmhist-completion-map)) + +(defvar gmhist-must-match-map nil + "Key map for generic minibuffer history with completion that must match, +see gmhist-map.") + +(if gmhist-must-match-map + nil + (setq gmhist-must-match-map (copy-keymap minibuffer-local-must-match-map)) + (gmhist-define-keys gmhist-must-match-map)) + +(defvar gmhist-filename-completion-map 'gmhist-completion-map + "A keymap (or a symbol pointing to one) to use in filename +completion that need not match. Defaults to 'gmhist-completion-map.") + +(defvar gmhist-filename-must-match-map 'gmhist-must-match-map + + "A keymap (or a symbol pointing to one) to use in filename +completion that must match. Defaults to 'gmhist-must-match-map.") + + +;; Minibuffer commands to implement history +;; They run inside read-with-history-in and heavily depend on fluid +;; vars from there. + +(defun gmhist-goto (n) + ;; Go to history position N, 1 <= N <= length of history + ;; N<0 means the future and inserts an empty string + ;; N=0 means minibuffer-initial-contents (fluid var from + ;; gmhist-new-read-from-minibuffer) + (run-hooks 'gmhist-before-move-hook) + (erase-buffer) + (setq minibuffer-history-position n) + (if (< n 0) + nil + (insert + (gmhist-stringify + (if (= n 0) + (or minibuffer-initial-contents "") + (nth (1- n) (symbol-value minibuffer-history-symbol))))) + (run-hooks 'gmhist-after-insert-hook) + ;; next two actually would be a good application for this hook + (goto-char (if (get minibuffer-history-symbol 'cursor-end) + (point-max) + (point-min))) + (let ((pos (get minibuffer-history-symbol 'cursor-pos))) + (if (stringp pos) + (if (eobp) + (re-search-backward pos nil t) + (re-search-forward pos nil t)))))) + +(defun gmhist-beginning () + "Go to the oldest command in the history." + (interactive) + (gmhist-goto (length (symbol-value minibuffer-history-symbol)))) + +(defun gmhist-end () + "Position before the most recent command in the history." + (interactive) + (gmhist-goto 0)) + +(defun gmhist-toggle (&optional n) + "If at end of history, move to beginning, else move to end. +Prefix arg is history position to go to." + (interactive "P") + (if n + (gmhist-goto (prefix-numeric-value n)) + (if (= 0 minibuffer-history-position) + (gmhist-beginning) + (gmhist-end)))) + +(defun gmhist-switch-history (new-history) + "Switch to a different history." + (interactive + (let ((enable-recursive-minibuffers t)) + (list (read-from-minibuffer "Switch to history: " nil nil t)))) + (setq minibuffer-history-symbol new-history + minibuffer-history-position 0)) + +(defun gmhist-next (n) + "Go to next history position." + ;; fluid vars: minibuffer-history-symbol minibuffer-history-position + ;; Inserts the next element of minibuffer-history-symbol's value + ;; into the minibuffer. + ;; minibuffer-history-position is the current history position. + (interactive "p") + ;; clip the new history position to the valid range: + (let ((narg (min (max 0 (- minibuffer-history-position n)) + (length (symbol-value minibuffer-history-symbol))))) + (if (= minibuffer-history-position narg) + (error "No %s item in %s" + (if (= 0 minibuffer-history-position) "following" "preceding") + minibuffer-history-symbol) + (gmhist-goto narg)))) + +(defun gmhist-previous (n) + "Go to previous history position." + (interactive "p") + (gmhist-next (- n))) + +;; Searching the history + +(defun gmhist-search-backward (regexp &optional forward) + "Search backward in the history list for REGEXP. +With prefix argument, search for line that contains match for current line." + (interactive + (if current-prefix-arg + (list (regexp-quote (buffer-string))) + (let ((enable-recursive-minibuffers t)) + (list (read-with-history-in 'gmhist-search-history + "History search (regexp): "))))) + (let* (found + (direction (if forward -1 1)) + (pos (+ minibuffer-history-position direction)) ; find _next_ match! + (history (symbol-value minibuffer-history-symbol)) + (len (length history))) + (while (and (if forward (> pos 0) (<= pos len)) + (not (setq found + (string-match + regexp + (gmhist-stringify (nth (1- pos) history)))))) + (setq pos (+ pos direction))) + (or found (error "%s not found in %s" regexp minibuffer-history-symbol)) + (gmhist-goto pos))) + +(defun gmhist-search-forward (regexp &optional backward) + "Search forward in the history list for REGEXP. +With prefix argument, search for line that matches current line +instead of prompting for REGEXP." + (interactive + (if current-prefix-arg + (list (regexp-quote (buffer-string))) + (let ((enable-recursive-minibuffers t)) + (list (read-with-history-in 'gmhist-search-history + "History search forward (regexp): "))))) + (gmhist-search-backward regexp (not backward))) + +;; Misc. + +(defun gmhist-stringify (elt) + ;; If ELT is not a string, convert it to one. + (if (stringp elt) elt (prin1-to-string elt))) + +(defun gmhist-show () + "Show the history list in another buffer. +Use \\[scroll-other-window] to scroll, with negative arg to scroll back." + (interactive) + (let ((count 0)) + (with-output-to-temp-buffer (concat "*" (symbol-name minibuffer-history-symbol) "*") + (mapcar + (function + (lambda (x) + (princ (format "%2s%2d: %s\n" + (if (eq (setq count (1+ count)) + minibuffer-history-position) + "> " + " ") + count x)))) + (symbol-value minibuffer-history-symbol))))) + +(defun gmhist-remember-zero () + "Put this function on gmhist-before-move-hook to make gmhist +remember the initial value even after you edited it: + + (setq gmhist-before-move-hook 'gmhist-remember-zero)" + (if (zerop minibuffer-history-position) + (setq minibuffer-initial-contents (buffer-string)))) + +;; Hack up interactive specifications of existing functions + +(defun gmhist-copy-function (fun) + (let ((old (gmhist-symbol-function fun))) + (if (consp old) ; interpreted, or v18 compiled + ;; copy-sequence does not copy recursively. + ;; Iteration is faster than recursion, and we need just two levels + ;; to be able to use setcdr to mung the interactive spec. + (let (new elt) + (while old + (setq elt (car old) + old (cdr old) + new (cons (if (sequencep elt) + (copy-sequence elt) + elt) + new))) + (nreverse new)) + ;; else v19 compiled + (let ((new (append old nil))) + (setcar (nthcdr 5 new) (copy-sequence (aref old 5))) + (apply 'make-byte-code new))))) + +(defun gmhist-check-autoload (fun) + "If FUN is an autoload, load its definition." + (let ((lis (symbol-function fun))) + (if (and (listp lis) ; FUN could also be a subr + (eq 'autoload (car lis))) + (load (nth 1 lis))))) + +(defun gmhist-replace-spec (fun new-spec &optional copy-first) + "Replace the interactive specification of FUN with NEW-SPEC. +FUN must be a symbol with a function definition. +Autoload functions are taken care of by loading the appropriate file first. +If FUN is a pure storage function (one dumped into Emacs) it is first + copied onto itself, because pure storage cannot be modified. + Optional non-nil third arg COPY-FIRST is used internally for this. +The old spec is put on FUN's gmhist-old-interactive-spec property. + That property is never overwritten by this function. It is used by + function gmhist-remove-magic." + (gmhist-check-autoload fun) + (if copy-first ; copy (from pure storage) + (fset fun (gmhist-copy-function fun))) + (let* ((flambda (gmhist-symbol-function fun)) + (fint (and (consp flambda) + (if (eq 'interactive (car-safe (nth 2 flambda))) + (nth 2 flambda) + (if (eq 'interactive (car-safe (nth 3 flambda))) + (nth 3 flambda) + (error "%s is not interactive" fun))))) + (old-spec (if fint + (nth 1 fint) + (gmhist-spec fun)))) + ;; Save old interactive spec as property of FUN: + (or (get fun 'gmhist-old-interactive-spec) + (put fun 'gmhist-old-interactive-spec old-spec)) + ;; Replace '(interactive OLD-SPEC) with '(interactive NEW-SPEC) + (if copy-first + ;; This should not fail - if it does, we must abort. + (if (consp flambda) + (setcdr fint (list new-spec)) + ;; can't "aset" a #<byte-code> object, though aref works... + (setq flambda (append flambda nil)) + (setcar (nthcdr 5 flambda) new-spec) + (setq flambda (apply 'make-byte-code flambda)) + (fset fun flambda)) + ;; else prepare for a second try + (condition-case err + (setcdr fint (list new-spec)) + (error + ;; Setcdr bombs on preloaded functions: + ;; (error "Attempt to modify read-only object") + ;; There seems to be no simple way to test whether an object + ;; resides in pure storage, so we let it bomb and try again + ;; after copying it into writable storage. + (gmhist-replace-spec fun new-spec t)))))) + +(defun gmhist-spec (fun) + "Get the current interactive specification for FUN (a symbol). +Signal an error if FUN is not interactive." + (let ((flambda (gmhist-symbol-function fun)) + fint) + (cond ((consp flambda) ; interpreted, or v18 compiled + ;; do it exactly like call-interactively, even if this + ;; means (interactive...) can come arbitrary late in FUN's body + (setq fint (assq 'interactive (cdr (cdr flambda)))) + (or fint + (error "Cannot get spec of a non-interactive command: %s!" fun)) + (nth 1 fint)) + (t ; otherwise it's a v19 compiled-code object + (aref flambda 5))))) + +(defun gmhist-symbol-function (fun) + ;; Return FUN's ultimate definition. + ;; Recurse if FUN is fset to another function's name. + (let ((flambda (symbol-function fun))) + (if (symbolp flambda) + ;; Prefer recursion over while because infinite loop is caught + ;; by max-lisp-eval-depth. + (gmhist-symbol-function flambda) + flambda))) + +;; Automagic gmhistification + +;; There should be a builtin split function - inverse to mapconcat. +(defun gmhist-split (pat str &optional limit) + "Splitting on regexp PAT, turn string STR into a list of substrings. +Optional third arg LIMIT (>= 1) is a limit to the length of the +resulting list. +Thus, if SEP is a regexp that only matches itself, + + (mapconcat 'identity (gmhist-split SEP STRING) SEP) + +is always equal to STRING." + (let* ((start (string-match pat str)) + (result (list (substring str 0 start))) + (count 1) + (end (if start (match-end 0)))) + (if end ; else nothing left + (while (and (or (not (integerp limit)) + (< count limit)) + (string-match pat str end)) + (setq start (match-beginning 0) + count (1+ count) + result (cons (substring str end start) result) + end (match-end 0) + start end) + )) + (if (and (or (not (integerp limit)) + (< count limit)) + end) ; else nothing left + (setq result + (cons (substring str end) result))) + (nreverse result))) + +(defun gmhist-interactive (spec hist) + "Interpret SPEC, an interactive string, like call-interactively +would, only with minibuffer history in HIST (a symbol). + +If the value of HIST is another symbol (which can never happen if +history lists are already stored on it), this symbol is taken instead +to facilitate dynamic indirections. + +Currently recognized key letters are: + + a b B c C d D k m N n s S x X f F r p P v + +and initial `*'. + +Use it inside interactive like this + + \(interactive \(gmhist-interactive \"sPrompt: \\nP\" 'foo-history\)\) + +or even like this: + + \(interactive + \(gmhist-interactive \"sReplace: \\nsReplace %s with: \" 'replace-history\)\) +" + (or (stringp spec) + (error "gmhist-interactive: not a string %s" spec)) + (if (and (> (length spec) 0) (eq ?\* (aref spec 0))) + (progn + (barf-if-buffer-read-only) + (setq spec (substring spec 1)))) + (if (and (boundp hist) + (symbolp (symbol-value hist)) + (not (null (symbol-value hist)))) + (setq hist (symbol-value hist))) + (let ((spec-list (mapcar '(lambda (x) + ;; forgive empty entries like + ;; call-interactively does: + (if (equal "" x) + nil + (cons (aref x 0) (substring x 1)))) + (gmhist-split "\n" spec))) + cur-arg args-so-far special elt char prompt xprompt) + (setq spec-list (delq nil spec-list)) + (while spec-list + (setq elt (car spec-list) + spec-list (cdr spec-list) + special nil ; special handling of args-so-far + char (car elt) + prompt (cdr elt) + xprompt (apply (function format) prompt (reverse args-so-far))) + (cond ((eq char ?a) ; Symbol defined as a function + (setq cur-arg (intern + (completing-read-with-history-in + hist xprompt obarray 'fboundp t nil)))) + ((eq char ?b) ; Name of existing buffer + (setq cur-arg (read-buffer-with-history-in + hist xprompt (other-buffer) t))) + ((eq char ?B) ; Name of possibly non-existing buffer + (setq cur-arg (read-buffer-with-history-in + hist xprompt (other-buffer) nil))) + ((eq char ?c) ; Character + (message xprompt) ; history doesn't make sense for this + (setq cur-arg (read-char))) + ((eq char ?C) ; Command + (setq cur-arg (intern + (completing-read-with-history-in + hist xprompt obarray 'commandp t nil)))) + ((eq char ?d) ; Value of point. Does not do I/O. + (setq cur-arg (point))) + ((eq char ?D) ; directory name + ;; This does not check file-directory-p, but neither does + ;; call-interactively. + (setq cur-arg (read-file-name-with-history-in + hist + xprompt + nil + default-directory + 'confirm))) + ((eq char ?f) ; existing file name + (setq cur-arg (read-file-name-with-history-in + hist + xprompt + nil nil 'confirm))) + ((eq char ?F) ; possibly nonexistent file name + (setq cur-arg (read-file-name-with-history-in + hist + xprompt))) + ((eq char ?k) ; Key sequence (string) + (setq cur-arg (read-key-sequence (if (equal xprompt "") + nil xprompt)))) + ((eq char ?m) ; Value of mark. Does not do I/O. + (setq cur-arg (or (mark) (error "The mark is not set now")))) + ((eq char ?N) ; Prefix arg, else number from minibuf + (if current-prefix-arg + (setq cur-arg (prefix-numeric-value current-prefix-arg)) + (while (not (integerp + (setq cur-arg + (read-with-history-in hist xprompt nil t))))))) + ((eq char ?n) ; Read number from minibuffer + (while (not (integerp + (setq cur-arg + (read-with-history-in hist xprompt nil t)))))) + ((eq char ?p) ; cooked prefix arg + (setq cur-arg (prefix-numeric-value current-prefix-arg))) + ((eq char ?P) ; raw prefix arg + (setq cur-arg current-prefix-arg)) + ((eq char ?r) ; region + (let (region-min region-max) + ;; take some pains to behave exactly like interactive "r" + (setq region-min (min (or (mark) + (error "The mark is not set now")) + (point)) + region-max (max (or (mark) + (error "The mark is not set now")) + (point))) + (setq args-so-far + (append (list region-max region-min) args-so-far) + special t))) + ((eq char '?s) ; string + (setq cur-arg (read-with-history-in hist xprompt))) + ((eq char ?S) ; any symbol + (setq cur-arg (read-with-history-in hist xprompt nil t))) + ((eq char ?v) ; Variable name + (setq cur-arg (completing-read-with-history-in + hist xprompt obarray 'user-variable-p t nil))) + ((memq char '(?x ?X)) ; lisp expression + (setq cur-arg (read-with-history-in + hist + xprompt + nil + ;; have to tell gmhist to read s-exps + ;; instead of strings: + t)) + (if (eq char ?X) ; lisp expression, evaluated + (setq cur-arg (eval cur-arg)))) + + (t + (error "Invalid control letter `%c' in gmhist-interactive" char))) + (or special + (setq args-so-far (cons cur-arg args-so-far)))) + (reverse args-so-far))) + +(defun gmhist-new-spec (fun &optional hist no-error) + "Return a new interactive specification for FUN, suitable for use +with setcdr in function gmhist-replace-spec. +Use symbol HIST to store the history. HIST defaults to `FUN-history'. +The returned spec does the same as the old one, only with history in HIST. + +If FUN is an autoload object, its file is loaded first. + +See function gmhist-interactive for a list of recognized interactive +keys letters. + +Unless optional third arg NO-ERROR is given, signals an error if FUN's +interactive string contains unknown key letters or has no interactive string. +With NO-ERROR, it returns nil." + (or hist (setq hist (intern (concat (symbol-name fun) "-history")))) + (gmhist-check-autoload fun) + (let ((spec (gmhist-spec fun))) + (if (stringp spec) + (list 'gmhist-interactive spec (list 'quote hist)) + (if no-error + nil + (error "Can't gmhistify %s's spec: %s" fun spec))))) + +(defun gmhist-make-magic (fun &optional hist) + "Make FUN magically maintain minibuffer history in symbol HIST. +HIST defaults to `FUN-history'. +This works by modifying the interactive specification, which must be a +string. For more complicated cases, see gmhist-replace-spec. +The magic goes away when you call gmhist-remove-magic on FUN." + (interactive "CPut gmhist magic on command: ") + (let ((new-spec (gmhist-new-spec fun hist t))) + (if new-spec + (gmhist-replace-spec fun new-spec) + ;; else there was some error. Try to find out if this is a retry. + (if (not (get fun 'gmhist-old-interactive-spec)) + (error "Too complicated for gmhist: %s" fun) + (message "Another attempt to put magic on %s..." fun) + (gmhist-remove-magic fun) ; will abort if not a retry + ;; This time we don't catch errors - magic or blow! + (gmhist-replace-spec fun (gmhist-new-spec fun hist)) + (message "Another attempt to put magic on %s...done." fun))))) + +(defun gmhist-remove-magic (fun) + "Remove the magic that gmhist-make-magic put on FUN, +restoring the old interactive spec." + (interactive "CRemove gmhist magic from command: ") + (gmhist-replace-spec + fun + (or (get fun 'gmhist-old-interactive-spec) + (error "Can't find %s's old interactive spec!" fun)))) + +;; Now make yourself magic +(gmhist-make-magic 'gmhist-make-magic 'gmhist-make-magic-history) +(gmhist-make-magic 'gmhist-remove-magic 'gmhist-make-magic-history) + + +;; Examples, pedagogic and serious ones. More in gmhist-app.el. + +;;(defun foo-command (cmd) +;; (interactive (list +;; (read-with-history-in 'foo-history "Foo: "))) +;; (message "Foo %s" cmd)) +;; +;; ;; The interactive clause could also have been the simpler +;; ;; (interactive (gmhist-interactive "sFoo: " 'foo-history)) +;; +;; +;;;(put 'foo-history 'hist-map minibuffer-local-map) ; disable motion ... +;;;(put 'foo-history 'hist-function 'gmhist-read-nohistory) ; and history +;; +;;(put 'foo-history 'hist-function nil) ; enable history ... +;;(put 'foo-history 'hist-map nil) ; and motion again +;; +;;(defun gmhist-read-nohistory (symbol prompt initial-input read) +;; "An example function to put on the hist-function property." +;; (message "read-nohistory...") +;; (sit-for 2) +;; (read-string prompt initial-input)) +;; +;; Example for reading file names: +;;(defun bar-command (cmd) +;; (interactive +;; (list +;; (read-file-name-with-history-in +;; ;; HIST-SYM PROMPT DIR DFLT MUSTMATCH +;; 'bar-history "Bar: " nil nil 'confirm))) +;; (message "Bar %s" cmd)) +;; +;; Example function to apply gmhist-make-magic to. +;; Compare the missing initial input in bar to the magic version of zod. +;;(defun zod-command (cmd) +;; (interactive "fZod: ") +;; (message "Zod %s" cmd)) + +;; Finally run the load-hook + +(run-hooks 'gmhist-load-hook) + +;; End of file gmhist.el