This is a doom emacs configuration of how to use emacs with language server in devcontainer.
- use lsp-bridge as the LSP client
- use devcontainer-feature-emacs-lsp-bridge to install lsp-bridge and the language server inside the container
- creation of the devcontainer is delegated to VSCode with Dev Containers Extension
Currently I’m the main contributor of lsp-bridge’s devcontainer support. Here I trace the features that I’d like to add, and what I’ve done for Emacs to work with devcontainer.
status | scope | |
---|---|---|
DONE | devcontainer-feature | able to install lsp-bridge |
DONE | devcontainer-feature | able to install any language server that lsp-bridge supports [1] |
DONE | devcontainer-feature | daily auto update latest version of lsp-bridge and release |
DONE | lsp-bridge | open container file using find-file /docker: |
DONE | lsp-bridge | enable auto-completion |
DONE | lsp-bridge | save file by lsp-bridge-remote-save-buffer |
DONE | lsp-bridge | format buffer using apheleia |
DONE | lsp-bridge | jump to definition and jump back |
DONE | doom-emacs | ripgrep search in container |
DONE | doom-emacs | open vTerm with bash in container |
DONE | devcontainer-feature | instructions of how to setup with Python projects |
TBD | devcontainer | manage container in Emacs with CLI2ELI |
TBD | devcontainer | get rid of VSCode [2] |
TBD | lsp-bridge | auto re-connect to the new container and re-open file if devcontainer has been rebuilt |
- [1] Consider it’s done, while a few language servers are missing at Nixpkgs or require special setting, also language server which runs on Windows is not supported
- [2] Currently VSCode is needed as port forward is not implemented by
devcontainer CLI
. POC at devcontainer-cli-port-forwarder
- Use
M-x tramp-cleanup-all-connections
to remove all cached connection history. - Use
M-x tramp-cleanup-all-buffers
to close all remote buffers.
To install additional tools like ripgrep
in a devcontainer, you can use the features/nix
.
Add this to your devcontainer.json,
{
"features": {
"ghcr.io/devcontainers/features/nix:1": {
"packages": "ripgrep"
}
}
}
use nix to install tools that you want to use in the container.
Ensure that ~/.nix-profile/bin
is included in the PATH
when executing remote commands.
(after! tramp
(add-to-list 'tramp-remote-path "~/.nix-profile/bin")
(add-to-list 'tramp-remote-path 'tramp-own-remote-path))
this will make search tools such as ripgrep.el
and projectile-ripgrep
works with remote files.
(when (package! lsp-bridge
:recipe (:host github
:repo "manateelazycat/lsp-bridge"
:branch "master"
:files ("*.el" "*.py" "acm" "core" "langserver" "multiserver" "resources")
:build (:not compile)))
;; doom-emacs has mardown-mode
;; (package! markdown-mode)
(package! yasnippet)
(package! topsy)
(package! flymake-bridge
:recipe (:host github :repo "liuyinz/flymake-bridge" :branch "master")))
(use-package! lsp-bridge
:config
;; for muscle memory to save buffer
(defun my/save-buffer ()
(interactive)
(if lsp-bridge-remote-file-flag
(call-interactively #'lsp-bridge-remote-save-buffer)
(call-interactively #'save-buffer)))
(map! "C-x C-s" #'my/save-buffer))
(use-package! flymake-bridge
:after lsp-bridge
:hook (lsp-bridge-mode . flymake-bridge-setup))
(map! :after flymake
"M-n" #'flymake-goto-next-error
"M-p" #'flymake-goto-prev-error)
Enable format
feature in init.el
, it will install the apheleia
package.
use SPC c f
to format the buffer.
(use-package! apheleia
:after lsp-bridge
:config
;; don't mess up with lsp-mode
(setq +format-with-lsp nil)
(setq apheleia-remote-algorithm 'remote))
Add a sticky header to indicate editing remote file
(use-package! topsy
:after lsp-bridge
:config
;; display a bar to remind editing remote file
(setcdr (assoc nil topsy-mode-functions)
(lambda ()
(when (lsp-bridge-is-remote-file) "[LBR] REMOTE FILE")))
;; do not activate when the current major mode is org-mode
(add-hook 'lsp-bridge-mode-hook (lambda ()
(unless (derived-mode-p 'org-mode)
(topsy-mode 1)))))
Enable vterm
feature in init.el
use /bin/bash
for vterm when editing container file, use SPC o t
to open vTerm buffer
(after! vterm
(defun my/set-vterm-shell ()
(when (string-prefix-p "/docker:" (file-remote-p default-directory))
(when (eq major-mode 'vterm-mode)
(let ((shell (if (string-prefix-p "/docker:" (file-remote-p default-directory))
"/bin/bash"
(or (getenv "SHELL") "/bin/bash"))))
(vterm-send-string (format "exec %s\n" shell))
(vterm-send-string "clear\n")))))
(add-hook 'vterm-mode-hook #'my/set-vterm-shell))
It should be clearly stated to avoid any confusing that lsp-bridge itself runs in a python process, and basedpyright-langserver uses another python process.
Normally you created a virtual environment using tools like uv or pdm at the root directory, suppose the name of the virtual environment is “.venv”.
You need to add a section to the pyproject.toml, and basedpyright will inspect the virtual environment correctly, more details.
[tool.basedpyright]
venvPath = "."
venv = ".venv"