Implement Custom Tramp Method in Emacs
Published on 27 September, 2023
Emacs supports editing remote files using the Tramp package. Emacs transparently handles the remote files based on the special path prefixes specified, so the experience is similar to operating on a local file. Tramp uses a remote shell underneath to access remote files. In this article, I'll talk about how to extend Tramp to support custom Methods to access remote files.
I work on a Cloud platform that manage and operate Robots remotely. To test the platform, we have an internal service that implements an API to create virtual Robots on-demand. These virtual robots are accessible over SSH Port on internal VPN.
It is often required to edit configuration files on the virtual Robots or check debug logs. This is a multi-step process and requires more than one tool:
- Query IP of the Robot from the API.
- SSH on the Robot.
Requires: Terminal and
- Operate on the Files.
Requires: text-editor on the Robot, usually
Like most Emacs users, I want to achieve this without leaving Emacs. I started by writting a simple shell script that wraps the REST API and exposes a simple command-line interface to work with. This allows me to use it from Terminal directly as well. I'll be using this command-line interface later on to integrate it with the Tramp package.
hwil list hwil create <DEVICE_NAME> hwil delete <DEVICE_NAME> hwil get <DEVICE_NAME> hwil ssh <DEVICE_NAME>
Tramp over SSH
Let's look at a common use-case of Tramp, edit files on a SSH Host. To open the remote file, it must be prefixed with the special value
/ssh:user@host:/path. Tramp determines appropriate method to use from the filepath. In this case, it is the built-in SSH Method.
Tramp uses the SSH Method to login to the remote host, prompting for password if required. It then uses the remote shell to operate on the files on the host. Tramp can work with popular shells (
zsh, etc) out of the box. In case of a non-UNIX shell (eg
fish), Tramp invokes a UNIX-compatible sub-shell and uses that instead.
Tramp defines the
tramp-methods list that is used to look-up Method definitions. The SSH Method is defined and added to the list in the
tramp-sh.el file. The definition is a list of cons defining parameters that Tramp uses to start the remote connection. The full list of parameters are defined in the documentation of the
(add-to-list 'tramp-methods `("ssh" (tramp-login-program "ssh") (tramp-login-args (("-l" "%u") ("-p" "%p") ("%c") ("-e" "none") ("%h"))) (tramp-async-args (("-q"))) (tramp-direct-async t) (tramp-remote-shell ,tramp-default-remote-shell) (tramp-remote-shell-login ("-l")) (tramp-remote-shell-args ("-c"))))
Custom Tramp Method
We know now that SSH Method is just an entry in the
tramp-methods alist. To implement the custom method, it must be added to this list as well. We will use the
hwil command-line interface in the method.
(add-to-list 'tramp-methods `("hwil" (tramp-login-program "hwil") (tramp-login-args (("ssh") ("%h"))) (tramp-remote-shell "/bin/bash") (tramp-remote-shell-args ("-i" "-c"))))
After adding the
hwil method to the list, Tramp can operate on
Bonus: Host Completion
The Tramp package also supports completions for the special paths. See the SSH Method's completion list for instance.
To handle the completions, Tramp defines the
tramp-set-completion-function function. It binds the Method's name with a list of completion functions. The completion function takes a single parameter and returns a list of completions. I will use the
hwil command-line interface again to implement the completion function.
;; The list command returns multiple columns. This fetches the Name ;; column from the output lines. (defun hwil--parse-devices (line) (list nil (caddr (split-string line "[[:space:]]+" t)))) (defun hwil--list-devices () (mapcar #'hwil--parse-devices (process-lines "hwil" "list"))) (defun hwil--tramp-completion (&optional ignored) (hwil--list-devices))
hwil--tramp-completion is the final completion function. It invokes the
hwil list command and parses the Robot's name from the output. We ignore the parameter because we don't need it. Next, this function must be set for the custom Tramp Method defined earlier.
(tramp-set-completion-function "hwil" '((hwil--tramp-completion "")))
Finally, I have the custom Tramp method to interact with the internal API and operate on the files of virtual Robots.