vterm 里打开 dired 或者编辑文件应该怎么做?

spacemacs

#1

问一个比较SB的问题:

现在 emacs 中的主力终端换成 vterm 了,在 vterm 里用什么办法能打开当前目录的 dired buffer呢?或者用当前 emacs 进程打开文件编辑呢?(除了用 emacsclient)


#2

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

#3

或许可以尝试命令行提示符获得当前路径,然后直接用 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 了。


#4

直接 C-x dC-x C-f 不就是打开 vterm 所处的路径吗?


#5

说的好有道理,可是有些人就喜欢敲 vi filename 在emacs中打开:joy:


#6

还是不完全一样,比如vterm下随便cd到别处,但vterm buffer 的 default-directory还是初始化时的路径


#7

这种要改 vterm 代码,终端模拟器一般会在路径变化的时候发出信号,这时候需要添加回调改变 emacs buffer default-directory 的值


#8

楼上二位准确地说出了我的痛点


#9

人家本来就支持啊:

Directory tracking

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.


#10

默认没打开?


#11

首页那个文档, 你找到这一段, 接着往下看, 有开启方法


#12

M-x shel 可以,M-x term 不行。可以想办法通知 Emacs 更新工作路径,比如利用 shell 更新提示符时调用 emacsclient,不过应该比较折腾,可能还会提高 shell 的延迟。我上面的方式是直接从 PROMPT 中猜,优点是简单,缺点是必须用特定格式的 PROMPT,而且不能有命令在运行。


#13

抱歉没仔细看文档 :joy:。而且这个设置不是在 emacs 里设(在 bashrc 里设):

vterm_prompt_end(){
    printf "\e]51;A$(whoami)@$(hostname):$(pwd)\e\\"
}
PS1=$PS1'$(vterm_prompt_end)'

亲测可行,感谢各位提供帮助,特别是 @netjune 指出了文档中已有解决的方法。(本来是勾选 @netjune 的回复作为答案,但是为了方便后面的同学,所以直接把这个粘贴了代码段的楼层作为答案。)


#15

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 状态那些快捷操作都不行: