Vterm In Emacs

· 581 words · 3 minute read

Overview

Using Emacs in daily life, I rely on vterm terminal emulator instead of eshell. However, I've noticed that there are certain limitations in terms of integration between vterm and Emacs. While the package supports some user-accessible functions, they are not sufficient. I've always wanted the integration level as VSCode, and at least it should be able to open files from the terminal interface. Well, this is one of essential features of the terminal emulator running on editors, so I thought that having this kind of issue was ridiculous. So I tried to find solutions by googling about it, but none of them had a one-shot method to achieve this. So, I made up my mind to write functions by myself.

In this article, I am going to describe the following things:

  1. A callback function to open files from vterm
  2. Functions to manage vterm session

Note that since I am using Doomemacs right now, the keymap setting could differ from yours. If you do not want to read any details about functions that I wrote, just use the following settings.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
(require 'filenotify)

(defvar my:get-vterm--backup nil)
(defun my:vterm-new ()
  (interactive)
  (if (not (string-match-p "vterm" (buffer-name (current-buffer))))
      (setq my:get-vterm--backup (current-buffer)))
  (call-interactively #'+vterm/here))

(defun my:vterm-toggle ()
  (interactive)
  (let ((cnt (cl-remove-if #'null
                           (mapcar (lambda (x)
                                     (and (string-match-p "vterm" (buffer-name x)) (buffer-name x)))
                                   (buffer-list)))))
    (if (null cnt)
        (progn
          (setq my:get-vterm--backup (current-buffer))
          (call-interactively #'+vterm/here))
      (if (and (string-match-p "vterm" (buffer-name (current-buffer)))
              my:get-vterm--backup)
          (my:vterm--restore)
        (call-interactively #'my:vterm--select)))))

(defun my:vterm--restore ()
  (switch-to-buffer my:get-vterm--backup)
  (setq my:get-vterm--backup nil))

(defun my:vterm--select (choice)
"Argument CHOICE user's selection."
  (interactive
   (list (completing-read "Choose: "
                          (cl-remove-if #'null
                                        (mapcar (lambda (x) (and (string-match-p "vterm" (buffer-name x)) (buffer-name x)))
                                                (buffer-list)))
                          nil t)))
  (car (split-string choice " "))
  (setq my:get-vterm--backup (current-buffer))
  (switch-to-buffer choice))

(after! vterm
  ;; Following must be used with bash alias:
  ;; => alias eo='echo $1 > ~/.config/emacs/.local/cache/vterm-pipe'
  (let* ((pipe-file (expand-file-name "vterm-pipe" user-emacs-directory))
         (func-open-file (lambda (event)
                           (find-file
                            (with-temp-buffer
                              (insert-file-contents (expand-file-name "vterm-pipe" user-emacs-directory))
                              (string-trim (buffer-string)))))))
    (file-notify-add-watch pipe-file '(change) func-open-file))

  (add-hook 'vterm-mode-hook (lambda ()
                               (evil-emacs-state))))

File open from vterm - filenotify

Since Emacs-28.1, Emacs supports the filenotify package, which makes it possible to watch any change from the file. It means that whenever I write any to the file, Emacs can get the triggered event from the write. Let's register a callback function for the vterm-pipe in user-emacs-directory.

1
(file-notify-add-watch pipe-file '(change) callback-func)

Add the following code to $HOME/.bashrc to use the alias eo command. Now, using the eo alias will trigger the event and invoke the callback function. It's done.

1
alias eo='realpath $1 > ~/.config/emacs/.local/cache/vterm-pipe'

Vterm session management

Unfortunately, vterm does not support any functions to manage its session. And a function to toggle it is not perfect. Let's improve it by using an interactive menu. You can toggle the vterm session with my:vterm-toggle. In the code, there are many to refactor but it is sufficient to resolve the lack of session management and inefficient UI toggle.

Wrap up

Since I started to learn how to write code in elisp, I have been able to use Emacs efficiently. Beyond the simple editor, now I can see why Emacs has been loved by lots of developers. I know, this should be the same for VI/M users :P.