Trying Ty for my LSP in Emacs

3 min read

Astral announced Ty Beta today, the new Rust-based Python type checker. I’ve been using Ruff and uv for a while now, and they’ve consistently delivered on the “rewrite Python tooling in Rust to make it absurdly fast” promise. So when they claimed Ty is 10-80x faster than Pyright for incremental checking, I had to try it.

I’ve been using basedpyright with Eglot and it works, but I’d be lying if I said the lag didn’t bother me. When you’re editing and the type checker is half a second behind, you stop trusting the feedback. The squiggly lines show up after you’ve already moved on to the next thought.

The Dual Setup

Since Ty just hit Beta, I wasn’t ready to completely abandon basedpyright. Instead, I set up a toggle so I can switch between them. Here’s my configuration:

  ;; Python LSP Server Selection
  (defvar my/python-lsp-server 'ty
    "Which Python language server to use: 'basedpyright or 'ty")

  (defun my/python-lsp-command ()
    "Return the LSP command based on selected server."
    (pcase my/python-lsp-server
      ('ty '("ty" "server"))
      ('basedpyright '("basedpyright-langserver" "--stdio"
                       :initializationOptions (:basedpyright (:plugins (
                         :ruff (:enabled t
                               :lineLength 88
                               :exclude ["E501"]
                               :select ["E" "F" "I" "UP"])
                         :pycodestyle (:enabled nil)
                         :pyflakes (:enabled nil)
                         :pylint (:enabled nil)
                         :rope_completion (:enabled t)
                         :autopep8 (:enabled nil))))))))

  (defun my/switch-python-lsp ()
    "Toggle between Ty and basedpyright, restart Eglot."
    (interactive)
    (setq my/python-lsp-server
          (if (eq my/python-lsp-server 'ty) 'basedpyright 'ty))
    (when (eglot-managed-p)
      (ignore-errors (eglot-shutdown (eglot-current-server)))
      (sleep-for 0.5)
      (eglot-ensure))
    (message "Switched to %s" my/python-lsp-server))

  The Eglot configuration uses a lambda to dynamically evaluate which server to start:

  (use-package eglot
    :ensure t
    :defer t
    :commands (eglot eglot-ensure)
    :config
    (setq eglot-server-programs
          '((python-ts-mode . (lambda (&rest _) (my/python-lsp-command)))
            ((js-ts-mode typescript-ts-mode tsx-ts-mode) .
             ("typescript-language-server" "--stdio"))))
    :hook ((python-ts-mode . eglot-ensure)))

That lambda matters. Without it, Eglot evaluates my/python-lsp-command once when your config loads and hardcodes the result. The lambda forces fresh evaluation each time Eglot starts a server, which is what makes toggling work.

The speed difference is very noticeable. Fix a type error and the diagnostic disappears immediately instead of lingering for that half-second that makes you wonder if it registered. It’s the kind of improvement that changes whether you pay attention to type feedback or ignore it. Completions and jumping to definitions are instant now.

I hit one issue. Ty doesn’t handle the LSP shutdown request cleanly, throwing a JSON parsing error. Wrapping the shutdown in ignore-errors fixed it, but it’s the kind of rough edge you expect from a December 2024 Beta release.

Installation

Install Ty globally

uv tool install ty@latest

Keep basedpyright around

pip install basedpyright ruff

Then M-x my/switch-python-lsp toggles between them.

Is It Worth It?

If you’re already using Ruff and uv, yes. The speed improvement is real. The dual setup gives you a fallback when if I hit issues, which I expect to happen occasionally given the Beta status.

My emacs config is on Github for reference.

Comments from Mastodon

4 comments • Join the discussion

Steve Purcell's avatar

@greg been using Ty for lsp for a few weeks and am pretty happy with it, coming from basedpyright. Btw, if you have both LSPs in your PATH and configured with eglot-alternatives, eglot will prompt you to choose at startup, so you can just eglot-restart and then up/down and return.

Greg Newman's avatar
Greg Newman @greg
View on Mastodon

@sanityinc no issues so far? I’m pleased with how fast it is.

Yes, eglot prompts me to choose between quite a few because I’m horrible about cleanup. Once I’m confident with Ty I’ll remove all the others and the toggle. But for now the toggle is quicker to switch between my two main candidates.

1
Steve Purcell's avatar

@greg I think I noticed (some of?) the quick fix code actions don't work, but I don't rely on those particularly. Main thing I want is good type checking, completion, and jump to definition, and for those it's been great.