`comint-use-prompt-regexp'.")
(defvar comint-dynamic-complete-functions
- '(comint-replace-by-expanded-history comint-filename-completion)
+ '(comint-c-a-p-replace-by-expanded-history comint-filename-completion)
"List of functions called to perform completion.
Works like `completion-at-point-functions'.
See also `comint-dynamic-complete'.
(define-key map [menu-bar completion complete-file]
'("Complete File Name" . comint-dynamic-complete-filename))
(define-key map [menu-bar completion complete]
- '("Complete Before Point" . comint-dynamic-complete))
+ '("Complete at Point" . completion-at-point))
;; Input history:
(define-key map [menu-bar inout]
(cons "In/Out" (make-sparse-keymap "In/Out")))
(setq font-lock-defaults '(nil t))
(add-hook 'change-major-mode-hook 'font-lock-defontify nil t)
(add-hook 'isearch-mode-hook 'comint-history-isearch-setup nil t)
+ (add-hook 'completion-at-point-functions 'comint-completion-at-point nil t)
;; This behavior is not useful in comint buffers, and is annoying
(set (make-local-variable 'next-line-add-newlines) nil))
Returns t if successful."
(interactive)
+ (let ((f (comint-c-a-p-replace-by-expanded-history silent start)))
+ (if f (funcall f))))
+
+(defun comint-c-a-p-replace-by-expanded-history (&optional silent start)
+ "Expand input command history at point.
+For use on `completion-at-point-functions'."
(if (and comint-input-autoexpand
(if comint-use-prompt-regexp
;; Use comint-prompt-regexp
;; Use input fields. User input that hasn't been entered
;; yet, at the end of the buffer, has a nil `field' property.
(and (null (get-char-property (point) 'field))
- (string-match "!\\|^\\^" (field-string)))))
- ;; Looks like there might be history references in the command.
- (let ((previous-modified-tick (buffer-modified-tick)))
- (comint-replace-by-expanded-history-before-point silent start)
- (/= previous-modified-tick (buffer-modified-tick)))))
-
-
-(defun comint-replace-by-expanded-history-before-point (silent &optional start)
+ (string-match "!\\|^\\^" (field-string))))
+ (catch 'dry-run
+ (comint-replace-by-expanded-history-before-point
+ silent start 'dry-run)))
+ (lambda ()
+ ;; Looks like there might be history references in the command.
+ (let ((previous-modified-tick (buffer-modified-tick)))
+ (comint-replace-by-expanded-history-before-point silent start)
+ (/= previous-modified-tick (buffer-modified-tick))))))
+
+
+(defun comint-replace-by-expanded-history-before-point
+ (silent &optional start dry-run)
"Expand directory stack reference before point.
See `comint-replace-by-expanded-history'. Returns t if successful.
If the optional argument START is non-nil, that specifies the
start of the text to scan for history references, rather
-than the logical beginning of line."
+than the logical beginning of line.
+
+If DRY-RUN is non-nil, throw to DRY-RUN before performing any
+actual side-effect."
(save-excursion
(let ((toend (- (line-end-position) (point)))
(start (or start (comint-line-beginning-position))))
(goto-char (1+ (point))))
((looking-at "![0-9]+\\($\\|[^-]\\)")
;; We cannot know the interpreter's idea of input line numbers.
+ (if dry-run (throw dry-run 'message))
(goto-char (match-end 0))
(message "Absolute reference cannot be expanded"))
((looking-at "!-\\([0-9]+\\)\\(:?[0-9^$*-]+\\)?")
;; Just a number of args from `number' lines backward.
+ (if dry-run (throw dry-run 'history))
(let ((number (1- (string-to-number
(buffer-substring (match-beginning 1)
(match-end 1))))))
(message "Relative reference exceeds input history size"))))
((or (looking-at "!!?:?\\([0-9^$*-]+\\)") (looking-at "!!"))
;; Just a number of args from the previous input line.
+ (if dry-run (throw dry-run 'expand))
(replace-match (comint-args (comint-previous-input-string 0)
(match-beginning 1) (match-end 1))
t t)
"!\\??\\({\\(.+\\)}\\|\\(\\sw+\\)\\)\\(:?[0-9^$*-]+\\)?")
;; Most recent input starting with or containing (possibly
;; protected) string, maybe just a number of args. Phew.
+ (if dry-run (throw dry-run 'expand))
(let* ((mb1 (match-beginning 1)) (me1 (match-end 1))
(mb2 (match-beginning 2)) (me2 (match-end 2))
(exp (buffer-substring (or mb2 mb1) (or me2 me1)))
(message "History item: %d" (1+ pos)))))
((looking-at "\\^\\([^^]+\\)\\^?\\([^^]*\\)\\^?")
;; Quick substitution on the previous input line.
+ (if dry-run (throw dry-run 'expand))
(let ((old (buffer-substring (match-beginning 1) (match-end 1)))
(new (buffer-substring (match-beginning 2) (match-end 2)))
(pos nil))
(replace-match new t t)
(message "History item: substituted"))))
(t
- (forward-char 1)))))))
+ (forward-char 1)))))
+ nil))
(defun comint-magic-space (arg)
(insert copy)
copy)))
(input (if (not (eq comint-input-autoexpand 'input))
- ;; Just whatever's already there
+ ;; Just whatever's already there.
intxt
- ;; Expand and leave it visible in buffer
+ ;; Expand and leave it visible in buffer.
(comint-replace-by-expanded-history t pmark)
(buffer-substring pmark (point))))
(history (if (not (eq comint-input-autoexpand 'history))
(setq i (+ 1 (match-beginning 0)))))
filename)))
+(defun comint-completion-at-point ()
+ (run-hook-with-args-until-success 'comint-dynamic-complete-functions))
-(defun comint-dynamic-complete ()
- "Dynamically perform completion at point.
-Calls the functions in `comint-dynamic-complete-functions' to perform
-completion until a function returns non-nil, at which point completion is
-assumed to have occurred."
- (interactive)
- (let ((completion-at-point-functions comint-dynamic-complete-functions))
- (completion-at-point)))
-
+(define-obsolete-function-alias
+ 'comint-dynamic-complete
+ 'completion-at-point "24.1")
(defun comint-dynamic-complete-filename ()
"Dynamically complete the filename at point.
in which case TERMINATOR-REGEXP is a regular expression whose submatch
number 1 should match TERMINATOR. This is used when there is a need to
distinguish occurrences of the TERMINATOR strings which are really terminators
-from others (e.g. escaped)."
+from others (e.g. escaped). In this form, the car of TERMINATOR can also be,
+instead of a string, a function that takes the completion and returns the
+\"terminated\" string."
;; FIXME: This implementation is not right since it only adds the terminator
;; in try-completion, so any completion-style that builds the completion via
;; all-completions won't get the terminator, and selecting an entry in
(bounds (completion-boundaries string table pred suffix))
(terminator-regexp (if (consp terminator)
(cdr terminator) (regexp-quote terminator)))
- (max (string-match terminator-regexp suffix)))
+ (max (and terminator-regexp
+ (string-match terminator-regexp suffix))))
(list* 'boundaries (car bounds)
(min (cdr bounds) (or max (length suffix))))))
((eq action nil)
(let ((comp (try-completion string table pred)))
(if (consp terminator) (setq terminator (car terminator)))
(if (eq comp t)
- (concat string terminator)
- (if (and (stringp comp)
- ;; FIXME: Try to avoid this second call, especially since
+ (if (functionp terminator)
+ (funcall terminator string)
+ (concat string terminator))
+ (if (and (stringp comp) (not (zerop (length comp)))
+ ;; Try to avoid the second call to try-completion, since
;; it may be very inefficient (because `comp' made us
;; jump to a new boundary, so we complete in that
;; boundary with an empty start string).
- ;; completion-boundaries might help.
+ (let ((newbounds (completion-boundaries comp table pred "")))
+ (< (car newbounds) (length comp)))
(eq (try-completion comp table pred) t))
- (concat comp terminator)
+ (if (functionp terminator)
+ (funcall terminator comp)
+ (concat comp terminator))
comp))))
((eq action t)
;; FIXME: We generally want the `try' and `all' behaviors to be
(defvar completion-in-region-mode-map
(let ((map (make-sparse-keymap)))
+ ;; FIXME: Only works if completion-in-region-mode was activated via
+ ;; completion-at-point called directly.
(define-key map "?" 'completion-help-at-point)
(define-key map "\t" 'completion-at-point)
map)
-;;; 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.
;; 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)
;;; Customization and Buffer Variables
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.
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]
(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-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
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.
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)
+ (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
(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
+ (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.
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)