Disable whitespace-newline-mode properly.
[bpt/emacs.git] / lisp / shell.el
index ea89ce7..c75594d 100644 (file)
@@ -1,4 +1,4 @@
-;;; 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-1997, 2000-2011  Free Software Foundation, Inc.
 
 
 ;; Copyright (C) 1988, 1993-1997, 2000-2011  Free Software Foundation, Inc.
 
@@ -79,7 +79,7 @@
 
 ;; Shell Mode Commands:
 ;;         shell                       Fires up the shell process
 
 ;; 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
 ;; m-?     comint-dynamic-list-filename-completions
 ;;                                     List completions in help buffer
 ;; m-c-f   shell-forward-command       Forward a shell command
@@ -96,7 +96,9 @@
 
 ;;; Code:
 
 
 ;;; Code:
 
+(eval-when-compile (require 'cl))
 (require 'comint)
 (require 'comint)
+(require 'pcomplete)
 
 ;;; Customization and Buffer Variables
 
 
 ;;; Customization and Buffer Variables
 
@@ -181,12 +183,14 @@ shell buffer.  The value may depend on the operating system or shell.
 This is a fine thing to set in your `.emacs' file.")
 
 (defvar shell-dynamic-complete-functions
 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.
   "List of functions called to perform completion.
 This variable is used to initialize `comint-dynamic-complete-functions' in the
 shell buffer.
@@ -312,7 +316,7 @@ This mirrors the optional behavior of tcsh (its autoexpand and histlit).
 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
 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."
 
 This variable supplies a default for `comint-input-autoexpand',
 for Shell mode only."
@@ -339,7 +343,7 @@ Thus, this does not include the shell's current directory.")
   (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)
   (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]
     (define-key map (kbd "M-RET") 'shell-resync-dirs)
     (define-key map "\M-?" 'comint-dynamic-list-filename-completions)
     (define-key map [menu-bar completion]
@@ -377,7 +381,71 @@ 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
 to track your current directory.  It can be used instead of or in addition
 to `dirtrack-mode'."
   :group 'shell
-  :type '(choice (const nil) regexp))
+  :type '(choice (const nil) regexp)
+  :version "24.1")
+
+(defun shell-parse-pcomplete-arguments ()
+  "Parse whitespace separated arguments in the current region."
+  (let ((begin (save-excursion (shell-backward-command 1) (point)))
+       (end (point))
+       begins args)
+    (save-excursion
+      (goto-char begin)
+      (while (< (point) end)
+       (skip-chars-forward " \t\n")
+       (push (point) begins)
+        (let ((arg ()))
+          (while (looking-at
+                  (eval-when-compile
+                    (concat
+                     "\\(?:[^\s\t\n\\\"']+"
+                     "\\|'\\([^']*\\)'?"
+                     "\\|\"\\(\\(?:[^\"\\]\\|\\\\.\\)*\\)\"?"
+                     "\\|\\\\\\(\\(?:.\\|\n\\)?\\)\\)")))
+            (goto-char (match-end 0))
+            (cond
+             ((match-beginning 3)       ;Backslash escape.
+              (push (if (= (match-beginning 3) (match-end 3))
+                        "\\" (match-string 3))
+                    arg))
+             ((match-beginning 2)       ;Double quote.
+              (push (replace-regexp-in-string
+                     "\\\\\\(.\\)" "\\1" (match-string 2))
+                    arg))
+             ((match-beginning 1)       ;Single quote.
+              (push (match-string 1) arg))
+             (t (push (match-string 0) arg))))
+          (push (mapconcat #'identity (nreverse arg) "") args)))
+      (cons (nreverse args) (nreverse begins)))))
+
+(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)
+       #'shell-parse-pcomplete-arguments)
+  (set (make-local-variable 'pcomplete-arg-quote-list)
+       (append "\\ \t\n\r\"'`$|&;(){}[]<>#" nil))
+  (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)
 
 
 (put 'shell-mode 'mode-class 'special)
 
@@ -436,22 +504,12 @@ Variables `comint-output-filter-functions', a hook, and
 control whether input and output cause the window to scroll to the end of the
 buffer."
   (setq comint-prompt-regexp shell-prompt-pattern)
 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) "\\'")
   (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-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.
@@ -459,7 +517,12 @@ buffer."
   ;; shell-dependent assignments.
   (when (ring-empty-p comint-input-ring)
     (let ((shell (file-name-nondirectory (car
   ;; 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")
       (setq comint-input-ring-file-name
            (or (getenv "HISTFILE")
                (cond ((string-equal shell "bash") "~/.bash_history")
@@ -481,7 +544,7 @@ buffer."
                  (t "dirs")))
       ;; Bypass a bug in certain versions of bash.
       (when (string-equal shell "bash")
                  (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.
                   '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.
@@ -499,15 +562,10 @@ started with the `--noediting' option and Select Graphic
 Rendition (SGR) control sequences (formerly known as ANSI escape
 sequences) are used to color the prompt.
 
 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.
 
 (defun shell-write-history-on-exit (process event)
   "Called when the shell process is stopped.
@@ -569,18 +627,33 @@ Otherwise, one argument `-i' is passed to the shell.
                ;; of the current-buffer rather than of the *shell* buffer.
               (setq default-directory
                     (expand-file-name
                ;; 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
                       "Default directory: " default-directory default-directory
-                      t nil 'file-directory-p))))))))
+                      t nil))))))))
   (require 'ansi-color)
   (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)))
   (require 'ansi-color)
   (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)))
-  ;; 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)
+
+  ;; 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))))
+
+  ;; The buffer's window must be correctly set when we call comint (so
+  ;; that comint sets the COLUMNS env var properly).
+  (pop-to-buffer-same-window buffer)
   (unless (comint-check-proc buffer)
     (let* ((prog (or explicit-shell-file-name
                     (getenv "ESHELL") shell-file-name))
   (unless (comint-check-proc buffer)
     (let* ((prog (or explicit-shell-file-name
                     (getenv "ESHELL") shell-file-name))
@@ -597,9 +670,6 @@ Otherwise, one argument `-i' is passed to the shell.
       (shell-mode)))
   buffer)
 
       (shell-mode)))
   buffer)
 
-;; Don't do this when shell.el is loaded, only while dumping.
-;;;###autoload (add-hook 'same-window-buffer-names (purecopy "*shell*"))
-
 ;;; Directory tracking
 ;;
 ;; This code provides the shell mode input sentinel
 ;;; Directory tracking
 ;;
 ;; This code provides the shell mode input sentinel
@@ -672,11 +742,12 @@ and `shell-pushd-dunique' control the behavior of the relevant command.
 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.
 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)))
          (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)
                end cmd arg1)
            (while (string-match shell-command-regexp str start)
              (setq end (match-end 0)
@@ -819,9 +890,13 @@ Environment variables are expanded, see function `substitute-in-file-name'."
 
 (defvaralias 'shell-dirtrack-mode 'shell-dirtrackp)
 (define-minor-mode shell-dirtrack-mode
 
 (defvaralias 'shell-dirtrack-mode 'shell-dirtrackp)
 (define-minor-mode shell-dirtrack-mode
-  "Turn directory tracking on and off in a shell buffer.
-The `dirtrack' package provides an alternative implementation of this
-feature - see the function `dirtrack-mode'."
+  "Toggle directory tracking in this shell buffer (Shell Dirtrack mode).
+With a prefix argument ARG, enable Shell Dirtrack mode if ARG is
+positive, and disable it otherwise.  If called from Lisp, enable
+the mode if ARG is omitted or nil.
+
+The `dirtrack' package provides an alternative implementation of
+this feature; see the function `dirtrack-mode'."
   nil nil nil
   (setq list-buffers-directory (if shell-dirtrack-mode default-directory))
   (if shell-dirtrack-mode
   nil nil nil
   (setq list-buffers-directory (if shell-dirtrack-mode default-directory))
   (if shell-dirtrack-mode
@@ -991,30 +1066,36 @@ candidates.  Note that this may not be the same as the shell's idea of the
 path.
 
 Completion is dependent on the value of `shell-completion-execonly', plus
 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)
 
 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))))
   (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) ""))
   (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))
         (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
         (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 ()))
                          comint-completion-fignore "\\|")))
         (dir "") (comps-in-dir ())
         (file "") (abs-file-name "") (completions ()))
@@ -1038,18 +1119,34 @@ See `shell-dynamic-complete-filename'.  Returns t if successful."
        (setq comps-in-dir (cdr comps-in-dir)))
       (setq path-dirs (cdr path-dirs)))
     ;; OK, we've got a list of 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)
 
 (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
   (let ((opoint (point))
        (beg (comint-line-beginning-position)))
     (when (save-excursion
@@ -1057,24 +1154,21 @@ filename argument."
                           (match-end 0)
                         beg))
            (re-search-forward "[^ \t][ \t]" opoint t))
                           (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
 
 (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 \"$\".
 
 (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
 
 This function is similar to `comint-dynamic-complete-filename', except that it
 searches `process-environment' for completion candidates.  Note that this may
@@ -1086,38 +1180,68 @@ called `shell-dynamic-complete-process-environment-variable'.
 
 Returns non-nil if successful."
   (interactive)
 
 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..."))
        (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.
 
 (defun shell-replace-by-expanded-directory ()
   "Expand directory stack reference before point.
@@ -1126,24 +1250,8 @@ See `default-directory' and `shell-dirstack'.
 
 Returns t if successful."
   (interactive)
 
 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)
 
 
 (provide 'shell)