问一个比较SB的问题:
现在 emacs 中的主力终端换成 vterm 了,在 vterm 里用什么办法能打开当前目录的 dired buffer呢?或者用当前 emacs 进程打开文件编辑呢?(除了用 emacsclient)
问一个比较SB的问题:
现在 emacs 中的主力终端换成 vterm 了,在 vterm 里用什么办法能打开当前目录的 dired buffer呢?或者用当前 emacs 进程打开文件编辑呢?(除了用 emacsclient)
readme里有详细说明
## Message passing
`vterm` can read and execute commands. At the moment, a command is
passed by providing a specific escape sequence. For example, to evaluate
``` elisp
(message "Hello!")
use
printf "\e]51;Emessage \"Hello\!\"\e\\"
The commands that are understood are defined in the setting vterm-eval-cmds
.
As split-string-and-unquote
is used the parse the passed string, double quotes
and backslashes need to be escaped via backslash. For instance, bash can replace
strings internally.
vterm_cmd() {
printf "\e]51;E"
local r
while [[ $# -gt 0 ]]; do
r="${1//\\/\\\\}"
r="${r//\"/\\\"}"
printf '"%s" ' "$r"
shift
done
printf "\e\\"
}
However if you are using dash and need a pure POSIX implementation:
vterm_cmd() {
printf "\e]51;E"
while [ $# -gt 0 ]; do
printf '"%s" ' "$(printf "%s" "$1" | sed -e 's|\\|\\\\|g' -e 's|"|\\"|g')"
shift
done
printf "\e\\"
}
Now we can write shell functions to call the ones defined in vterm-eval-cmds
.
find_file() {
vterm_cmd find-file "$(realpath "$@")"
}
say() {
vterm_cmd message "%s" "$*"
}
This can be used inside vterm
as
find_file name_of_file_in_local_directory
As an example, say you like having files opened below the current window. You could add the command to do it on the lisp side like so:
(push (list "find-file-below"
(lambda (path)
(if-let* ((buf (find-file-noselect path))
(window (display-buffer-below-selected buf nil)))
(select-window window)
(message "Failed to open file: %s" path))))
vterm-eval-cmds)
Then add the command in your .bashrc
file.
open_file_below() {
vterm_cmd find-file-below "$(realpath "$@")"
}
Then you can open any file from inside your shell.
open_file_below ~/Documents
或许可以尝试命令行提示符获得当前路径,然后直接用 C-x C-f。我试了试在 M-x ansi-term 下这么处理:
(defun ansi-term-find-file ()
(interactive)
(let ((default-directory
(or
;; 对命令行提示符格式有要求:绝对路径 + 空格开头,如
;; ~/.emacs.d/lisp $
(save-excursion
(goto-char (line-beginning-position))
(and-let* ((f (thing-at-point 'filename))
((file-exists-p f))
((file-directory-p f))
(f (expand-file-name f))
(d (file-name-as-directory f)))
d))
default-directory)))
(call-interactively #'helm-find-files)))
(bind-key "C-x C-f" #'ansi-term-find-file term-mode-map)
(bind-key "C-x C-f" #'ansi-term-find-file term-raw-map)
用 emacsclient 也挺方便,开了 Emacs Server 就 OK 了。
直接 C-x d
和 C-x C-f
不就是打开 vterm 所处的路径吗?
说的好有道理,可是有些人就喜欢敲 vi filename 在emacs中打开:joy:
还是不完全一样,比如vterm下随便cd到别处,但vterm buffer 的 default-directory还是初始化时的路径
这种要改 vterm 代码,终端模拟器一般会在路径变化的时候发出信号,这时候需要添加回调改变 emacs buffer default-directory 的值
楼上二位准确地说出了我的痛点
人家本来就支持啊:
vterm
supports directory tracking . If this feature is enabled, the default directory in Emacs and the current working directory in vterm
are synced. As a result, interactive functions that ask for a path or a file (e.g., dired
or find-file
) will do so starting from the current location.
默认没打开?
首页那个文档, 你找到这一段, 接着往下看, 有开启方法
M-x shel 可以,M-x term 不行。可以想办法通知 Emacs 更新工作路径,比如利用 shell 更新提示符时调用 emacsclient,不过应该比较折腾,可能还会提高 shell 的延迟。我上面的方式是直接从 PROMPT 中猜,优点是简单,缺点是必须用特定格式的 PROMPT,而且不能有命令在运行。
抱歉没仔细看文档 。而且这个设置不是在 emacs 里设(在 bashrc 里设):
vterm_prompt_end(){
printf "\e]51;A$(whoami)@$(hostname):$(pwd)\e\\"
}
PS1=$PS1'$(vterm_prompt_end)'
亲测可行,感谢各位提供帮助,特别是 @netjune 指出了文档中已有解决的方法。(本来是勾选 @netjune 的回复作为答案,但是为了方便后面的同学,所以直接把这个粘贴了代码段的楼层作为答案。)
evil
用户还得再等等。
我大致看了一下,vterm.el 目前只能发送按键信息给 vterm-module:
;; elisp
(vterm--update vterm--term key shift meta ctrl)
;; c
env->copy_string_contents(env, args[1], (char *)key, &len);
VTermModifier modifier = VTERM_MOD_NONE;
if (env->is_not_nil(env, args[2]))
modifier = modifier | VTERM_MOD_SHIFT;
if (env->is_not_nil(env, args[3]))
modifier = modifier | VTERM_MOD_ALT;
if (env->is_not_nil(env, args[4]))
modifier = modifier | VTERM_MOD_CTRL;
也就是说无法通知 vterm 删除一个 region,所以 evil normal 状态那些快捷操作都不行:
如果可以,实在是不想动.zshrc
。因为用的grml-zsh-config
,会重置PROMPT
变量,导致在.zshrc
里设置之后会被覆盖。于是vterm
推荐的那种根据prompt
来识别的方案就会失效。
一个想法:
但是为什么不通过shell-dirtrack-mode
来实现目录同步呢?这是通过监测cd
, pushd
, popd
这种会更改目录的命令来实现的。这个后面是依托于comint-mode
(command-interpreter-in-a-buffer) 来实现的。如果vterm-mode
继承了comint-mode
,是不是就可以令shell-dirtrack-mode
生效了?
以上想法已经验证失败。
目前解法是在C-x C-f
的时候同步一下目录。因为用到了procfs
,所以是linux
限定。
不清楚mac
能不能用。
(use-package vterm
:ensure t
:custom
(vterm-kill-buffer-on-exit t)
(vterm-clear-scrollback-when-clearing t)
:bind (:map vterm-mode-map
("C-x C-f" . (lambda (&rest _)
(interactive)
(my/vterm-directory-sync)
(call-interactively 'counsel-find-file))))
:config
;; Directory synchronization (linux-only)
(defun my/vterm-directory-sync ()
"Synchronize current working directory."
(when vterm--process
(let* ((pid (process-id vterm--process))
(dir (file-truename (format "/proc/%d/cwd" pid))))
(setq-local default-directory dir))))
)
演示动画
理论上 你可以这样用, 但是这样有个副作用, 无法正确定位PROMPT的结束位置, vterm–at-prompt-p vterm-beginning-of-line 等函数会有bug
autoload -U add-zsh-hook
add-zsh-hook -Uz chpwd (){
vterm_printf "51;A$(whoami)@$(hostname):$(pwd)"
}