(query-replace-read-from): Use
[bpt/emacs.git] / lisp / eshell / em-unix.el
1 ;;; em-unix.el --- UNIX command aliases
2
3 ;; Copyright (C) 1999, 2000, 2001, 2004 Free Software Foundation
4
5 ;; Author: John Wiegley <johnw@gnu.org>
6
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-unix)
25
26 (eval-when-compile (require 'esh-maint))
27 (require 'eshell)
28
29 (defgroup eshell-unix nil
30 "This module defines many of the more common UNIX utilities as
31 aliases implemented in Lisp. These include mv, ln, cp, rm, etc. If
32 the user passes arguments which are too complex, or are unrecognized
33 by the Lisp variant, the external version will be called (if
34 available). The only reason not to use them would be because they are
35 usually much slower. But in several cases their tight integration
36 with Eshell makes them more versatile than their traditional cousins
37 \(such as being able to use `kill' to kill Eshell background processes
38 by name)."
39 :tag "UNIX commands in Lisp"
40 :group 'eshell-module)
41
42 ;;; Commentary:
43
44 ;; This file contains implementations of several UNIX command in Emacs
45 ;; Lisp, for several reasons:
46 ;;
47 ;; 1) it makes them available on all platforms where the Lisp
48 ;; functions used are available
49 ;;
50 ;; 2) it makes their functionality accessible and modified by the
51 ;; Lisp programmer.
52 ;;
53 ;; 3) it allows Eshell to refrain from having to invoke external
54 ;; processes for common operations.
55
56 (defcustom eshell-unix-load-hook '(eshell-unix-initialize)
57 "*A list of functions to run when `eshell-unix' is loaded."
58 :type 'hook
59 :group 'eshell-unix)
60
61 (defcustom eshell-plain-grep-behavior nil
62 "*If non-nil, standalone \"grep\" commands will behave normally.
63 Standalone in this context means not redirected, and not on the
64 receiving side of a command pipeline."
65 :type 'boolean
66 :group 'eshell-unix)
67
68 (defcustom eshell-no-grep-available (not (eshell-search-path "grep"))
69 "*If non-nil, no grep is available on the current machine."
70 :type 'boolean
71 :group 'eshell-unix)
72
73 (defcustom eshell-plain-diff-behavior nil
74 "*If non-nil, standalone \"diff\" commands will behave normally.
75 Standalone in this context means not redirected, and not on the
76 receiving side of a command pipeline."
77 :type 'boolean
78 :group 'eshell-unix)
79
80 (defcustom eshell-plain-locate-behavior (eshell-under-xemacs-p)
81 "*If non-nil, standalone \"locate\" commands will behave normally.
82 Standalone in this context means not redirected, and not on the
83 receiving side of a command pipeline."
84 :type 'boolean
85 :group 'eshell-unix)
86
87 (defcustom eshell-rm-removes-directories nil
88 "*If non-nil, `rm' will remove directory entries.
89 Otherwise, `rmdir' is required."
90 :type 'boolean
91 :group 'eshell-unix)
92
93 (defcustom eshell-rm-interactive-query (= (user-uid) 0)
94 "*If non-nil, `rm' will query before removing anything."
95 :type 'boolean
96 :group 'eshell-unix)
97
98 (defcustom eshell-mv-interactive-query (= (user-uid) 0)
99 "*If non-nil, `mv' will query before overwriting anything."
100 :type 'boolean
101 :group 'eshell-unix)
102
103 (defcustom eshell-mv-overwrite-files t
104 "*If non-nil, `mv' will overwrite files without warning."
105 :type 'boolean
106 :group 'eshell-unix)
107
108 (defcustom eshell-cp-interactive-query (= (user-uid) 0)
109 "*If non-nil, `cp' will query before overwriting anything."
110 :type 'boolean
111 :group 'eshell-unix)
112
113 (defcustom eshell-cp-overwrite-files t
114 "*If non-nil, `cp' will overwrite files without warning."
115 :type 'boolean
116 :group 'eshell-unix)
117
118 (defcustom eshell-ln-interactive-query (= (user-uid) 0)
119 "*If non-nil, `ln' will query before overwriting anything."
120 :type 'boolean
121 :group 'eshell-unix)
122
123 (defcustom eshell-ln-overwrite-files nil
124 "*If non-nil, `ln' will overwrite files without warning."
125 :type 'boolean
126 :group 'eshell-unix)
127
128 (defcustom eshell-default-target-is-dot nil
129 "*If non-nil, the default destination for cp, mv or ln is `.'."
130 :type 'boolean
131 :group 'eshell-unix)
132
133 (defcustom eshell-du-prefer-over-ange nil
134 "*Use Eshell's du in ange-ftp remote directories.
135 Otherwise, Emacs will attempt to use rsh to invoke du on the remote machine."
136 :type 'boolean
137 :group 'eshell-unix)
138
139 (require 'esh-opt)
140
141 ;;; Functions:
142
143 (defun eshell-unix-initialize ()
144 "Initialize the UNIX support/emulation code."
145 (when (eshell-using-module 'eshell-cmpl)
146 (add-hook 'pcomplete-try-first-hook
147 'eshell-complete-host-reference nil t))
148 (make-local-variable 'eshell-complex-commands)
149 (setq eshell-complex-commands
150 (append '("grep" "egrep" "fgrep" "agrep" "glimpse" "locate"
151 "cat" "time" "cp" "mv" "make" "du" "diff")
152 eshell-complex-commands)))
153
154 (defalias 'eshell/date 'current-time-string)
155 (defalias 'eshell/basename 'file-name-nondirectory)
156 (defalias 'eshell/dirname 'file-name-directory)
157
158 (eval-when-compile
159 (defvar interactive)
160 (defvar preview)
161 (defvar recursive)
162 (defvar verbose))
163
164 (defun eshell/man (&rest args)
165 "Invoke man, flattening the arguments appropriately."
166 (funcall 'man (apply 'eshell-flatten-and-stringify args)))
167
168 (put 'eshell/man 'eshell-no-numeric-conversions t)
169
170 (defun eshell-remove-entries (path files &optional top-level)
171 "From PATH, remove all of the given FILES, perhaps interactively."
172 (while files
173 (if (string-match "\\`\\.\\.?\\'"
174 (file-name-nondirectory (car files)))
175 (if top-level
176 (eshell-error "rm: cannot remove `.' or `..'\n"))
177 (if (and (file-directory-p (car files))
178 (not (file-symlink-p (car files))))
179 (let ((dir (file-name-as-directory (car files))))
180 (eshell-remove-entries dir
181 (mapcar
182 (function
183 (lambda (file)
184 (concat dir file)))
185 (directory-files dir)))
186 (if verbose
187 (eshell-printn (format "rm: removing directory `%s'"
188 (car files))))
189 (unless
190 (or preview
191 (and interactive
192 (not (y-or-n-p
193 (format "rm: remove directory `%s'? "
194 (car files))))))
195 (eshell-funcalln 'delete-directory (car files))))
196 (if verbose
197 (eshell-printn (format "rm: removing file `%s'"
198 (car files))))
199 (unless (or preview
200 (and interactive
201 (not (y-or-n-p
202 (format "rm: remove `%s'? "
203 (car files))))))
204 (eshell-funcalln 'delete-file (car files)))))
205 (setq files (cdr files))))
206
207 (defun eshell/rm (&rest args)
208 "Implementation of rm in Lisp.
209 This is implemented to call either `delete-file', `kill-buffer',
210 `kill-process', or `unintern', depending on the nature of the
211 argument."
212 (setq args (eshell-flatten-list args))
213 (eshell-eval-using-options
214 "rm" args
215 '((?h "help" nil nil "show this usage screen")
216 (?f "force" nil force-removal "force removal")
217 (?i "interactive" nil interactive "prompt before any removal")
218 (?n "preview" nil preview "don't change anything on disk")
219 (?r "recursive" nil recursive
220 "remove the contents of directories recursively")
221 (?R nil nil recursive "(same)")
222 (?v "verbose" nil verbose "explain what is being done")
223 :preserve-args
224 :external "rm"
225 :show-usage
226 :usage "[OPTION]... FILE...
227 Remove (unlink) the FILE(s).")
228 (unless interactive
229 (setq interactive eshell-rm-interactive-query))
230 (if (and force-removal interactive)
231 (setq interactive nil))
232 (while args
233 (let ((entry (if (stringp (car args))
234 (directory-file-name (car args))
235 (if (numberp (car args))
236 (number-to-string (car args))
237 (car args)))))
238 (cond
239 ((bufferp entry)
240 (if verbose
241 (eshell-printn (format "rm: removing buffer `%s'" entry)))
242 (unless (or preview
243 (and interactive
244 (not (y-or-n-p (format "rm: delete buffer `%s'? "
245 entry)))))
246 (eshell-funcalln 'kill-buffer entry)))
247 ((eshell-processp entry)
248 (if verbose
249 (eshell-printn (format "rm: killing process `%s'" entry)))
250 (unless (or preview
251 (and interactive
252 (not (y-or-n-p (format "rm: kill process `%s'? "
253 entry)))))
254 (eshell-funcalln 'kill-process entry)))
255 ((symbolp entry)
256 (if verbose
257 (eshell-printn (format "rm: uninterning symbol `%s'" entry)))
258 (unless
259 (or preview
260 (and interactive
261 (not (y-or-n-p (format "rm: unintern symbol `%s'? "
262 entry)))))
263 (eshell-funcalln 'unintern entry)))
264 ((stringp entry)
265 (if (and (file-directory-p entry)
266 (not (file-symlink-p entry)))
267 (if (or recursive
268 eshell-rm-removes-directories)
269 (if (or preview
270 (not interactive)
271 (y-or-n-p
272 (format "rm: descend into directory `%s'? "
273 entry)))
274 (eshell-remove-entries nil (list entry) t))
275 (eshell-error (format "rm: %s: is a directory\n" entry)))
276 (eshell-remove-entries nil (list entry) t)))))
277 (setq args (cdr args)))
278 nil))
279
280 (put 'eshell/rm 'eshell-no-numeric-conversions t)
281
282 (defun eshell/mkdir (&rest args)
283 "Implementation of mkdir in Lisp."
284 (eshell-eval-using-options
285 "mkdir" args
286 '((?h "help" nil nil "show this usage screen")
287 :external "mkdir"
288 :show-usage
289 :usage "[OPTION] DIRECTORY...
290 Create the DIRECTORY(ies), if they do not already exist.")
291 (while args
292 (eshell-funcalln 'make-directory (car args))
293 (setq args (cdr args)))
294 nil))
295
296 (put 'eshell/mkdir 'eshell-no-numeric-conversions t)
297
298 (defun eshell/rmdir (&rest args)
299 "Implementation of rmdir in Lisp."
300 (eshell-eval-using-options
301 "rmdir" args
302 '((?h "help" nil nil "show this usage screen")
303 :external "rmdir"
304 :show-usage
305 :usage "[OPTION] DIRECTORY...
306 Remove the DIRECTORY(ies), if they are empty.")
307 (while args
308 (eshell-funcalln 'delete-directory (car args))
309 (setq args (cdr args)))
310 nil))
311
312 (put 'eshell/rmdir 'eshell-no-numeric-conversions t)
313
314 (eval-when-compile
315 (defvar no-dereference)
316 (defvar preview)
317 (defvar verbose))
318
319 (defvar eshell-warn-dot-directories t)
320
321 (defun eshell-shuffle-files (command action files target func deep &rest args)
322 "Shuffle around some filesystem entries, using FUNC to do the work."
323 (let ((attr-target (eshell-file-attributes target))
324 (is-dir (or (file-directory-p target)
325 (and preview (not eshell-warn-dot-directories))))
326 attr)
327 (if (and (not preview) (not is-dir)
328 (> (length files) 1))
329 (error "%s: when %s multiple files, last argument must be a directory"
330 command action))
331 (while files
332 (setcar files (directory-file-name (car files)))
333 (cond
334 ((string-match "\\`\\.\\.?\\'"
335 (file-name-nondirectory (car files)))
336 (if eshell-warn-dot-directories
337 (eshell-error (format "%s: %s: omitting directory\n"
338 command (car files)))))
339 ((and attr-target
340 (or (not (eshell-under-windows-p))
341 (eq system-type 'ms-dos))
342 (setq attr (eshell-file-attributes (car files)))
343 (nth 10 attr-target) (nth 10 attr)
344 ;; Use equal, not -, since the inode and the device could
345 ;; cons cells.
346 (equal (nth 10 attr-target) (nth 10 attr))
347 (nth 11 attr-target) (nth 11 attr)
348 (equal (nth 11 attr-target) (nth 11 attr)))
349 (eshell-error (format "%s: `%s' and `%s' are the same file\n"
350 command (car files) target)))
351 (t
352 (let ((source (car files))
353 (target (if is-dir
354 (expand-file-name
355 (file-name-nondirectory (car files)) target)
356 target))
357 link)
358 (if (and (file-directory-p source)
359 (or (not no-dereference)
360 (not (file-symlink-p source)))
361 (not (memq func '(make-symbolic-link
362 add-name-to-file))))
363 (if (and (eq func 'copy-file)
364 (not recursive))
365 (eshell-error (format "%s: %s: omitting directory\n"
366 command (car files)))
367 (let (eshell-warn-dot-directories)
368 (if (and (not deep)
369 (eq func 'rename-file)
370 ;; Use equal, since the device might be a
371 ;; cons cell.
372 (equal (nth 11 (eshell-file-attributes
373 (file-name-directory
374 (directory-file-name
375 (expand-file-name source)))))
376 (nth 11 (eshell-file-attributes
377 (file-name-directory
378 (directory-file-name
379 (expand-file-name target)))))))
380 (apply 'eshell-funcalln func source target args)
381 (unless (file-directory-p target)
382 (if verbose
383 (eshell-printn
384 (format "%s: making directory %s"
385 command target)))
386 (unless preview
387 (eshell-funcalln 'make-directory target)))
388 (apply 'eshell-shuffle-files
389 command action
390 (mapcar
391 (function
392 (lambda (file)
393 (concat source "/" file)))
394 (directory-files source))
395 target func t args)
396 (when (eq func 'rename-file)
397 (if verbose
398 (eshell-printn
399 (format "%s: deleting directory %s"
400 command source)))
401 (unless preview
402 (eshell-funcalln 'delete-directory source))))))
403 (if verbose
404 (eshell-printn (format "%s: %s -> %s" command
405 source target)))
406 (unless preview
407 (if (and no-dereference
408 (setq link (file-symlink-p source)))
409 (progn
410 (apply 'eshell-funcalln 'make-symbolic-link
411 link target args)
412 (if (eq func 'rename-file)
413 (if (and (file-directory-p source)
414 (not (file-symlink-p source)))
415 (eshell-funcalln 'delete-directory source)
416 (eshell-funcalln 'delete-file source))))
417 (apply 'eshell-funcalln func source target args)))))))
418 (setq files (cdr files)))))
419
420 (defun eshell-shorthand-tar-command (command args)
421 "Rewrite `cp -v dir a.tar.gz' to `tar cvzf a.tar.gz dir'."
422 (let* ((archive (car (last args)))
423 (tar-args
424 (cond ((string-match "z2" archive) "If")
425 ((string-match "gz" archive) "zf")
426 ((string-match "\\(az\\|Z\\)" archive) "Zf")
427 (t "f"))))
428 (if (file-exists-p archive)
429 (setq tar-args (concat "u" tar-args))
430 (setq tar-args (concat "c" tar-args)))
431 (if verbose
432 (setq tar-args (concat "v" tar-args)))
433 (if (equal command "mv")
434 (setq tar-args (concat "--remove-files -" tar-args)))
435 ;; truncate the archive name from the arguments
436 (setcdr (last args 2) nil)
437 (throw 'eshell-replace-command
438 (eshell-parse-command
439 (format "tar %s %s" tar-args archive) args))))
440
441 ;; this is to avoid duplicating code...
442 (defmacro eshell-mvcpln-template (command action func query-var
443 force-var &optional preserve)
444 `(let ((len (length args)))
445 (if (or (= len 0)
446 (and (= len 1) (null eshell-default-target-is-dot)))
447 (error "%s: missing destination file or directory" ,command))
448 (if (= len 1)
449 (nconc args '(".")))
450 (setq args (eshell-stringify-list (eshell-flatten-list args)))
451 (if (and ,(not (equal command "ln"))
452 (string-match eshell-tar-regexp (car (last args)))
453 (or (> (length args) 2)
454 (and (file-directory-p (car args))
455 (or (not no-dereference)
456 (not (file-symlink-p (car args)))))))
457 (eshell-shorthand-tar-command ,command args)
458 (let ((target (car (last args)))
459 ange-cache)
460 (setcdr (last args 2) nil)
461 (eshell-shuffle-files
462 ,command ,action args target ,func nil
463 ,@(append
464 `((if (and (or interactive
465 ,query-var)
466 (not force))
467 1 (or force ,force-var)))
468 (if preserve
469 (list preserve)))))
470 nil)))
471
472 (defun eshell/mv (&rest args)
473 "Implementation of mv in Lisp."
474 (eshell-eval-using-options
475 "mv" args
476 '((?f "force" nil force
477 "remove existing destinations, never prompt")
478 (?i "interactive" nil interactive
479 "request confirmation if target already exists")
480 (?n "preview" nil preview
481 "don't change anything on disk")
482 (?v "verbose" nil verbose
483 "explain what is being done")
484 (nil "help" nil nil "show this usage screen")
485 :preserve-args
486 :external "mv"
487 :show-usage
488 :usage "[OPTION]... SOURCE DEST
489 or: mv [OPTION]... SOURCE... DIRECTORY
490 Rename SOURCE to DEST, or move SOURCE(s) to DIRECTORY.
491 \[OPTION] DIRECTORY...")
492 (let ((no-dereference t))
493 (eshell-mvcpln-template "mv" "moving" 'rename-file
494 eshell-mv-interactive-query
495 eshell-mv-overwrite-files))))
496
497 (put 'eshell/mv 'eshell-no-numeric-conversions t)
498
499 (defun eshell/cp (&rest args)
500 "Implementation of cp in Lisp."
501 (eshell-eval-using-options
502 "cp" args
503 '((?a "archive" nil archive
504 "same as -dpR")
505 (?d "no-dereference" nil no-dereference
506 "preserve links")
507 (?f "force" nil force
508 "remove existing destinations, never prompt")
509 (?i "interactive" nil interactive
510 "request confirmation if target already exists")
511 (?n "preview" nil preview
512 "don't change anything on disk")
513 (?p "preserve" nil preserve
514 "preserve file attributes if possible")
515 (?R "recursive" nil recursive
516 "copy directories recursively")
517 (?v "verbose" nil verbose
518 "explain what is being done")
519 (nil "help" nil nil "show this usage screen")
520 :preserve-args
521 :external "cp"
522 :show-usage
523 :usage "[OPTION]... SOURCE DEST
524 or: cp [OPTION]... SOURCE... DIRECTORY
525 Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.")
526 (if archive
527 (setq preserve t no-dereference t recursive t))
528 (eshell-mvcpln-template "cp" "copying" 'copy-file
529 eshell-cp-interactive-query
530 eshell-cp-overwrite-files preserve)))
531
532 (put 'eshell/cp 'eshell-no-numeric-conversions t)
533
534 (defun eshell/ln (&rest args)
535 "Implementation of ln in Lisp."
536 (eshell-eval-using-options
537 "ln" args
538 '((?h "help" nil nil "show this usage screen")
539 (?s "symbolic" nil symbolic
540 "make symbolic links instead of hard links")
541 (?i "interactive" nil interactive
542 "request confirmation if target already exists")
543 (?f "force" nil force "remove existing destinations, never prompt")
544 (?n "preview" nil preview
545 "don't change anything on disk")
546 (?v "verbose" nil verbose "explain what is being done")
547 :preserve-args
548 :external "ln"
549 :show-usage
550 :usage "[OPTION]... TARGET [LINK_NAME]
551 or: ln [OPTION]... TARGET... DIRECTORY
552 Create a link to the specified TARGET with optional LINK_NAME. If there is
553 more than one TARGET, the last argument must be a directory; create links
554 in DIRECTORY to each TARGET. Create hard links by default, symbolic links
555 with '--symbolic'. When creating hard links, each TARGET must exist.")
556 (let ((no-dereference t))
557 (eshell-mvcpln-template "ln" "linking"
558 (if symbolic
559 'make-symbolic-link
560 'add-name-to-file)
561 eshell-ln-interactive-query
562 eshell-ln-overwrite-files))))
563
564 (put 'eshell/ln 'eshell-no-numeric-conversions t)
565
566 (defun eshell/cat (&rest args)
567 "Implementation of cat in Lisp.
568 If in a pipeline, or the file is not a regular file, directory or
569 symlink, then revert to the system's definition of cat."
570 (setq args (eshell-stringify-list (eshell-flatten-list args)))
571 (if (or eshell-in-pipeline-p
572 (catch 'special
573 (eshell-for arg args
574 (unless (or (and (stringp arg)
575 (> (length arg) 0)
576 (eq (aref arg 0) ?-))
577 (let ((attrs (eshell-file-attributes arg)))
578 (and attrs (memq (aref (nth 8 attrs) 0)
579 '(?d ?l ?-)))))
580 (throw 'special t)))))
581 (let ((ext-cat (eshell-search-path "cat")))
582 (if ext-cat
583 (throw 'eshell-replace-command
584 (eshell-parse-command ext-cat args))
585 (if eshell-in-pipeline-p
586 (error "Eshell's `cat' does not work in pipelines")
587 (error "Eshell's `cat' cannot display one of the files given"))))
588 (eshell-init-print-buffer)
589 (eshell-eval-using-options
590 "cat" args
591 '((?h "help" nil nil "show this usage screen")
592 :external "cat"
593 :show-usage
594 :usage "[OPTION] FILE...
595 Concatenate FILE(s), or standard input, to standard output.")
596 (eshell-for file args
597 (if (string= file "-")
598 (throw 'eshell-external
599 (eshell-external-command "cat" args))))
600 (let ((curbuf (current-buffer)))
601 (eshell-for file args
602 (with-temp-buffer
603 (insert-file-contents file)
604 (goto-char (point-min))
605 (while (not (eobp))
606 (let ((str (buffer-substring
607 (point) (min (1+ (line-end-position))
608 (point-max)))))
609 (with-current-buffer curbuf
610 (eshell-buffered-print str)))
611 (forward-line)))))
612 (eshell-flush)
613 ;; if the file does not end in a newline, do not emit one
614 (setq eshell-ensure-newline-p nil))))
615
616 (put 'eshell/cat 'eshell-no-numeric-conversions t)
617
618 ;; special front-end functions for compilation-mode buffers
619
620 (defun eshell/make (&rest args)
621 "Use `compile' to do background makes."
622 (if (and eshell-current-subjob-p
623 (eshell-interactive-output-p))
624 (let ((compilation-process-setup-function
625 (list 'lambda nil
626 (list 'setq 'process-environment
627 (list 'quote (eshell-copy-environment))))))
628 (compile (concat "make " (eshell-flatten-and-stringify args))))
629 (throw 'eshell-replace-command
630 (eshell-parse-command "*make" (eshell-stringify-list
631 (eshell-flatten-list args))))))
632
633 (put 'eshell/make 'eshell-no-numeric-conversions t)
634
635 (defun eshell-occur-mode-goto-occurrence ()
636 "Go to the occurrence the current line describes."
637 (interactive)
638 (let ((pos (occur-mode-find-occurrence)))
639 (pop-to-buffer (marker-buffer pos))
640 (goto-char (marker-position pos))))
641
642 (defun eshell-occur-mode-mouse-goto (event)
643 "In Occur mode, go to the occurrence whose line you click on."
644 (interactive "e")
645 (let (pos)
646 (save-excursion
647 (set-buffer (window-buffer (posn-window (event-end event))))
648 (save-excursion
649 (goto-char (posn-point (event-end event)))
650 (setq pos (occur-mode-find-occurrence))))
651 (pop-to-buffer (marker-buffer pos))
652 (goto-char (marker-position pos))))
653
654 (defun eshell-poor-mans-grep (args)
655 "A poor version of grep that opens every file and uses `occur'.
656 This eats up memory, since it leaves the buffers open (to speed future
657 searches), and it's very slow. But, if your system has no grep
658 available..."
659 (save-selected-window
660 (let ((default-dir default-directory))
661 (with-current-buffer (get-buffer-create "*grep*")
662 (let ((inhibit-read-only t)
663 (default-directory default-dir))
664 (erase-buffer)
665 (occur-mode)
666 (let ((files (eshell-stringify-list
667 (eshell-flatten-list (cdr args))))
668 (inhibit-redisplay t)
669 string)
670 (when (car args)
671 (if (get-buffer "*Occur*")
672 (kill-buffer (get-buffer "*Occur*")))
673 (setq string nil)
674 (while files
675 (with-current-buffer (find-file-noselect (car files))
676 (save-excursion
677 (ignore-errors
678 (occur (car args))))
679 (if (get-buffer "*Occur*")
680 (with-current-buffer (get-buffer "*Occur*")
681 (setq string (buffer-string))
682 (kill-buffer (current-buffer)))))
683 (if string (insert string))
684 (setq string nil
685 files (cdr files)))))
686 (local-set-key [mouse-2] 'eshell-occur-mode-mouse-goto)
687 (local-set-key [(control ?c) (control ?c)]
688 'eshell-occur-mode-goto-occurrence)
689 (local-set-key [(control ?m)]
690 'eshell-occur-mode-goto-occurrence)
691 (local-set-key [return] 'eshell-occur-mode-goto-occurrence)
692 (pop-to-buffer (current-buffer) t)
693 (goto-char (point-min))
694 (resize-temp-buffer-window))))))
695
696 (defun eshell-grep (command args &optional maybe-use-occur)
697 "Generic service function for the various grep aliases.
698 It calls Emacs' grep utility if the command is not redirecting output,
699 and if it's not part of a command pipeline. Otherwise, it calls the
700 external command."
701 (if (and maybe-use-occur eshell-no-grep-available)
702 (eshell-poor-mans-grep args)
703 (if (or eshell-plain-grep-behavior
704 (not (and (eshell-interactive-output-p)
705 (not eshell-in-pipeline-p)
706 (not eshell-in-subcommand-p))))
707 (throw 'eshell-replace-command
708 (eshell-parse-command (concat "*" command)
709 (eshell-stringify-list
710 (eshell-flatten-list args))))
711 (let* ((compilation-process-setup-function
712 (list 'lambda nil
713 (list 'setq 'process-environment
714 (list 'quote (eshell-copy-environment)))))
715 (args (mapconcat 'identity
716 (mapcar 'shell-quote-argument
717 (eshell-stringify-list
718 (eshell-flatten-list args)))
719 " "))
720 (cmd (progn
721 (set-text-properties 0 (length args)
722 '(invisible t) args)
723 (format "%s -n %s" command args)))
724 compilation-scroll-output)
725 (grep cmd)))))
726
727 (defun eshell/grep (&rest args)
728 "Use Emacs grep facility instead of calling external grep."
729 (eshell-grep "grep" args t))
730
731 (defun eshell/egrep (&rest args)
732 "Use Emacs grep facility instead of calling external egrep."
733 (eshell-grep "egrep" args t))
734
735 (defun eshell/fgrep (&rest args)
736 "Use Emacs grep facility instead of calling external fgrep."
737 (eshell-grep "fgrep" args t))
738
739 (defun eshell/agrep (&rest args)
740 "Use Emacs grep facility instead of calling external agrep."
741 (eshell-grep "agrep" args))
742
743 (defun eshell/glimpse (&rest args)
744 "Use Emacs grep facility instead of calling external glimpse."
745 (let (null-device)
746 (eshell-grep "glimpse" (append '("-z" "-y") args))))
747
748 ;; completions rules for some common UNIX commands
749
750 (defsubst eshell-complete-hostname ()
751 "Complete a command that wants a hostname for an argument."
752 (pcomplete-here (eshell-read-host-names)))
753
754 (defun eshell-complete-host-reference ()
755 "If there is a host reference, complete it."
756 (let ((arg (pcomplete-actual-arg))
757 index)
758 (when (setq index (string-match "@[a-z.]*\\'" arg))
759 (setq pcomplete-stub (substring arg (1+ index))
760 pcomplete-last-completion-raw t)
761 (throw 'pcomplete-completions (eshell-read-host-names)))))
762
763 (defalias 'pcomplete/ftp 'eshell-complete-hostname)
764 (defalias 'pcomplete/ncftp 'eshell-complete-hostname)
765 (defalias 'pcomplete/ping 'eshell-complete-hostname)
766 (defalias 'pcomplete/rlogin 'eshell-complete-hostname)
767
768 (defun pcomplete/telnet ()
769 (require 'pcmpl-unix)
770 (pcomplete-opt "xl(pcmpl-unix-user-names)")
771 (eshell-complete-hostname))
772
773 (defun pcomplete/rsh ()
774 "Complete `rsh', which, after the user and hostname, is like xargs."
775 (require 'pcmpl-unix)
776 (pcomplete-opt "l(pcmpl-unix-user-names)")
777 (eshell-complete-hostname)
778 (pcomplete-here (funcall pcomplete-command-completion-function))
779 (funcall (or (pcomplete-find-completion-function (pcomplete-arg 1))
780 pcomplete-default-completion-function)))
781
782 (defalias 'pcomplete/ssh 'pcomplete/rsh)
783
784 (eval-when-compile
785 (defvar block-size)
786 (defvar by-bytes)
787 (defvar dereference-links)
788 (defvar grand-total)
789 (defvar human-readable)
790 (defvar max-depth)
791 (defvar only-one-filesystem)
792 (defvar show-all))
793
794 (defsubst eshell-du-size-string (size)
795 (let* ((str (eshell-printable-size size human-readable block-size t))
796 (len (length str)))
797 (concat str (if (< len 8)
798 (make-string (- 8 len) ? )))))
799
800 (defun eshell-du-sum-directory (path depth)
801 "Summarize PATH, and its member directories."
802 (let ((entries (eshell-directory-files-and-attributes path))
803 (size 0.0))
804 (while entries
805 (unless (string-match "\\`\\.\\.?\\'" (caar entries))
806 (let* ((entry (concat path (char-to-string directory-sep-char)
807 (caar entries)))
808 (symlink (and (stringp (cadr (car entries)))
809 (cadr (car entries)))))
810 (unless (or (and symlink (not dereference-links))
811 (and only-one-filesystem
812 (/= only-one-filesystem
813 (nth 12 (car entries)))))
814 (if symlink
815 (setq entry symlink))
816 (setq size
817 (+ size
818 (if (eq t (cadr (car entries)))
819 (eshell-du-sum-directory entry (1+ depth))
820 (let ((file-size (nth 8 (car entries))))
821 (prog1
822 file-size
823 (if show-all
824 (eshell-print
825 (concat (eshell-du-size-string file-size)
826 entry "\n")))))))))))
827 (setq entries (cdr entries)))
828 (if (or (not max-depth)
829 (= depth max-depth)
830 (= depth 0))
831 (eshell-print (concat (eshell-du-size-string size)
832 (directory-file-name path) "\n")))
833 size))
834
835 (defun eshell/du (&rest args)
836 "Implementation of \"du\" in Lisp, passing ARGS."
837 (setq args (if args
838 (eshell-stringify-list (eshell-flatten-list args))
839 '(".")))
840 (let ((ext-du (eshell-search-path "du")))
841 (if (and ext-du
842 (not (catch 'have-ange-path
843 (eshell-for arg args
844 (if (eq (find-file-name-handler (expand-file-name arg)
845 'directory-files)
846 'ange-ftp-hook-function)
847 (throw 'have-ange-path t))))))
848 (throw 'eshell-replace-command
849 (eshell-parse-command ext-du args))
850 (eshell-eval-using-options
851 "du" args
852 '((?a "all" nil show-all
853 "write counts for all files, not just directories")
854 (nil "block-size" t block-size
855 "use SIZE-byte blocks (i.e., --block-size SIZE)")
856 (?b "bytes" nil by-bytes
857 "print size in bytes")
858 (?c "total" nil grand-total
859 "produce a grand total")
860 (?d "max-depth" t max-depth
861 "display data only this many levels of data")
862 (?h "human-readable" 1024 human-readable
863 "print sizes in human readable format")
864 (?H "is" 1000 human-readable
865 "likewise, but use powers of 1000 not 1024")
866 (?k "kilobytes" 1024 block-size
867 "like --block-size 1024")
868 (?L "dereference" nil dereference-links
869 "dereference all symbolic links")
870 (?m "megabytes" 1048576 block-size
871 "like --block-size 1048576")
872 (?s "summarize" 0 max-depth
873 "display only a total for each argument")
874 (?x "one-file-system" nil only-one-filesystem
875 "skip directories on different filesystems")
876 (nil "help" nil nil
877 "show this usage screen")
878 :external "du"
879 :usage "[OPTION]... FILE...
880 Summarize disk usage of each FILE, recursively for directories.")
881 (unless by-bytes
882 (setq block-size (or block-size 1024)))
883 (if (and max-depth (stringp max-depth))
884 (setq max-depth (string-to-int max-depth)))
885 ;; filesystem support means nothing under Windows
886 (if (eshell-under-windows-p)
887 (setq only-one-filesystem nil))
888 (let ((size 0.0) ange-cache)
889 (while args
890 (if only-one-filesystem
891 (setq only-one-filesystem
892 (nth 11 (eshell-file-attributes
893 (file-name-as-directory (car args))))))
894 (setq size (+ size (eshell-du-sum-directory
895 (directory-file-name (car args)) 0)))
896 (setq args (cdr args)))
897 (if grand-total
898 (eshell-print (concat (eshell-du-size-string size)
899 "total\n"))))))))
900
901 (defvar eshell-time-start nil)
902
903 (defun eshell-show-elapsed-time ()
904 (let ((elapsed (format "%.3f secs\n"
905 (- (eshell-time-to-seconds (current-time))
906 eshell-time-start))))
907 (set-text-properties 0 (length elapsed) '(face bold) elapsed)
908 (eshell-interactive-print elapsed))
909 (remove-hook 'eshell-post-command-hook 'eshell-show-elapsed-time t))
910
911 (defun eshell/time (&rest args)
912 "Implementation of \"time\" in Lisp."
913 (let ((time-args (copy-alist args))
914 (continue t)
915 last-arg)
916 (while (and continue args)
917 (if (not (string-match "^-" (car args)))
918 (progn
919 (if last-arg
920 (setcdr last-arg nil)
921 (setq args '("")))
922 (setq continue nil))
923 (setq last-arg args
924 args (cdr args))))
925 (eshell-eval-using-options
926 "time" args
927 '((?h "help" nil nil "show this usage screen")
928 :external "time"
929 :show-usage
930 :usage "COMMAND...
931 Show wall-clock time elapsed during execution of COMMAND.")
932 (setq eshell-time-start (eshell-time-to-seconds (current-time)))
933 (add-hook 'eshell-post-command-hook 'eshell-show-elapsed-time nil t)
934 ;; after setting
935 (throw 'eshell-replace-command
936 (eshell-parse-command (car time-args) (cdr time-args))))))
937
938 (defalias 'eshell/whoami 'user-login-name)
939
940 (defvar eshell-diff-window-config nil)
941
942 (defun eshell-diff-quit ()
943 "Restore the window configuration previous to diff'ing."
944 (interactive)
945 (if eshell-diff-window-config
946 (set-window-configuration eshell-diff-window-config)))
947
948 (defun eshell/diff (&rest args)
949 "Alias \"diff\" to call Emacs `diff' function."
950 (let ((orig-args (eshell-stringify-list (eshell-flatten-list args))))
951 (if (or eshell-plain-diff-behavior
952 (not (and (eshell-interactive-output-p)
953 (not eshell-in-pipeline-p)
954 (not eshell-in-subcommand-p))))
955 (throw 'eshell-replace-command
956 (eshell-parse-command "*diff" orig-args))
957 (setq args (copy-sequence orig-args))
958 (if (< (length args) 2)
959 (throw 'eshell-replace-command
960 (eshell-parse-command "*diff" orig-args)))
961 (let ((old (car (last args 2)))
962 (new (car (last args)))
963 (config (current-window-configuration)))
964 (if (= (length args) 2)
965 (setq args nil)
966 (setcdr (last args 3) nil))
967 (with-current-buffer
968 (condition-case err
969 (diff old new (eshell-flatten-and-stringify args))
970 (error
971 (throw 'eshell-replace-command
972 (eshell-parse-command "*diff" orig-args))))
973 (when (fboundp 'diff-mode)
974 (make-local-variable 'compilation-finish-functions)
975 (add-hook
976 'compilation-finish-functions
977 `(lambda (buff msg)
978 (with-current-buffer buff
979 (diff-mode)
980 (set (make-local-variable 'eshell-diff-window-config)
981 ,config)
982 (local-set-key [?q] 'eshell-diff-quit)
983 (if (fboundp 'turn-on-font-lock-if-enabled)
984 (turn-on-font-lock-if-enabled))
985 (goto-char (point-min))))))
986 (pop-to-buffer (current-buffer))))))
987 nil)
988
989 (put 'eshell/diff 'eshell-no-numeric-conversions t)
990
991 (defun eshell/locate (&rest args)
992 "Alias \"locate\" to call Emacs `locate' function."
993 (if (or eshell-plain-locate-behavior
994 (not (and (eshell-interactive-output-p)
995 (not eshell-in-pipeline-p)
996 (not eshell-in-subcommand-p)))
997 (and (stringp (car args))
998 (string-match "^-" (car args))))
999 (throw 'eshell-replace-command
1000 (eshell-parse-command "*locate" (eshell-stringify-list
1001 (eshell-flatten-list args))))
1002 (save-selected-window
1003 (let ((locate-history-list (list (car args))))
1004 (locate-with-filter (car args) (cadr args))))))
1005
1006 (put 'eshell/locate 'eshell-no-numeric-conversions t)
1007
1008 (defun eshell/occur (&rest args)
1009 "Alias \"occur\" to call Emacs `occur' function."
1010 (let ((inhibit-read-only t))
1011 (if (> (length args) 2)
1012 (error "usage: occur: (REGEXP &optional NLINES)")
1013 (apply 'occur args))))
1014
1015 (put 'eshell/occur 'eshell-no-numeric-conversions t)
1016
1017 ;;; Code:
1018
1019 ;;; arch-tag: 2462edd2-a76a-4cf2-897d-92e9a82ac1c9
1020 ;;; em-unix.el ends here