;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; ;; File: shellext.el ;; ;; Author: Wolfgang Rupprecht ;; ;; Address: wolfgang@mgm.mit.edu (IP addr 18.82.0.114) ;; ;; Created: Mon Oct 12 18:52:34 EDT 1987 ;; ;; Contents: ksh-like extensions to shell.el ;; ;; ;; ;; Copyright (c) 1987 Wolfgang Rupprecht. ;; ;; ;; ;; $Log$ ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; GNU Emacs and this file "shellext.el", is distributed in the hope ;; that it will be useful, but WITHOUT ANY WARRANTY. No author or ;; distributor accepts responsibility to anyone for the consequences ;; of using it or for whether it serves any particular purpose or ;; works at all, unless he says so in writing. Refer to the GNU Emacs ;; General Public License for full details. ;; Everyone is granted permission to copy, modify and redistribute GNU ;; Emacs and shellext.el, but only under the conditions described in ;; the GNU Emacs General Public License. A copy of this license is ;; supposed to have been given to you along with GNU Emacs so you can ;; know your rights and responsibilities. It should be in a file ;; named COPYING. Among other things, the copyright notice and this ;; notice must be preserved on all copies. ;; If you like this shell hack, and would like other custom ;; non-proprietary GnuEmacs extensions, let me know. I may be ;; interested in doing it for you on a contract basis. -wsr (provide 'shellext) ; for require 'shellext in ; patched shell.el (defvar shell-last-search "" "Last shell search string.") (defvar shell-max-history 60 "*Max shell history retained") (defvar shell-history-list nil "History list of past shell commands.") (defvar shell-history-index -1 "Where we are on the history list. It is -1 unless currently walking up/down the list") (defvar shell-history-list-order nil "*If t, list shell history with most recent command last." nil) (defvar shell-read-history t "*If t, the emacs shell will read in the user's ~/.history file. This somewhat slows down shell startup.") (define-key shell-mode-map "\C-c\t" 'shell-filename-expand) (define-key shell-mode-map "\C-c\C-p" 'shell-previous-command) (define-key shell-mode-map "\C-c\C-n" 'shell-next-command) (define-key shell-mode-map "\C-c\C-r" 'shell-history-search-backward) (define-key shell-mode-map "\C-c\C-s" 'shell-history-search-forward) (define-key shell-mode-map "\C-ch" 'shell-list-history) (define-key shell-mode-map "\C-c\C-a" 'shell-beginning-of-line) (define-key shell-mode-map "\C-c\r" 'shell-push-input) ; and now rebind 'show-output-from shell (which was on C-cC-r) (define-key shell-mode-map "\C-c[" 'show-output-from-shell) (defun shell-filename-expand () "Complete the filename before (point) as far as possible." (interactive) (let* ((end (point)) (beg (progn (re-search-backward "[ \t\n;]") (forward-char 1) (point))) (new (path-name-completion (buffer-substring beg end)))) (cond ((eq new t) (progn (goto-char end) (message "File name is already complete"))) ((null new) (progn (goto-char end) (error "No completion possible"))) (t (progn (delete-region beg end) (insert new)))))) (defun path-name-completion (path-name) "Complete PATHNAME as far as possible, return this string." (let* ((dir (file-name-directory path-name)) (file (file-name-nondirectory path-name)) (completion (file-name-completion file (or dir "")))) (if (string-equal file completion) ; we are at a branch point (let ((list (sort (file-name-all-completions (or file "") (or dir "")) 'string-lessp))) (with-output-to-temp-buffer " *Completions*" (princ "Possible completions are:\n") (while list ;; -40 padding spec doesn't work !!! ;; (princ (format "%-40s %s" (car list) ;; (or (car (cdr list)) ""))) (princ (car list)) (if (cdr list) (progn (princ (make-string (- 35 (length (car list))) ?\ )) (princ (car (cdr list))))) (terpri) (setq list (cdr (cdr list))))) path-name) ; give 'em back their input ;; BUG?: emacs outputs to wrong-buffer (inserted text destined for ;; the *shell* buffer goes to next buffer on buffer list.) ;; Seemingly unneeded save-excursion around kill-buffer solves ;; this problem. -wsr (save-excursion (let ((buffer (get-buffer " *Completions*"))) (if buffer (kill-buffer buffer)))) (if dir (and completion (or (eq completion t) ; leave t or nil alone (concat dir completion))) completion)))) (defun shell-previous-command () "Insert the previous command into the shell buffer." (interactive) (let ((history (nthcdr (1+ shell-history-index) shell-history-list))) (if history (progn (delete-region (process-mark (get-buffer-process (current-buffer))) (point-max)) (goto-char (point-max)) (insert (car history)) (setq shell-history-index (1+ shell-history-index))) ; (message "history %d" shell-history-index) (error "End of history list, (history %d)" shell-history-index)))) (defun shell-next-command () "Insert the next command into the shell buffer." (interactive) (delete-region (process-mark (get-buffer-process (current-buffer))) (point-max)) (goto-char (point-max)) (if (<= 0 shell-history-index) (setq shell-history-index (1- shell-history-index)) (error "Top of history list (history %d)" shell-history-index)) (if (<= 0 shell-history-index) (progn ; (message "history %d" shell-history-index) (insert (nth shell-history-index shell-history-list))))) (defun shell-history-search-backward (string) "Search the history list for an occurance of STRING." (interactive (list (setq shell-last-search (read-string "History search for: " shell-last-search)))) (let* ((index (1+ shell-history-index)) ; start at next command (history (nthcdr index shell-history-list))) (while (and history (null (string-match string (car history)))) (setq index (1+ index) history (cdr history))) (if history (progn (setq shell-history-index index) (delete-region (process-mark (get-buffer-process (current-buffer))) (point-max)) (goto-char (point-max)) (insert (car history)) ; (message "history %d" shell-history-index) ) (error "No match found, history %d" shell-history-index)))) (defun shell-history-search-forward (string) "Search the history list for an occurance of STRING." (interactive (list (setq shell-last-search (read-string "History search for: " shell-last-search)))) (let ((index shell-history-index)) (while (and (<= 0 index) ; not as effecient as backwards hum... (null (string-match string (nth (setq index (1- index)) shell-history-list))))) ;; index is bounded by: (-1 <= index <= shell-history-index) (if (<= 0 index) (progn (setq shell-history-index index) (delete-region (process-mark (get-buffer-process (current-buffer))) (point-max)) (goto-char (point-max)) (insert (or (nth index shell-history-list) "")) ; (message "history %d" shell-history-index) ) (error "No match found, history %d" shell-history-index)))) (defun shell-list-history () "List the history in the *History* buffer. A '*' indicates current position on the history list." (interactive) (with-output-to-temp-buffer "*History*" (if shell-history-list-order (let ((history (reverse (cons "<none>" shell-history-list))) (index (1- (length shell-history-list)))) (while history (princ (format "%c [%d] %s\n" (if (= index shell-history-index) ?* ?\ ) index (car history))) (setq history (cdr history) index (1- index)))) (let ((history (cons "<none>" shell-history-list)) (index -1)) (while history (princ (format "%c [%d] %s\n" (if (= index shell-history-index) ?* ?\ ) index (car history))) (setq history (cdr history) index (1+ index))))))) (defun shell-beginning-of-line () "Goto the beggining of the commad line. (ie. just after the prompt)" (interactive) (goto-char (process-mark (get-buffer-process (current-buffer))))) (defun shell-input-history-file () "Read in the user's ~/.history file." (if shell-history-list ; if non-nil, its only a shell restart, nil ; so don't trash existing history list. (let (list) (save-excursion (set-buffer (get-buffer-create "*history-temp*")) (erase-buffer) (insert-file (expand-file-name "~/.history")) (goto-char (point-min)) (while (re-search-forward "[^\^@\n]+$" nil t) (setq list (cons (buffer-substring (match-beginning 0) (match-end 0)) list))) (kill-buffer (current-buffer))) (let ((prune-pt (nthcdr shell-max-history list))) (and prune-pt (rplacd prune-pt nil))) (make-local-variable 'shell-history-index) (make-local-variable 'shell-history-list) (setq shell-history-list list) (setq shell-history-index -1)))) (defun shell-output-history-file () "Write out the user's ~/.history file from the internal history list." (save-excursion (let ((list shell-history-list)) ; hold onto that slippery local var (set-buffer (get-buffer-create "*history-temp*")) (erase-buffer) (while list (insert (car list) "\n\^@") (setq list (cdr list)))) (write-region (point-min) (point-max) (expand-file-name "~/.history") nil 'nomsg))) (defun shell-save-history () "Save this command on the shell-history-list." (let ((command (buffer-substring last-input-start (1- last-input-end)))) (if (or (string-match "^[ \t]*$" command) (string-equal command (car shell-history-list))) nil ; don't hang dups on list (setq shell-history-list (cons command shell-history-list)) (let ((prune-pt (nthcdr shell-max-history shell-history-list))) (and prune-pt (rplacd prune-pt nil))))) (setq shell-history-index -1)) (defun shell-push-input () "Pushes all pending shell input to shell. Like \\[shell-send-input], only it doesn't append a newline. Useful for programs that expect to talk to a tty in raw mode (eg. tip(1)). The pushed input doesn't get recorded on the shell's history list." (interactive) (goto-char (point-max)) (move-marker last-input-start (process-mark (get-buffer-process (current-buffer)))) (move-marker last-input-end (point)) (let ((process (get-buffer-process (current-buffer)))) (process-send-region process last-input-start last-input-end) (set-marker (process-mark process) (point))))