在Emacs的shell模式下使用Bash自动完成

98

在 GNOME 终端中,Bash 支持智能自动补全。例如:

apt-get in<TAB>

成为

apt-get install
在Emacs的shell-mode中,即使我明确地使用/etc/bash_completion命令,在这里自动完成功能也无法工作。以上示例将粘滞为in,或者自动完成为当前目录中的文件名,而不是一个有效的apt-get命令选项。这可能是因为Emacs截获了Tab键按下事件。如何在shell-mode中启用智能自动完成?

对于任何新手来说,Emacs 中还有其他 shell 模式。例如 eshell-mode 具有制表符自动完成功能。更多信息请参见:http://www.masteringemacs.org/articles/2010/11/01/running-shells-in-emacs-overview/ - Ross
4
Eshell 的自动补全只适用于本地目录,如果你通过 SSH 连接到另一台机器,你将突然失去这个功能。 - hajovonta
9个回答

102

我知道这个问题已经三年了,但这也是我一直想要解决的问题。一次网络搜索让我找到了一段elisp代码,可以使Emacs在shell模式下使用bash进行自动补全。无论如何,它对我很有效。

请查看 https://github.com/szermatt/emacs-bash-completion 获取更多信息。


6
感谢你有勇气回答一个三年前的问题,但它仍然非常相关。+1。谢谢! - djhaskin987
4
这个已经在melpa中(http://melpa.org/#/bash-completion),使用`M-x package-list-packages`很容易安装,但最后它并没有对我起作用,我不知道为什么,可能是因为我的emacs(24.3.1)或者bash(4.3.30)版本。文档上说"bash-completion.el对操作系统和BASH版本非常敏感...",然后列出了一个列表,我的版本不在其中。 - boclodoa
不幸的是,由于待处理的问题#6,这似乎对当前的CLI工具并不那么有用。 - Jesse Glick
第6个问题已经关闭,原话是“至少大部分已经修复/需要一个不起作用的示例”。 - WHO's NoToOldRx4CovidIsMurder

21

在emacs shell中,实际上是emacs进行自动补全,而不是bash。如果shell和emacs不同步(例如使用pushd、popd或某个bash用户函数更改了shell的当前目录),那么自动补全就会停止工作。

要解决这个问题,只需在shell中键入“dirs”,然后事情就会重新同步。

我还在我的.emacs中添加了以下内容:

(global-set-key "\M-\r" 'shell-resync-dirs)

按下 Esc 键然后按下回车键即可重新同步自动完成。


11
这是对另一个问题的不错回答! - Chris Conway
谢谢。我使用了autojump,这就是目录不同步的问题所在。 - Plankalkül
克里斯·康维,是的,对于这个:http://emacs.stackexchange.com/questions/30550/company-mode-in-shell-mode-doesnt-complete-executables/30552 :-) - unhammer

16

我不知道这个问题的答案。但它不能按照您的期望工作的原因可能是因为emacs shell中的补全由emacs内部处理(通过comint-dynamic-complete函数),并没有内置这些智能补全功能。

恐怕这不是一个容易解决的问题。

编辑:njsf建议使用term-mode可能是最好的选择。用

M-x term
启动。 它已经包含在标准的emacs发行版中(至少在Ubuntu和Debian的emacs21-common或emacs22-common中也是如此)。


3
M-x term 中,使用Tab键来自动补全更加糟糕。 - mcandre

10
请考虑使用另一种模式M-x term,就像我在2011年遇到问题时所做的那样。当时我试图收集所有网上的努力来使shell与Bash完成工作,包括这个问题。但是自从发现了替代方案term-mode后,我甚至都不想尝试eshell
它是完整的终端仿真器,因此您可以在其中运行交互式程序,例如Midnight commander。或者切换到zsh完成,以便您不会浪费时间在Emacs配置上。
您可以免费获得bash中的TAB完成。但更重要的是,您可以获得完整的Readline功能,例如增量或前缀命令搜索。为了使此设置更加方便,请查看我的.inputrc.bashrc.emacs.inputrc的关键部分:
# I like this!
set editing-mode emacs

# Don't strip characters to 7 bits when reading.
set input-meta on

# Allow iso-latin1 characters to be inserted rather than converted to
# prefix-meta sequences.
set convert-meta off

# Display characters with the eighth bit set directly rather than as
# meta-prefixed characters.
set output-meta on

# Ignore hidden files.
set match-hidden-files off

# Ignore case (on/off).
set completion-ignore-case on

set completion-query-items 100

# First tab suggests ambiguous variants.
set show-all-if-ambiguous on

# Replace common prefix with ...
set completion-prefix-display-length 1

set skip-completed-text off

# If set to 'on', completed directory names have a slash appended. The default is 'on'.
set mark-directories on
set mark-symlinked-directories on

# If set to 'on', a character denoting a file's type is appended to the
# filename when listing possible completions. The default is 'off'.
set visible-stats on

set horizontal-scroll-mode off

$if Bash
"\C-x\C-e": edit-and-execute-command
$endif

# Define my favorite Emacs key bindings.
"\C-@": set-mark
"\C-w": kill-region
"\M-w": copy-region-as-kill

# Ctrl+Left/Right to move by whole words.
"\e[1;5C": forward-word
"\e[1;5D": backward-word
# Same with Shift pressed.
"\e[1;6C": forward-word
"\e[1;6D": backward-word

# Ctrl+Backspace/Delete to delete whole words.
"\e[3;5~": kill-word
"\C-_": backward-kill-word

# UP/DOWN filter history by typed string as prefix.
"\e[A": history-search-backward
"\C-p": history-search-backward
"\eOA": history-search-backward
"\e[B": history-search-forward
"\C-n": history-search-forward
"\eOB": history-search-forward

# Bind 'Shift+TAB' to complete as in Python TAB was need for another purpose.
"\e[Z": complete
# Cycling possible completion forward and backward in place.
"\e[1;3C": menu-complete                    # M-Right
"\e[1;3D": menu-complete-backward           # M-Left
"\e[1;5I": menu-complete                    # C-TAB

.bashrc文件(太好了!在Bash中可以从~/.bash_history的任何单词中使用dabbrev):

set -o emacs

if [[ $- == *i* ]]; then
  bind '"\e/": dabbrev-expand'
  bind '"\ee": edit-and-execute-command'
fi

.emacs文件用于在终端缓冲区中提供舒适的导航体验:

(setq term-buffer-maximum-size (lsh 1 14))

(eval-after-load 'term
  '(progn
    (defun my-term-send-delete-word-forward () (interactive) (term-send-raw-string "\ed"))
    (defun my-term-send-delete-word-backward () (interactive) (term-send-raw-string "\e\C-h"))
    (define-key term-raw-map [C-delete] 'my-term-send-delete-word-forward)
    (define-key term-raw-map [C-backspace] 'my-term-send-delete-word-backward)
    (defun my-term-send-forward-word () (interactive) (term-send-raw-string "\ef"))
    (defun my-term-send-backward-word () (interactive) (term-send-raw-string "\eb"))
    (define-key term-raw-map [C-left] 'my-term-send-backward-word)
    (define-key term-raw-map [C-right] 'my-term-send-forward-word)
    (defun my-term-send-m-right () (interactive) (term-send-raw-string "\e[1;3C"))
    (defun my-term-send-m-left () (interactive) (term-send-raw-string "\e[1;3D"))
    (define-key term-raw-map [M-right] 'my-term-send-m-right)
    (define-key term-raw-map [M-left] 'my-term-send-m-left)
    ))

(defun my-term-mode-hook ()
  (goto-address-mode 1))
(add-hook 'term-mode-hook #'my-term-mode-hook)

由于终端仿真模式下常规命令如C-x o无法正常使用,因此我通过扩展键映射来解决:

(unless
    (ignore-errors
      (require 'ido)
      (ido-mode 1)
      (global-set-key [?\s-d] #'ido-dired)
      (global-set-key [?\s-f] #'ido-find-file)
      t)
  (global-set-key [?\s-d] #'dired)
  (global-set-key [?\s-f] #'find-file))

(defun my--kill-this-buffer-maybe-switch-to-next ()
  "Kill current buffer. Switch to next buffer if previous command
was switching to next buffer or this command itself allowing
sequential closing of uninteresting buffers."
  (interactive)
  (let ( (cmd last-command) )
    (kill-buffer (current-buffer))
    (when (memq cmd (list 'next-buffer this-command))
      (next-buffer))))
(global-set-key [s-delete] 'my--kill-this-buffer-maybe-switch-to-next)
(defun my--backward-other-window ()
  (interactive)
  (other-window -1))
(global-set-key [s-up] #'my--backward-other-window)
(global-set-key [s-down] #'other-window)
(global-set-key [s-tab] 'other-window)

请注意,我使用super键,以便term-raw-map和可能的任何其他键映射不会与我的按键绑定冲突。为了将左边的Win键变成super键,我使用.xmodmaprc文件:
! To load this config run:
!   $ xmodmap .xmodmaprc

! Win key.
clear mod3
clear mod4

keycode 133 = Super_L
keycode 134 = Hyper_R
add mod3 = Super_L
add mod4 = Hyper_R

您只需要记住两个命令:C-c C-j - 进入普通的Emacs编辑模式(用于复制或在缓冲区文本中查找),C-c C-k - 返回终端仿真模式。

鼠标选择和 Shift-Insert 的工作方式与 xterm 相同。


1
谢谢,这太棒了!特别是.inputrc部分! - cmantas

6

就像Matli所说的那样,这不是一项易如反掌的任务,因为bash是以--noediting启动的,并且TAB键被绑定到comint-dynamic-complete。

可以在shell-command-hook中使用local-set-key将TAB重新绑定到self-insert-command,使shell-mode不再以--noediting启动,方法是M-x customize-variable然后RETEexplicit-bash-args,但我怀疑它可能无法很好地适应所有其他编辑。

您可以尝试term-mode,但它有另一组问题,因为term-mode接管了其他一些常规按键绑定。

编辑:我指被term-mode接管的其他常规按键绑定,除了C-c之外,它成为了切换缓冲区的转义键。因此,用于杀死缓冲区的C-x k变为了C-c C-x k。或者要切换到另一个缓冲区,则是'C-c C-x o'或'C-c C-x 2'


self-insert-command不是它:它插入的是TAB字符,而不是将TAB键按下传递给Bash。 - Chris Conway
我没有术语模式命令,也不知道从哪里获取它。 - Chris Conway

4

我知道这篇文章已经超过11年了。但是我已经创建了一个函数,可以在Emacs中提供本地shell自动补全功能。它只是向底层进程发送一个tab键,并拦截输出,所以与你在shell本身中得到的完全相同。

https://github.com/CeleritasCelery/emacs-native-shell-complete


哇!太棒了!(基于预览的第一印象。) - WHO's NoToOldRx4CovidIsMurder
我很喜欢它。非常感谢你。如果当TAB无法完成单个命令时,因为有多个候选项,这些候选项在Emacs中像Bash一样出现,那就更好了。 - Jeffrey Benjamin Brown
你的意思是在终端中使用一个大列表,而不是使用Emacs完成显示吗? - Prgrm.celeritas

1
我使用Prelude,当我按下Meta+Tab时,它会自动为我完成。
此外,Ctrl+i似乎也可以完成相同的操作。

0

我使用helm模式。它有这个功能(按下“TAB”后): {{link1:输入图像描述}}


-2
我不是emacs专家,但这可以解决你的问题:
创建: ~/.emacs
添加以下内容:
(require 'shell-command) (shell-command-completion-mode)
Emacs接管了shell,因此BASH设置不会生效。 这将为EMACS本身设置自动完成。

不,这并不能解决问题。它只适用于 shell-command,而不适用于 shell-mode。此外,它也无法启用在问题中要求的智能完成功能(它只能完成命令和文件名)。 - matli
1
shell-command-completion-mode提供文件名补全功能,并默认启用。我想要Bash的补全功能,它可以扩展为包括apt-get和svn等子命令。 - Chris Conway

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接