-;;; shell.el --- specialized comint.el for running the shell
+;;; shell.el --- specialized comint.el for running the shell -*- lexical-binding: t -*-
-;; Copyright (C) 1988, 1993, 1994, 1995, 1996, 1997, 2000, 2001,
-;; 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
+;; Copyright (C) 1988, 1993-1997, 2000-2011 Free Software Foundation, Inc.
;; Author: Olin Shivers <shivers@cs.cmu.edu>
;; Simon Marshall <simon@gnu.org>
;; Shell Mode Commands:
;; shell Fires up the shell process
-;; tab comint-dynamic-complete Complete filename/command/history
+;; tab completion-at-point Complete filename/command/history
;; m-? comint-dynamic-list-filename-completions
;; List completions in help buffer
;; m-c-f shell-forward-command Forward a shell command
;;; Code:
+(eval-when-compile (require 'cl))
(require 'comint)
+(require 'pcomplete)
;;; Customization and Buffer Variables
:type '(repeat (string :tag "Suffix"))
:group 'shell)
-(defvar shell-delimiter-argument-list '(?\| ?& ?< ?> ?\( ?\) ?\;)
+(defcustom shell-delimiter-argument-list nil ; '(?\| ?& ?< ?> ?\( ?\) ?\;)
"List of characters to recognize as separate arguments.
This variable is used to initialize `comint-delimiter-argument-list' in the
-shell buffer. The value may depend on the operating system or shell.
-
-This is a fine thing to set in your `.emacs' file.")
+shell buffer. The value may depend on the operating system or shell."
+ :type '(choice (const nil)
+ (repeat :tag "List of characters" character))
+ :version "24.1" ; changed to nil (bug#8027)
+ :group 'shell)
(defvar shell-file-name-chars
(if (memq system-type '(ms-dos windows-nt cygwin))
This is a fine thing to set in your `.emacs' file.")
(defvar shell-dynamic-complete-functions
- '(comint-replace-by-expanded-history
- shell-dynamic-complete-environment-variable
- shell-dynamic-complete-command
- shell-replace-by-expanded-directory
- shell-dynamic-complete-filename
- comint-dynamic-complete-filename)
+ '(comint-c-a-p-replace-by-expanded-history
+ shell-environment-variable-completion
+ shell-command-completion
+ shell-c-a-p-replace-by-expanded-directory
+ pcomplete-completions-at-point
+ shell-filename-completion
+ ;; Not sure when this one would still be useful. --Stef
+ comint-filename-completion)
"List of functions called to perform completion.
This variable is used to initialize `comint-dynamic-complete-functions' in the
shell buffer.
If the value is `input', then the expansion is seen on input.
If the value is `history', then the expansion is only when inserting
into the buffer's input ring. See also `comint-magic-space' and
-`comint-dynamic-complete'.
+`comint-dynamic-complete-functions'.
This variable supplies a default for `comint-input-autoexpand',
for Shell mode only."
(let ((map (nconc (make-sparse-keymap) comint-mode-map)))
(define-key map "\C-c\C-f" 'shell-forward-command)
(define-key map "\C-c\C-b" 'shell-backward-command)
- (define-key map "\t" 'comint-dynamic-complete)
+ (define-key map "\t" 'completion-at-point)
(define-key map (kbd "M-RET") 'shell-resync-dirs)
(define-key map "\M-?" 'comint-dynamic-list-filename-completions)
(define-key map [menu-bar completion]
;;; Basic Procedures
+(defcustom shell-dir-cookie-re nil
+ "Regexp matching your prompt, including some part of the current directory.
+If your prompt includes the current directory or the last few elements of it,
+set this to a pattern that matches your prompt and whose subgroup 1 matches
+the directory part of it.
+This is used by `shell-dir-cookie-watcher' to try and use this info
+to track your current directory. It can be used instead of or in addition
+to `dirtrack-mode'."
+ :group 'shell
+ :type '(choice (const nil) regexp))
+
+
+(defun shell-completion-vars ()
+ "Setup completion vars for `shell-mode' and `read-shell-command'."
+ (set (make-local-variable 'comint-completion-fignore)
+ shell-completion-fignore)
+ (set (make-local-variable 'comint-delimiter-argument-list)
+ shell-delimiter-argument-list)
+ (set (make-local-variable 'comint-file-name-chars) shell-file-name-chars)
+ (set (make-local-variable 'comint-file-name-quote-list)
+ shell-file-name-quote-list)
+ (set (make-local-variable 'comint-dynamic-complete-functions)
+ shell-dynamic-complete-functions)
+ (set (make-local-variable 'pcomplete-parse-arguments-function)
+ ;; FIXME: This function should be moved to shell.el.
+ #'pcomplete-parse-comint-arguments)
+ (set (make-local-variable 'pcomplete-termination-string)
+ (cond ((not comint-completion-addsuffix) "")
+ ((stringp comint-completion-addsuffix)
+ comint-completion-addsuffix)
+ ((not (consp comint-completion-addsuffix)) " ")
+ (t (cdr comint-completion-addsuffix))))
+ ;; Don't use pcomplete's defaulting mechanism, rely on
+ ;; shell-dynamic-complete-functions instead.
+ (set (make-local-variable 'pcomplete-default-completion-function) #'ignore)
+ (setq comint-input-autoexpand shell-input-autoexpand)
+ ;; Not needed in shell-mode because it's inherited from comint-mode, but
+ ;; placed here for read-shell-command.
+ (add-hook 'completion-at-point-functions 'comint-completion-at-point nil t))
+
(put 'shell-mode 'mode-class 'special)
(define-derived-mode shell-mode comint-mode "Shell"
control whether input and output cause the window to scroll to the end of the
buffer."
(setq comint-prompt-regexp shell-prompt-pattern)
- (setq comint-completion-fignore shell-completion-fignore)
- (setq comint-delimiter-argument-list shell-delimiter-argument-list)
- (setq comint-file-name-chars shell-file-name-chars)
- (setq comint-file-name-quote-list shell-file-name-quote-list)
- (set (make-local-variable 'comint-dynamic-complete-functions)
- shell-dynamic-complete-functions)
+ (shell-completion-vars)
(set (make-local-variable 'paragraph-separate) "\\'")
- (make-local-variable 'paragraph-start)
- (setq paragraph-start comint-prompt-regexp)
- (make-local-variable 'font-lock-defaults)
- (setq font-lock-defaults '(shell-font-lock-keywords t))
- (make-local-variable 'shell-dirstack)
- (setq shell-dirstack nil)
- (make-local-variable 'shell-last-dir)
- (setq shell-last-dir nil)
- (setq comint-input-autoexpand shell-input-autoexpand)
+ (set (make-local-variable 'paragraph-start) comint-prompt-regexp)
+ (set (make-local-variable 'font-lock-defaults) '(shell-font-lock-keywords t))
+ (set (make-local-variable 'shell-dirstack) nil)
+ (set (make-local-variable 'shell-last-dir) nil)
(shell-dirtrack-mode 1)
;; This is not really correct, since the shell buffer does not really
;; edit this directory. But it is useful in the buffer list and menus.
;; shell-dependent assignments.
(when (ring-empty-p comint-input-ring)
(let ((shell (file-name-nondirectory (car
- (process-command (get-buffer-process (current-buffer)))))))
+ (process-command (get-buffer-process (current-buffer))))))
+ (hsize (getenv "HISTSIZE")))
+ (and (stringp hsize)
+ (integerp (setq hsize (string-to-number hsize)))
+ (> hsize 0)
+ (set (make-local-variable 'comint-input-ring-size) hsize))
(setq comint-input-ring-file-name
(or (getenv "HISTFILE")
(cond ((string-equal shell "bash") "~/.bash_history")
(t "dirs")))
;; Bypass a bug in certain versions of bash.
(when (string-equal shell "bash")
- (add-hook 'comint-output-filter-functions
+ (add-hook 'comint-preoutput-filter-functions
'shell-filter-ctrl-a-ctrl-b nil t)))
+ (when shell-dir-cookie-re
+ ;; Watch for magic cookies in the output to track the current dir.
+ (add-hook 'comint-output-filter-functions
+ 'shell-dir-cookie-watcher nil t))
(comint-read-input-ring t)))
(defun shell-filter-ctrl-a-ctrl-b (string)
Rendition (SGR) control sequences (formerly known as ANSI escape
sequences) are used to color the prompt.
-This function can be put on `comint-output-filter-functions'.
-The argument STRING is ignored."
- (let ((pmark (process-mark (get-buffer-process (current-buffer)))))
- (save-excursion
- (goto-char (or (and (markerp comint-last-output-start)
- (marker-position comint-last-output-start))
- (point-min)))
- (while (re-search-forward "[\C-a\C-b]" pmark t)
- (replace-match "")))))
+This function can be put on `comint-preoutput-filter-functions'."
+ (if (string-match "[\C-a\C-b]" string)
+ (replace-regexp-in-string "[\C-a\C-b]" "" string t t)
+ string))
(defun shell-write-history-on-exit (process event)
"Called when the shell process is stopped.
(generate-new-buffer-name "*shell*"))
(if (file-remote-p default-directory)
;; It must be possible to declare a local default-directory.
+ ;; FIXME: This can't be right: it changes the default-directory
+ ;; of the current-buffer rather than of the *shell* buffer.
(setq default-directory
(expand-file-name
- (read-file-name
+ (read-directory-name
"Default directory: " default-directory default-directory
- t nil 'file-directory-p))))))))
+ t nil))))))))
(require 'ansi-color)
- (setq buffer (get-buffer-create (or buffer "*shell*")))
+ (setq buffer (if (or buffer (not (derived-mode-p 'shell-mode))
+ (comint-check-proc (current-buffer)))
+ (get-buffer-create (or buffer "*shell*"))
+ ;; If the current buffer is a dead shell buffer, use it.
+ (current-buffer)))
+
+ ;; On remote hosts, the local `shell-file-name' might be useless.
+ (if (and (called-interactively-p 'any)
+ (file-remote-p default-directory)
+ (null explicit-shell-file-name)
+ (null (getenv "ESHELL")))
+ (with-current-buffer buffer
+ (set (make-local-variable 'explicit-shell-file-name)
+ (file-remote-p
+ (expand-file-name
+ (read-file-name
+ "Remote shell path: " default-directory shell-file-name
+ t shell-file-name))
+ 'localname))))
+
;; Pop to buffer, so that the buffer's window will be correctly set
;; when we call comint (so that comint sets the COLUMNS env var properly).
(pop-to-buffer buffer)
;; replace it with a process filter that watches for and strips out
;; these messages.
+(defun shell-dir-cookie-watcher (text)
+ ;; This is fragile: the TEXT could be split into several chunks and we'd
+ ;; miss it. Oh well. It's a best effort anyway. I'd expect that it's
+ ;; rather unusual to have the prompt split into several packets, but
+ ;; I'm sure Murphy will prove me wrong.
+ (when (and shell-dir-cookie-re (string-match shell-dir-cookie-re text))
+ (let ((dir (match-string 1 text)))
+ (cond
+ ((file-name-absolute-p dir) (shell-cd dir))
+ ;; Let's try and see if it seems to be up or down from where we were.
+ ((string-match "\\`\\(.*\\)\\(?:/.*\\)?\n\\(.*/\\)\\1\\(?:/.*\\)?\\'"
+ (setq text (concat dir "\n" default-directory)))
+ (shell-cd (concat (match-string 2 text) dir)))))))
+
(defun shell-directory-tracker (str)
"Tracks cd, pushd and popd commands issued to the shell.
This function is called on each input passed to the shell.
Environment variables are expanded, see function `substitute-in-file-name'."
(if shell-dirtrackp
;; We fail gracefully if we think the command will fail in the shell.
- (condition-case chdir-failure
+ (condition-case nil
(let ((start (progn (string-match
(concat "^" shell-command-separator-regexp)
str) ; skip whitespace
(match-end 0)))
+ (case-fold-search)
end cmd arg1)
(while (string-match shell-command-regexp str start)
(setq end (match-end 0)
"Move forward across ARG shell command(s). Does not cross lines.
See `shell-command-regexp'."
(interactive "p")
- (let ((limit (save-excursion (end-of-line nil) (point))))
+ (let ((limit (line-end-position)))
(if (re-search-forward (concat shell-command-regexp "\\([;&|][\t ]*\\)+")
limit 'move arg)
(skip-syntax-backward " "))))
path.
Completion is dependent on the value of `shell-completion-execonly', plus
-those that effect file completion. See `shell-dynamic-complete-as-command'.
+those that effect file completion.
Returns t if successful."
(interactive)
+ (let ((data (shell-command-completion)))
+ (if data
+ (prog2 (unless (window-minibuffer-p (selected-window))
+ (message "Completing command name..."))
+ (apply #'completion-in-region data)))))
+
+(defun shell-command-completion ()
+ "Return the completion data for the command at point, if any."
(let ((filename (comint-match-partial-filename)))
(if (and filename
(save-match-data (not (string-match "[~/]" filename)))
(eq (match-beginning 0)
(save-excursion (shell-backward-command 1) (point))))
- (prog2 (unless (window-minibuffer-p (selected-window))
- (message "Completing command name..."))
- (shell-dynamic-complete-as-command)))))
-
+ (shell--command-completion-data))))
-(defun shell-dynamic-complete-as-command ()
- "Dynamically complete at point as a command.
-See `shell-dynamic-complete-filename'. Returns t if successful."
+(defun shell--command-completion-data ()
+ "Return the completion data for the command at point."
(let* ((filename (or (comint-match-partial-filename) ""))
+ (start (if (zerop (length filename)) (point) (match-beginning 0)))
+ (end (if (zerop (length filename)) (point) (match-end 0)))
(filenondir (file-name-nondirectory filename))
- (path-dirs (cdr (reverse exec-path)))
+ (path-dirs (cdr (reverse exec-path))) ;FIXME: Why `cdr'?
(cwd (file-name-as-directory (expand-file-name default-directory)))
(ignored-extensions
(and comint-completion-fignore
- (mapconcat (function (lambda (x) (concat (regexp-quote x) "$")))
+ (mapconcat (function (lambda (x) (concat (regexp-quote x) "\\'")))
comint-completion-fignore "\\|")))
(dir "") (comps-in-dir ())
(file "") (abs-file-name "") (completions ()))
(setq comps-in-dir (cdr comps-in-dir)))
(setq path-dirs (cdr path-dirs)))
;; OK, we've got a list of completions.
- (let ((success (let ((comint-completion-addsuffix nil))
- (comint-dynamic-simple-complete filenondir completions))))
- (if (and (memq success '(sole shortest)) comint-completion-addsuffix
- (not (file-directory-p (comint-match-partial-filename))))
- (insert " "))
- success)))
+ (list
+ start end
+ (lambda (string pred action)
+ (if (string-match "/" string)
+ (completion-file-name-table string pred action)
+ (complete-with-action action completions string pred)))
+ :exit-function
+ (lambda (_string finished)
+ (when (memq finished '(sole finished))
+ (if (looking-at " ")
+ (goto-char (match-end 0))
+ (insert " ")))))))
+
+;; (defun shell-dynamic-complete-as-command ()
+;; "Dynamically complete at point as a command.
+;; See `shell-dynamic-complete-filename'. Returns t if successful."
+;; (apply #'completion-in-region shell--command-completion-data))
(defun shell-dynamic-complete-filename ()
"Dynamically complete the filename at point.
This completes only if point is at a suitable position for a
filename argument."
(interactive)
+ (let ((data (shell-filename-completion)))
+ (if data (apply #'completion-in-region data))))
+
+(defun shell-filename-completion ()
+ "Return the completion data for file name at point, if any."
(let ((opoint (point))
(beg (comint-line-beginning-position)))
(when (save-excursion
(match-end 0)
beg))
(re-search-forward "[^ \t][ \t]" opoint t))
- (comint-dynamic-complete-as-filename))))
+ (comint-filename-completion))))
(defun shell-match-partial-variable ()
"Return the shell variable at point, or nil if none is found."
(save-excursion
- (let ((limit (point)))
- (if (re-search-backward "[^A-Za-z0-9_{}]" nil 'move)
- (or (looking-at "\\$") (forward-char 1)))
- ;; Anchor the search forwards.
- (if (or (eolp) (looking-at "[^A-Za-z0-9_{}$]"))
- nil
- (re-search-forward "\\$?{?[A-Za-z0-9_]*}?" limit)
- (buffer-substring (match-beginning 0) (match-end 0))))))
+ (if (re-search-backward "[^A-Za-z0-9_{(]" nil 'move)
+ (or (looking-at "\\$") (forward-char 1)))
+ (if (or (eolp) (looking-at "[^A-Za-z0-9_{($]"))
+ nil
+ (looking-at "\\$?[{(]?[A-Za-z0-9_]*[})]?")
+ (buffer-substring (match-beginning 0) (match-end 0)))))
(defun shell-dynamic-complete-environment-variable ()
"Dynamically complete the environment variable at point.
Completes if after a variable, i.e., if it starts with a \"$\".
-See `shell-dynamic-complete-as-environment-variable'.
This function is similar to `comint-dynamic-complete-filename', except that it
searches `process-environment' for completion candidates. Note that this may
Returns non-nil if successful."
(interactive)
- (let ((variable (shell-match-partial-variable)))
- (if (and variable (string-match "^\\$" variable))
+ (let ((data (shell-environment-variable-completion)))
+ (if data
(prog2 (unless (window-minibuffer-p (selected-window))
(message "Completing variable name..."))
- (shell-dynamic-complete-as-environment-variable)))))
-
-
-(defun shell-dynamic-complete-as-environment-variable ()
- "Dynamically complete at point as an environment variable.
-Used by `shell-dynamic-complete-environment-variable'.
-Uses `comint-dynamic-simple-complete'."
- (let* ((var (or (shell-match-partial-variable) ""))
- (variable (substring var (or (string-match "[^$({]\\|$" var) 0)))
- (variables (mapcar (function (lambda (x)
- (substring x 0 (string-match "=" x))))
- process-environment))
- (addsuffix comint-completion-addsuffix)
- (comint-completion-addsuffix nil)
- (success (comint-dynamic-simple-complete variable variables)))
- (if (memq success '(sole shortest))
- (let* ((var (shell-match-partial-variable))
- (variable (substring var (string-match "[^$({]" var)))
- (protection (cond ((string-match "{" var) "}")
- ((string-match "(" var) ")")
- (t "")))
- (suffix (cond ((null addsuffix) "")
- ((file-directory-p
- (comint-directory (getenv variable))) "/")
- (t " "))))
- (insert protection suffix)))
- success))
-
+ (apply #'completion-in-region data)))))
+
+
+(defun shell-environment-variable-completion ()
+ "Completion data for an environment variable at point, if any."
+ (let* ((var (shell-match-partial-variable))
+ (end (match-end 0)))
+ (when (and (not (zerop (length var))) (eq (aref var 0) ?$))
+ (let* ((start
+ (save-excursion
+ (goto-char (match-beginning 0))
+ (looking-at "\\$?[({]*")
+ (match-end 0)))
+ (variables (mapcar (lambda (x)
+ (substring x 0 (string-match "=" x)))
+ process-environment))
+ (suffix (case (char-before start) (?\{ "}") (?\( ")") (t ""))))
+ (list start end variables
+ :exit-function
+ (lambda (s finished)
+ (when (memq finished '(sole finished))
+ (let ((suf (concat suffix
+ (if (file-directory-p
+ (comint-directory (getenv s)))
+ "/"))))
+ (if (looking-at (regexp-quote suf))
+ (goto-char (match-end 0))
+ (insert suf))))))))))
+
+
+(defun shell-c-a-p-replace-by-expanded-directory ()
+ "Expand directory stack reference before point.
+For use on `completion-at-point-functions'."
+ (when (comint-match-partial-filename)
+ (save-excursion
+ (goto-char (match-beginning 0))
+ (let ((stack (cons default-directory shell-dirstack))
+ (index (cond ((looking-at "=-/?")
+ (length shell-dirstack))
+ ((looking-at "=\\([0-9]+\\)/?")
+ (string-to-number
+ (buffer-substring
+ (match-beginning 1) (match-end 1)))))))
+ (when index
+ (let ((start (match-beginning 0))
+ (end (match-end 0))
+ (replacement (file-name-as-directory (nth index stack))))
+ (lambda ()
+ (cond
+ ((>= index (length stack))
+ (error "Directory stack not that deep"))
+ (t
+ (save-excursion
+ (goto-char start)
+ (insert replacement)
+ (delete-char (- end start)))
+ (message "Directory item: %d" index)
+ t)))))))))
(defun shell-replace-by-expanded-directory ()
"Expand directory stack reference before point.
Returns t if successful."
(interactive)
- (if (comint-match-partial-filename)
- (save-excursion
- (goto-char (match-beginning 0))
- (let ((stack (cons default-directory shell-dirstack))
- (index (cond ((looking-at "=-/?")
- (length shell-dirstack))
- ((looking-at "=\\([0-9]+\\)/?")
- (string-to-number
- (buffer-substring
- (match-beginning 1) (match-end 1)))))))
- (cond ((null index)
- nil)
- ((>= index (length stack))
- (error "Directory stack not that deep"))
- (t
- (replace-match (file-name-as-directory (nth index stack)) t t)
- (message "Directory item: %d" index)
- t))))))
+ (let ((f (shell-c-a-p-replace-by-expanded-directory)))
+ (if f (funcall f))))
(provide 'shell)
-;; arch-tag: bcb5f12a-c1f4-4aea-a809-2504bd5bd797
;;; shell.el ends here