https://github.com/nohzafk/emacs-devcontainer
Doom Emacs config works with LSP in Devcontainer
https://github.com/nohzafk/emacs-devcontainer
devcontainer-feature doom-emacs-configuration emacs lsp-client
Last synced: 8 months ago
JSON representation
Doom Emacs config works with LSP in Devcontainer
- Host: GitHub
- URL: https://github.com/nohzafk/emacs-devcontainer
- Owner: nohzafk
- Created: 2024-08-04T18:08:29.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2024-10-19T20:33:02.000Z (over 1 year ago)
- Last Synced: 2025-04-07T19:39:02.695Z (10 months ago)
- Topics: devcontainer-feature, doom-emacs-configuration, emacs, lsp-client
- Language: Emacs Lisp
- Homepage:
- Size: 21.5 KB
- Stars: 9
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.org
Awesome Lists containing this project
README
* Emacs with LSP in Devcontainer
This is a [[https://github.com/doomemacs/doomemacs][doom emacs]] configuration of how to use emacs with language server in devcontainer.
- use [[https://github.com/manateelazycat/lsp-bridge][lsp-bridge]] as the LSP client
- use [[https://github.com/nohzafk/devcontainer-feature-emacs-lsp-bridge][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 [[https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers][Dev Containers Extension]]
** status of devcontainer support
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 [[https://github.com/nohzafk/cli2eli][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 [[https://github.com/nohzafk/devcontainer-cli-port-forwarder][devcontainer-cli-port-forwarder]]
* Remote Editing Configuration
** TRAMP
- 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.
** install additional tools
To install additional tools like =ripgrep= in a devcontainer, you can use the =features/nix=.
Add this to your [[https://code.visualstudio.com/docs/devcontainers/create-dev-container][devcontainer.json]],
#+begin_src json :tangle no
{
"features": {
"ghcr.io/devcontainers/features/nix:1": {
"packages": "ripgrep"
}
}
}
#+end_src
use nix to install tools that you want to use in the container.
** tramp-remote-path
Ensure that =~/.nix-profile/bin= is included in the =PATH= when executing remote commands.
#+begin_src elisp :tangle config.el
(after! tramp
(add-to-list 'tramp-remote-path "~/.nix-profile/bin")
(add-to-list 'tramp-remote-path 'tramp-own-remote-path))
#+end_src
this will make search tools such as =ripgrep.el= and =projectile-ripgrep= works with remote files.
** lsp-bridge
#+begin_src elisp :tangle packages.el
(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")))
#+end_src
#+begin_src elisp :tangle config.el
(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)
#+end_src
** formatter support
Enable =format= feature in =init.el=, it will install the =apheleia= package.
use =SPC c f= to format the buffer.
#+begin_src elisp :tangle config.el
(use-package! apheleia
:after lsp-bridge
:config
;; don't mess up with lsp-mode
(setq +format-with-lsp nil)
(setq apheleia-remote-algorithm 'remote))
#+end_src
** remote file indicator
Add a sticky header to indicate editing remote file
#+begin_src elisp :tangle config.el
(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)))))
#+end_src
** vTerm
Enable =vterm= feature in =init.el=
use =/bin/bash= for vterm when editing container file, use =SPC o t= to open vTerm buffer
#+begin_src elisp :tangle config.el
(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))
#+end_src
** how to setup language server for python project (using basedpyright)
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, [[https://docs.basedpyright.com/latest/configuration/config-files/][more details]].
#+begin_src toml
[tool.basedpyright]
venvPath = "."
venv = ".venv"
#+end_src