2000-10-29 Michael Kifer <kifer@cs.sunysb.edu>
[bpt/emacs.git] / lisp / eshell / em-alias.el
CommitLineData
affbf647
GM
1;;; em-alias --- creation and management of command aliases
2
faadfb0a 3;; Copyright (C) 1999, 2000 Free Software Foundation
affbf647 4
7de5b421
GM
5;; Author: John Wiegley <johnw@gnu.org>
6
affbf647
GM
7;; This file is part of GNU Emacs.
8
9;; GNU Emacs is free software; you can redistribute it and/or modify
10;; it under the terms of the GNU General Public License as published by
11;; the Free Software Foundation; either version 2, or (at your option)
12;; any later version.
13
14;; GNU Emacs is distributed in the hope that it will be useful,
15;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17;; GNU General Public License for more details.
18
19;; You should have received a copy of the GNU General Public License
20;; along with GNU Emacs; see the file COPYING. If not, write to the
21;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22;; Boston, MA 02111-1307, USA.
23
24(provide 'em-alias)
25
26(eval-when-compile (require 'esh-maint))
27
28(defgroup eshell-alias nil
29 "Command aliases allow for easy definition of alternate commands."
30 :tag "Command aliases"
493fa1c5 31 :link '(info-link "(eshell)Command aliases")
affbf647
GM
32 :group 'eshell-module)
33
34;;; Commentary:
35
36;; Command aliases greatly simplify the definition of new commands.
37;; They exist as an alternative to alias functions, which are
38;; otherwise quite superior, being more flexible and natural to the
39;; Emacs Lisp environment (if somewhat trickier to define; [Alias
40;; functions]).
41;;
42;;;_* Creating aliases
43;;
44;; The user interface is simple: type 'alias' followed by the command
45;; name followed by the definition. Argument references are made
46;; using '$1', '$2', etc., or '$*'. For example:
47;;
48;; alias ll 'ls -l $*'
49;;
50;; This will cause the command 'll NEWS' to be replaced by 'ls -l
51;; NEWS'. This is then passed back to the command parser for
52;; reparsing.{Only the command text specified in the alias definition
53;; will be reparsed. Argument references (such as '$*') are handled
54;; using variable values, which means that the expansion will not be
55;; reparsed, but used directly.}
56;;
57;; To delete an alias, specify its name without a definition:
58;;
59;; alias ll
60;;
61;; Aliases are written to disk immediately after being defined or
62;; deleted. The filename in which they are kept is defined by the
63;; following variable:
64
65(defcustom eshell-aliases-file (concat eshell-directory-name "alias")
66 "*The file in which aliases are kept.
67Whenever an alias is defined by the user, using the `alias' command,
68it will be written to this file. Thus, alias definitions (and
69deletions) are always permanent. This approach was chosen for the
70sake of simplicity, since that's pretty much the only benefit to be
71gained by using this module."
72 :type 'file
73 :group 'eshell-alias)
74
75;;;
76;; The format of this file is quite basic. It specifies the alias
77;; definitions in almost exactly the same way that the user entered
78;; them, minus any argument quoting (since interpolation is not done
79;; when the file is read). Hence, it is possible to add new aliases
80;; to the alias file directly, using a text editor rather than the
81;; `alias' command. Or, this method can be used for editing aliases
82;; that have already defined.
83;;
84;; Here is an example of a few different aliases, and they would
85;; appear in the aliases file:
86;;
87;; alias clean rm -fr **/.#*~
88;; alias commit cvs commit -m changes $*
89;; alias ll ls -l $*
90;; alias info (info)
91;; alias reindex glimpseindex -o ~/Mail
92;; alias compact for i in ~/Mail/**/*~*.bz2(Lk+50) { bzip2 -9v $i }
93;;
94;;;_* Auto-correction of bad commands
95;;
96;; When a user enters the same unknown command many times during a
97;; session, it is likely that they are experiencing a spelling
98;; difficulty associated with a certain command. To combat this,
99;; Eshell will offer to automatically define an alias for that
100;; mispelled command, once a given tolerance threshold has been
101;; reached.
102
103(defcustom eshell-bad-command-tolerance 3
104 "*The number of failed commands to ignore before creating an alias."
105 :type 'integer
493fa1c5 106 :link '(custom-manual "(eshell)Auto-correction of bad commands")
affbf647
GM
107 :group 'eshell-alias)
108
109;;;
110;; Whenever the same bad command name is encountered this many times,
111;; the user will be prompted in the minibuffer to provide an alias
112;; name. An alias definition will then be created which will result
113;; in an equal call to the correct name. In this way, Eshell
114;; gradually learns about the commands that the user mistypes
115;; frequently, and will automatically correct them!
116;;
117;; Note that a '$*' is automatically appended at the end of the alias
118;; definition, so that entering it is unnecessary when specifying the
119;; corrected command name.
120
121;;; Code:
122
123(defcustom eshell-alias-load-hook '(eshell-alias-initialize)
124 "*A hook that gets run when `eshell-alias' is loaded."
125 :type 'hook
126 :group 'eshell-alias)
127
128(defvar eshell-command-aliases-list nil
129 "A list of command aliases currently defined by the user.
130Each element of this alias is a list of the form:
131
132 (NAME DEFINITION)
133
134Where NAME is the textual name of the alias, and DEFINITION is the
135command string to replace that command with.
136
137Note: this list should not be modified in your '.emacs' file. Rather,
138any desired alias definitions should be declared using the `alias'
139command, which will automatically write them to the file named by
140`eshell-aliases-file'.")
141
142(put 'eshell-command-aliases-list 'risky-local-variable t)
143
144(defvar eshell-failed-commands-alist nil
145 "An alist of command name failures.")
146
147(defun eshell-alias-initialize ()
148 "Initialize the alias handling code."
149 (make-local-variable 'eshell-failed-commands-alist)
150 (make-local-hook 'eshell-alternate-command-hook)
151 (add-hook 'eshell-alternate-command-hook 'eshell-fix-bad-commands t t)
152 (eshell-read-aliases-list)
153 (make-local-hook 'eshell-named-command-hook)
154 (add-hook 'eshell-named-command-hook 'eshell-maybe-replace-by-alias t t))
155
156(defun eshell/alias (&optional alias &rest definition)
157 "Define an ALIAS in the user's alias list using DEFINITION."
158 (if (not alias)
159 (eshell-for alias eshell-command-aliases-list
160 (eshell-print (apply 'format "alias %s %s\n" alias)))
161 (if (not definition)
162 (setq eshell-command-aliases-list
163 (delq (assoc alias eshell-command-aliases-list)
164 eshell-command-aliases-list))
165 (and (stringp definition)
166 (set-text-properties 0 (length definition) nil definition))
167 (let ((def (assoc alias eshell-command-aliases-list))
168 (alias-def (list alias
169 (eshell-flatten-and-stringify definition))))
170 (if def
171 (setq eshell-command-aliases-list
172 (delq def eshell-command-aliases-list)))
173 (setq eshell-command-aliases-list
174 (cons alias-def eshell-command-aliases-list))))
175 (eshell-write-aliases-list))
176 nil)
177
178(defun pcomplete/eshell-mode/alias ()
179 "Completion function for Eshell's `alias' command."
180 (pcomplete-here (eshell-alias-completions pcomplete-stub)))
181
182(defun eshell-read-aliases-list ()
183 "Read in an aliases list from `eshell-aliases-file'."
184 (let ((file eshell-aliases-file))
185 (when (file-readable-p file)
186 (setq eshell-command-aliases-list
187 (with-temp-buffer
188 (let (eshell-command-aliases-list)
189 (insert-file-contents file)
190 (while (not (eobp))
191 (if (re-search-forward
192 "^alias\\s-+\\(\\S-+\\)\\s-+\\(.+\\)")
193 (setq eshell-command-aliases-list
194 (cons (list (match-string 1)
195 (match-string 2))
196 eshell-command-aliases-list)))
197 (forward-line 1))
198 eshell-command-aliases-list))))))
199
200(defun eshell-write-aliases-list ()
201 "Write out the current aliases into `eshell-aliases-file'."
202 (if (file-writable-p (file-name-directory eshell-aliases-file))
203 (let ((eshell-current-handles
204 (eshell-create-handles eshell-aliases-file 'overwrite)))
205 (eshell/alias)
206 (eshell-close-handles 0))))
207
208(defsubst eshell-lookup-alias (name)
209 "Check whether NAME is aliased. Return the alias if there is one."
210 (assoc name eshell-command-aliases-list))
211
212(defvar eshell-prevent-alias-expansion nil)
213
214(defun eshell-maybe-replace-by-alias (command args)
493fa1c5 215 "If COMMAND has an alias definition, call that instead using ARGS."
affbf647
GM
216 (unless (and eshell-prevent-alias-expansion
217 (member command eshell-prevent-alias-expansion))
218 (let ((alias (eshell-lookup-alias command)))
219 (if alias
220 (throw 'eshell-replace-command
221 (list
222 'let
223 (list
224 (list 'eshell-command-name
225 (list 'quote eshell-last-command-name))
226 (list 'eshell-command-arguments
227 (list 'quote eshell-last-arguments))
228 (list 'eshell-prevent-alias-expansion
229 (list 'quote
230 (cons command
231 eshell-prevent-alias-expansion))))
232 (eshell-parse-command (nth 1 alias))))))))
233
234(defun eshell-alias-completions (name)
235 "Find all possible completions for NAME.
236These are all the command aliases which begin with NAME."
237 (let (completions)
238 (eshell-for alias eshell-command-aliases-list
239 (if (string-match (concat "^" name) (car alias))
240 (setq completions (cons (car alias) completions))))
241 completions))
242
243(defun eshell-fix-bad-commands (name)
244 "If the user repeatedly a bad command NAME, make an alias for them."
245 (ignore
246 (unless (file-name-directory name)
247 (let ((entry (assoc name eshell-failed-commands-alist)))
248 (if (not entry)
249 (setq eshell-failed-commands-alist
250 (cons (cons name 1) eshell-failed-commands-alist))
251 (if (< (cdr entry) eshell-bad-command-tolerance)
252 (setcdr entry (1+ (cdr entry)))
253 (let ((alias (concat
254 (read-string
255 (format "Define alias for \"%s\": " name))
256 " $*")))
257 (eshell/alias name alias)
258 (throw 'eshell-replace-command
259 (list
260 'let
261 (list
262 (list 'eshell-command-name
263 (list 'quote name))
264 (list 'eshell-command-arguments
265 (list 'quote eshell-last-arguments))
266 (list 'eshell-prevent-alias-expansion
267 (list 'quote
268 (cons name
269 eshell-prevent-alias-expansion))))
270 (eshell-parse-command alias))))))))))
271
272;;; em-alias.el ends here