5

I'm learning about the more powerful tab-completion and expansion capabilities of zsh, and they don't seem to work when I run zsh under emacs with M-x shell:

cat $PATH<TAB> expands the tab variable in Terminal, but in shell-mode it just beeps.

I poked around the emacs environment and here's what I found:

TAB (translated from ) runs the command completion-at-point, which is an interactive compiled Lisp function in `minibuffer.el'.

It is bound to TAB, .

(completion-at-point)

Perform completion on the text around point. The completion method is determined by `completion-at-point-functions'.

completion-at-point-functions is a variable defined in `minibuffer.el'. Its value is (tags-completion-at-point-function)

So I'm surmising I need to add a function to completion-at-point-functions, but which one?

4 Answers 4

4

You can't use shell completion inside M-x shell. Emacs sends the input to the shell one line at a time when you press RET. When you press TAB, it's Emacs's built-in completion that's triggered. In shell mode, Emacs tries to keep track of your current directory and completes file names, but that's all.

Depending on what program you're interacting with, shell mode can be nice (because you get all of Emacs's edition features instead of whatever limited capabilities the shell or other program provides) or not so nice (because you don't get whatever nifty capabilities the shell or other program provides. When running zsh, you're in the latter category. Inside Emacs, you can run M-x term to run a more complete terminal emulator inside Emacs, where you interact directly with the underlying program. You gain some, but you also lose some: Term mode's scrollback capabilities are poor.

You can switch between Term mode and Shell mode in the same buffer. You must start the buffer in Term mode, then you can use these functions to toggle between the two. (Note: I didn't write these functions originally, it was contributed to the Emacs Wiki and I have not tracked down the source. I have slightly adapted it; warning: I haven't tested this code.)

(defun term-switch-to-shell-mode ()
  (interactive)
  (shell-mode)
  (set-process-filter
   (get-buffer-process (current-buffer)) 'comint-output-filter )
  (compilation-shell-minor-mode 1)
  (comint-send-input))
(defun shell-switch-to-term-mode ()
  (compilation-shell-minor-mode -1)
  (font-lock-mode -1)
  (set-process-filter
   (get-buffer-process (current-buffer)) 'term-emulate-terminal)
  (term-mode)
  (term-char-mode)
  (term-send-raw-string (kbd "C-l")))
3

Try MultiTerm. Its the only Emacs terminal mode that seems to play nice with zsh.

With 'M-x shell' mode you can't use zle (the cool zsh tab completion stuff).

With 'M-x term' (and ansi-term), the terminal captures most of the emacs C- and M- commands that you want routed to emacs.

MultiTerm allows you to easily set which commands you want captured by emacs and which you want routed to the terminal. It comes preloaded with defaults that work very well.

One more tip: Add the following to your .zshrc to allow emacs to track your current directory as you cd around.

if [ -n "$INSIDE_EMACS" ]; then
  chpwd() { print -P "\033AnSiTc %d" }
  print -P "\033AnSiTu %n"
  print -P "\033AnSiTc %d"
fi
2
  • Could you elaborate on what the zsh snippet is doing? Commented Jan 31, 2015 at 18:34
  • Thanks a lot :) i made a minor modification to the snippet to fix auto completion issues when using grml config (grml.org/zsh) and posted it as another answer.
    – 0x17de
    Commented Aug 4, 2018 at 13:55
1

This is a complex problem. Let's look at what really happens behind the sense.

According to Emacs Doc, <Tab> is bound to function completion-at-point. Here is what completion-at-point does:

Perform completion on the text around point.

The completion method is determined by `completion-at-point-functions'.

Check Emacs Doc, the variable completion-at-point-functions goes here:

completion-at-point-functions is a variable defined in `minibuffer.el'.

Its value is (comint-completion-at-point t)

Local in buffer shell; global value is

(tags-completion-at-point-function)

Then let's check out document of comint-completion-at-point

comint-completion-at-point is an alias for icicle-comint-dynamic-complete' inob-sh.el'.

(comint-completion-at-point)

Dynamically perform completion at point.

Calls the functions in `comint-dynamic-complete-functions', but with

Icicles functions substituted, to perform completion until a function

returns non-nil. Return that value.

So it's clear that you need to add your favourite complete functions to comint-dynamic-complete-functions variable.

      (add-to-list 'comint-dynamic-complete-functions 'icicle-shell-dynamic-complete-filename)
      (add-to-list 'comint-dynamic-complete-functions 'icicle-shell-dynamic-complete-command)
0

Thanks bengineerd for the good explanation and snippet :)

I use zsh together with grml config - the chpwd function totally did the trick, but some things were broken. Here is the modified snippet to have that arrow key auto completion back:

if [ -n "$INSIDE_EMACS" ]; then
  # keep the old chpwd command and apply it later
  which zsh_chpwd >&/dev/null || \
    eval "$(echo "zsh_chpwd() {"; declare -f chpwd | tail -n +2)"
  chpwd() {
    print -P "\033AnSiTc %d"
    zsh_chpwd
  }
  print -P "\033AnSiTu %n"
  print -P "\033AnSiTc %d"
fi

and append that at the bottom of your .zshrc


Update

To fix jumping and deleting in terminals i found several sources which helped to improve the overall experience:

and combined them into https://github.com/0x17de/emacs-config/blob/master/multi-term-settings.el

Here is some excerpt of the critical part, which enabled meta+arrow keys (word jumping) and backwards word deletion:

(add-hook 'term-mode-hook
  (lambda ()
    (define-key term-raw-map (kbd "M-d") 'term-send-raw-meta)
    (define-key term-raw-map (kbd "M-<left>") 'term-send-backward-word)
    (define-key term-raw-map (kbd "M-<right>") 'term-send-forward-word)
    (define-key term-raw-map (kbd "M-<backspace>") 'term-send-backward-kill-word))
    (add-to-list 'term-bind-key-alist '("C-z e" . term-send-esc))))

(defun term-send-esc ()
  "Send ESC in term mode."
  (interactive)
  (term-send-raw-string "\e"))

See term-send-esc from the git repo if you ssh into a remote, enter insert mode on some vim and get stuck because there is no mapping for ESC yet.


also just a little related - with grml zshrc if you want that arrow key completion to happen also if you have less than five options, search for

 zstyle ':completion:*'               menu select=5

and change that "5" (default value) to "1"

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .