感谢 @twlz0ne @jixiuf 的帮助
vterm-edit-command
用法是当你在 vterm 中按下 C-x C-e,会打开一个独立的 sh-mode
buffer,编辑完毕之后按下 C-c C-c,就会把这个 buffer 中的内容复制到 vterm 中去。
需要注意的是,像下面代码的第一行会在你按下 C-c C-c 后会立刻执行的,因为换行的回车动作也被发送到 vterm 中去了。第二行则不会执行,因为我在代码里执行了 string-trim
删掉了首尾的空白字符。总之执行之前要小心一些危险命令,可能没有第二次修改的机会了。
echo 1 # 会立刻执行
echo 2 # 不会执行
下面的是代码,在 vterm 加载后执行即可。
(require 'subr-x)
(define-key vterm-mode-map (kbd "C-x C-e") 'vterm-edit-command-action)
(defun vterm-edit-command-action ()
(interactive)
(let* ((vterm-buffer (current-buffer))
(begin-point (vterm--get-prompt-point))
(end-point (point)))
(setq vterm-edit-command--vterm-buffer vterm-buffer)
(setq vterm-edit-command--begin-point begin-point)
(setq vterm-edit-command--end-point end-point)
(kill-ring-save begin-point end-point)
(vterm-edit-command-buffer)))
(defun vterm-edit-command-commit ()
(interactive)
(let ((content (buffer-string)))
(with-current-buffer vterm-edit-command--vterm-buffer
(vterm-delete-region vterm-edit-command--begin-point vterm-edit-command--end-point)
(vterm-send-string (string-trim content))))
(vterm-edit-command-abort))
(defun vterm-edit-command-abort ()
(interactive)
(kill-buffer-and-window))
(defvar vterm-edit-command-mode-map
(let ((keymap (make-sparse-keymap)))
(define-key keymap (kbd "C-c C-c") #'vterm-edit-command-commit)
(define-key keymap (kbd "C-c C-k") #'vterm-edit-command-abort)
keymap))
(define-minor-mode vterm-edit-command-mode
"Vterm Edit Command Mode")
(defun vterm-edit-command-buffer ()
(let ((buffer (get-buffer-create "vterm-edit-command")))
(with-current-buffer buffer
(insert-buffer-substring
vterm-edit-command--vterm-buffer
vterm-edit-command--begin-point
vterm-edit-command--end-point)
(vterm--remove-fake-newlines)
(set-text-properties (point-min) (point-max) nil)
(goto-char (point-max))
(insert "\n")
(sh-mode)
(vterm-edit-command-mode)
(setq-local header-line-format
(substitute-command-keys
(concat "Edit, then "
(mapconcat
'identity
(list "\\[vterm-edit-command-commit]: Finish"
"\\[vterm-edit-command-abort]: Abort"
)
", "))))
(split-window-sensibly)
(switch-to-buffer-other-window buffer))))
(provide 'vterm-edit-command)
;;; vterm-edit-command.el ends here
4 个赞
我现在不在电脑旁,晚些时候我会把完整代码放到 Github。
1 个赞
vterm 上游 push了一个新的commit ,可以尝试拉下最新代码,验证一下。
1 个赞
这里的 (let ((inhibit-read-only nil))
是笔误还是有特别的意思?为 nil 则底下 (vterm--delete-region start end)
必然出错。
故意这么做的,真正的delete 只能由libvterm 触发, vterm–delete-region 内部 实际是调用 vterm-delete-char 等命令让底层去按字符一个个删除。加(inhibit-read-only nil) 就是为了防止误删导致不一致。
如果你删不了 必然是删了不该删的区域
可以尝试 M-: (vterm-delete-region (vterm--get-prompt-point) (point))
我这边是能执行成功的
3 个赞
这儿换成
(insert-buffer-substring
vterm-edit-command--vterm-buffer
vterm-edit-command--begin-point
vterm-edit-command--end-point)
(vterm--remove-fake-newlines)
对于 长行的处理 会好一些。
1 个赞
我加 no-properties 主要是想去掉 vterm 里面的高亮去使用 shell mode 里面的高亮。所以有没有办法把 fake-newlines 和高亮都去掉?
找到办法了,在后面加一句
(set-text-properties (point-min) (point-max) nil)
jixiuf
10
(defun vmacs-kill-buffer-dwim(&optional buf)
(interactive)
(save-buffer)
(server-edit))
(defun zsh-find-file-hook()
(when (string-prefix-p "/tmp/zsh" (buffer-file-name))
(setq truncate-lines nil)
(local-set-key (kbd "C-c C-k") #'server-edit-abort)
(local-set-key (kbd "C-c C-c") #'vmacs-kill-buffer-dwim)))
(add-hook #'sh-mode-hook #'zsh-find-file-hook)
(define-key vterm-mode-map (kbd "C-x C-e") #'(lambda () (interactive) (vterm-send-string (kbd "C-x C-e"))))
(define-key vterm-mode-map (kbd "C-c e") #'(lambda () (interactive) (vterm-send-string (kbd "C-x C-e"))))
(define-key vterm-mode-map (kbd "C-c '") #'(lambda () (interactive) (vterm-send-string (kbd "C-x C-e"))))
(setq-default auto-mode-alist (append '(("zsh" . sh-mode)) auto-mode-alist))
大段内容的时候 vterm-delete-region
好像太慢了, 感觉还不如 zsh 原生C-xC-e 后使用emacsclient打开快。供参考。
不过现在vterm 通过 evil-collection 与evil 集成的已经很好了,常见的d
c
x
等操作,都能使用,基本也用不上这个。
还有一个思路,来应对vterm-delete-region
直接vterm-send-C-c
(发送ctrl-c) 丢弃原内容,在一个全新的prompt 上展示修改后的内容
(defun vterm-edit-command-commit ()
(interactive)
(let ((content (buffer-string)))
(with-current-buffer vterm-edit-command--vterm-buffer
(vterm-send-C-c)
(vterm-send-string content)))
(vterm-edit-command-abort))
1 个赞
感觉你这个更直接。
这个 C-x C-e 在没有 emacsclient 的情况下,能在当前 vterm 所在的 emacs 打开么?
jixiuf
12
(define-key vterm-mode-map (kbd "C-x C-e") 'vterm-edit-command-action)
(define-key vterm-mode-map (kbd "C-c e") 'vterm-edit-command-action)
(defun vterm-edit-command-action ()
(interactive)
(let* ((delete-trailing-lines t)
(vtermbuf (current-buffer))
(begin (vterm--get-prompt-point))
(buffer (get-buffer-create "vterm-edit-command"))
(n (length (filter-buffer-substring begin (point))))
foreground
(content (filter-buffer-substring
begin (point-max))))
(with-current-buffer buffer
(setq vterm-edit-vterm-buffer vtermbuf)
(erase-buffer)
(insert content)
(delete-trailing-whitespace)
(goto-char (1+ n))
;; delete zsh auto-suggest candidates
(setq foreground (plist-get (get-text-property (point) 'font-lock-face) :foreground ))
(when (equal foreground (face-background 'vterm-color-black nil 'default))
(delete-region (point) (point-max)))
(sh-mode)
(vterm-edit-command-mode)
(evil-insert-state)
(setq-local header-line-format
(substitute-command-keys
(concat "Edit, then "
(mapconcat
'identity
(list "\\[vterm-edit-command-commit]: Finish"
"\\[vterm-edit-command-abort]: Abort"
)
", "))))
(split-window-sensibly)
(switch-to-buffer-other-window buffer)))
)
(defun vterm-edit-command-commit ()
(interactive)
(let ((delete-trailing-lines t)
content)
(delete-trailing-whitespace)
(goto-char (point-max))
(when (looking-back "\n") (backward-delete-char 1))
(setq content (buffer-string))
(with-current-buffer vterm-edit-vterm-buffer
(vterm-send-key "a" nil nil t)
(vterm-send-key "k" nil nil t t)
(unless (vterm--at-prompt-p)
(vterm-send-C-c))
(vterm-send-string content)))
(vterm-edit-command-abort))
(defun vterm-edit-command-abort ()
(interactive)
(kill-buffer-and-window))
(defvar vterm-edit-command-mode-map
(let ((keymap (make-sparse-keymap)))
(define-key keymap (kbd "C-c C-c") #'vterm-edit-command-commit)
(define-key keymap (kbd "C-c C-k") #'vterm-edit-command-abort)
keymap))
(define-minor-mode vterm-edit-command-mode
"Vterm Edit Command Mode")
依据弃用vterm-delete-region
的思路改进了一版,
这版与楼主思路不同的地方是, 把从(vterm--get-prompt-point)
到(point-max)
的所有内容 copy 到新buffer,然后在新buffer把末尾的所有空行干掉,这样相当于把所有内容copy 到新buffer,
另一个增强点是 把光标在vterm的相对位置带到了 新buffer
1 个赞