Use lsp-bridge, a Fast LSP Client that Leverages Python's Processing Capabilities

I started using fast LSP client lsp-bridge. In this post, I introduce some additional configuration needed.

Why LSP-Bridge?

When it comes to LSP clients in Emacs, we have several options. The most popular ones are:

  1. lsp-mode: The full-featured behemoth
  2. eglot: The minimal, built-in solution in Emacs 29
  3. lsp-bridge: The new kid on the block, promising speed and efficiency

I had been used lsp-mode in the past, watching my CPU fan spin like a helicopter trying to take off. So I decided to give lsp-bridge a try in this time because it it’s “blazingly fast.”

Prerequisites and Initial Setup

The installation step is documented in the README.md of lsp-bridge:

  1. Install yasnippet
  2. Install some Python packages
  3. Configure the client

OK, let’s do it:

(leaf yasnippet
  :doc "Template system"
  :url "https://github.com/joaotavora/yasnippet"
  :ensure t
  :hook   ((after-init-hook . yas-reload-all)
           (prog-mode-hook  . yas-minor-mode))
  :custom (yas-snippet-dirs . `(,(expand-file-name "snippets" user-emacs-directory))))

Then install the required Python packages:

pip3 install epc orjson sexpdata six setuptools paramiko rapidfuzz watchdog packaging

The Path to Better Programming Experience

Everything seemed fine until I tried to actually use it; Emacs kept complaining about missing epc module, despite having just installed it.

After some investigation, I found that the value of exec-path and $PATH of OS shell was different. The problem wasn’t with the installation but with Emacs not knowing where to look. On macOS, the shell environment and Emacs environment are about as synchronized as a first-time dance class.

The solution was to use exec-path-from-shell:

(leaf exec-path-from-shell
  :ensure t
  :if (and (equal system-type 'darwin) (window-system))
  :custom
  (exec-path-from-shell-check-startup-files . nil)
  (exec-path-from-shell-variables . '("PATH" "GOPATH" "LANG"))
  :init
  (setq exec-path  (parse-colon-path (string-trim-right 
    (shell-command-to-string "echo $PATH"))))
  (setenv "PATH"   (string-trim-right 
    (shell-command-to-string "echo $PATH")))
  (setenv "GOPATH" (string-trim-right 
    (shell-command-to-string "echo $GOPATH")))
  :config
  (exec-path-from-shell-initialize))

Configure the Client

With the path issues resolved, here’s the working LSP configuration:

(leaf lsp-bridge
  :doc "fast LSP client"
  :vc (:url "https://github.com/manateelazycat/lsp-bridge")
  :require t
  :init (global-lsp-bridge-mode)
  :custom
  (acm-enable-tabnine                 . nil)
  (acm-enable-copilot                 . t)
  (acm-enable-quick-access            . t)
  (lsp-bridge-enable-hover-diagnostic . t)
  (acm-backend-yas-candidates-number  . 5))

How It Works

Let’s configure major mode for Python and see how lsp-bridge works.

(leaf python
  :doc "Python development environment"
  :url "https://wiki.python.org/moin/EmacsPythonMode"
  :mode ("\\.py\\'" . python-mode))

As a general practice of using LSP, we have to install a langauge server dedicated to the language used. By evaluating lsp-bridge-python-lsp-server, I found that lsp-bridge expects basedbyright as a language server of Python.

Thus I install it as follows:

brew install basedpyright

Finally, I managed to use LSP for Python codes 🎉

The state of init.el at the conclusion of this article can be found here 🚀

References


comments powered by Disqus