diff options
author | Stefan Monnier <monnier@iro.umontreal.ca> | 2011-04-20 19:31:06 -0300 |
---|---|---|
committer | Stefan Monnier <monnier@iro.umontreal.ca> | 2011-04-20 19:31:06 -0300 |
commit | c0a193ea2017fbaa6fb3e64a07125878656da156 (patch) | |
tree | 52ecaae3b27b33e9de36b354cbc2d17e2334f411 /lisp/shell.el | |
parent | 201133802956936332f1c4ce04eac42dfd1cf1c6 (diff) | |
download | emacs-c0a193ea2017fbaa6fb3e64a07125878656da156.tar.gz |
* lisp/shell.el: Use lexical-binding and std completion UI.
(shell-filter-ctrl-a-ctrl-b): Work as a preoutput filter.
(shell-mode): Put shell-filter-ctrl-a-ctrl-b on
comint-preoutput-filter-functions rather than on
comint-output-filter-functions.
(shell-command-completion, shell--command-completion-data)
(shell-filename-completion, shell-environment-variable-completion)
(shell-c-a-p-replace-by-expanded-directory): New functions.
(shell-dynamic-complete-functions, shell-dynamic-complete-command)
(shell-dynamic-complete-filename, shell-replace-by-expanded-directory)
(shell-dynamic-complete-environment-variable): Use them.
(shell-dynamic-complete-as-environment-variable)
(shell-dynamic-complete-as-command): Remove.
(shell-match-partial-variable): Match past point.
* lisp/comint.el: Clean up use of completion-at-point-functions.
(comint-completion-at-point): New function.
(comint-mode): Use it completion-at-point-functions.
(comint-dynamic-complete): Make it obsolete.
(comint-replace-by-expanded-history-before-point): Add dry-run arg.
(comint-c-a-p-replace-by-expanded-history): New function.
(comint-dynamic-complete-functions)
(comint-replace-by-expanded-history): Use it.
* lisp/minibuffer.el (completion-table-with-terminator): Allow dynamic
termination strings. Try harder to avoid second try-completion.
(completion-in-region-mode-map): Disable bindings that don't work yet.
Diffstat (limited to 'lisp/shell.el')
-rw-r--r-- | lisp/shell.el | 217 |
1 files changed, 122 insertions, 95 deletions
diff --git a/lisp/shell.el b/lisp/shell.el index 57187b6d7f9..d6bc685618c 100644 --- a/lisp/shell.el +++ b/lisp/shell.el @@ -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. @@ -79,7 +79,7 @@ ;; 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 @@ -96,6 +96,7 @@ ;;; Code: +(eval-when-compile (require 'cl)) (require 'comint) ;;; Customization and Buffer Variables @@ -181,12 +182,12 @@ 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 - '(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 + shell-filename-completion + comint-filename-completion) "List of functions called to perform completion. This variable is used to initialize `comint-dynamic-complete-functions' in the shell buffer. @@ -312,7 +313,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 -`comint-dynamic-complete'. +`comint-dynamic-complete-functions'. This variable supplies a default for `comint-input-autoexpand', for Shell mode only." @@ -339,7 +340,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) - (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] @@ -486,7 +487,7 @@ buffer." (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. @@ -494,7 +495,7 @@ buffer." 'shell-dir-cookie-watcher nil t)) (comint-read-input-ring t))) -(defun shell-filter-ctrl-a-ctrl-b (_string) +(defun shell-filter-ctrl-a-ctrl-b (string) "Remove `^A' and `^B' characters from comint output. Bash uses these characters as internal quoting characters in its @@ -504,15 +505,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. -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. @@ -1011,30 +1007,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 -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 ())) @@ -1058,18 +1060,31 @@ 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. - (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) + (completion-table-with-terminator + " " (lambda (string pred action) + (if (string-match "/" string) + (completion-file-name-table string pred action) + (complete-with-action action completions string pred))) + string pred action))))) + +;; (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 @@ -1077,24 +1092,21 @@ filename argument." (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 @@ -1106,38 +1118,69 @@ called `shell-dynamic-complete-process-environment-variable'. 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 + (apply-partially + #'completion-table-with-terminator + (cons (lambda (comp) + (concat comp + suffix + (if (file-directory-p + (comint-directory (getenv comp))) + "/"))) + "\\`a\\`") + variables)))))) + + +(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. @@ -1146,24 +1189,8 @@ See `default-directory' and `shell-dirstack'. 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) |