Skip to main content

xonsh-lsp

A Language Server Protocol (LSP) implementation for xonsh, the Python-powered shell.

Features

  • Syntax Highlighting (via tree-sitter-xonsh integration)
    • And optional symantic tokens (contributed by @nahoj, #6)
  • Code Completion
    • Environment variables ($VAR, ${expr})
    • Subprocess commands from PATH
    • Shell builtins (cd, echo, jobs, etc.)
    • Xonsh builtins and aliases (source, xontrib, aliases, etc.)
    • Python completions (via Jedi or external backend)
    • Path completions with directory traversal
    • Glob pattern completions (`...`)
    • At-object completions (@.env, @.imp)
    • Path literal completions (p"...", pf"...")
  • Diagnostics
    • Syntax errors (via tree-sitter)
    • Undefined environment variables (with quick fix)
    • Unknown commands (hint severity)
    • Empty subprocess warnings
    • Python errors (via Jedi or external backend)
  • Hover Information
    • Environment variable values
    • Xonsh operator documentation ($(), !(), $[], ![], @(), @$())
    • Xonsh builtin documentation
    • Command paths and --help preview
    • Python symbol information with signatures
    • Path literal documentation
  • Go to Definition
    • Python definitions (via Jedi or external backend)
    • Environment variable assignments
    • Alias definitions
    • Function definitions
  • Find References
    • Python references (via Jedi or external backend)
    • Environment variable references
    • Symbol references
  • Signature Help
    • Python function signatures (via Jedi or external backend)
    • Parameter tracking
  • Document Symbols
    • Functions, classes, variables, modules
  • Code Actions
    • Quick fix for undefined environment variables
  • Inlay Hints
    • Type annotations after xonsh expressions ($(), !(), p"…", $VAR, …)
    • Env-var values after $VAR (opt-in via inlayHints.envVarValues)
    • Python inlay hints forwarded from the backend (Pyright/ty)
  • Typed __xonsh_env__ stub for the Python backend
    • Generated TypedDict from xonsh's env-var registry — $XONSH_DEBUG types as int, $AUTO_CD as bool, etc. (Pyright/ty backends only)
    • User-defined env vars ($X = …) added per-document as Any
  • Multi-Backend Support
    • Built-in Jedi backend (default, no external dependencies)
    • LSP proxy to Pyright, basedpyright, pylsp, ty, or any LSP server
    • Transparent settings passthrough (editor → child backend)
    • Fallback backendSettings via initializationOptions

Installation

pip install xonsh-lsp
# or just run
uvx xonsh-lsp

To install with the built-in Jedi backend (recommended for standalone use):

pip install "xonsh-lsp[jedi]"

Jedi is optional — if you configure an external backend (Pyright, ty, etc.), you don't need Jedi installed.

Usage

Command Line

# Start with stdio (default, for editor integration)
xonsh-lsp

# Start with TCP
xonsh-lsp --tcp --host 127.0.0.1 --port 2087

# Debug mode
xonsh-lsp --log-level DEBUG

# Use Pyright as the Python backend
xonsh-lsp --python-backend pyright

# Use ty as the Python backend
xonsh-lsp --python-backend ty

# Use a custom LSP server command
xonsh-lsp --python-backend lsp-proxy --backend-command my-lsp-server --stdio

# Enable LSP semantic-tokens syntax highlighting (off by default)
xonsh-lsp --semantic-tokens

Neovim Integration

xonsh-lsp is not yet in the canonical nvim-lspconfig registry. For now, configure it manually using Neovim's native LSP API (0.11+) or the nvim-lspconfig custom-server pattern.

1. Register the xonsh filetype

Place this somewhere early in your config (e.g. an autocmds.lua file):

vim.filetype.add({
  extension = { xsh = 'xonsh', xonshrc = 'xonsh' },
  filename  = { ['.xonshrc'] = 'xonsh', ['xonshrc'] = 'xonsh' },
})

2. Configure the language server

Neovim 0.11+ (native vim.lsp)

vim.lsp.config('xonsh_lsp', {
  cmd = { 'xonsh-lsp' },             -- or { 'uvx', '-n', 'xonsh-lsp' }
  filetypes = { 'xonsh' },
  root_markers = { '.xonshrc', 'xonshrc', '.git' },
})
vim.lsp.enable('xonsh_lsp')

nvim-lspconfig (manual server registration)

local lspconfig = require('lspconfig')
local configs = require('lspconfig.configs')

if not configs.xonsh_lsp then
  configs.xonsh_lsp = {
    default_config = {
      cmd = { 'xonsh-lsp' },
      filetypes = { 'xonsh' },
      root_dir = function(fname)
        return lspconfig.util.find_git_ancestor(fname) or vim.fn.getcwd()
      end,
      settings = {},
    },
  }
end

lspconfig.xonsh_lsp.setup({})

3. Choose a Python backend

xonsh-lsp delegates Python analysis to an external backend. Set it via init_options:

ty (recommended — extremely fast, written in Rust)

vim.lsp.config('xonsh_lsp', {
  cmd = { 'uvx', '-n', 'xonsh-lsp' },
  filetypes = { 'xonsh' },
  root_markers = { '.xonshrc', 'xonshrc', '.git' },
  init_options = {
    pythonBackend = 'ty',
    -- Optional: pin the ty binary (e.g. from Mason)
    -- pythonBackendCommand = { '/path/to/ty', 'server' },
  },
  settings = {
    environment = {
      python = vim.fn.exepath('python3'),
    },
  },
})
vim.lsp.enable('xonsh_lsp')

Pyright

vim.lsp.config('xonsh_lsp', {
  cmd = { 'uvx', '-n', 'xonsh-lsp' },
  filetypes = { 'xonsh' },
  root_markers = { '.xonshrc', 'xonshrc', '.git' },
  init_options = {
    pythonBackend = 'pyright',
  },
  settings = {
    python = {
      pythonPath = vim.fn.exepath('python3'),
      analysis = {
        autoSearchPaths = true,
        useLibraryCodeForTypes = true,
      },
    },
  },
})
vim.lsp.enable('xonsh_lsp')

Your Python backend settings (e.g. settings.python.*, settings.environment.*) are forwarded transparently to the child LSP — xonsh-lsp does not detect or override paths itself.

4. (Optional) Tree-sitter highlighting

For syntax highlighting, install the tree-sitter-xonsh parser:

-- Register the parser so :TSInstall xonsh works
vim.api.nvim_create_autocmd('User', {
  pattern = 'TSUpdate',
  callback = function()
    require('nvim-treesitter.parsers').xonsh = {
      install_info = {
        url = 'https://github.com/FoamScience/tree-sitter-xonsh',
        queries = 'queries/',
      },
    }
  end,
})

-- Start treesitter highlighting for xonsh buffers
vim.api.nvim_create_autocmd('FileType', {
  pattern = 'xonsh',
  callback = function(args)
    if not require('nvim-treesitter.parsers').xonsh then
      vim.treesitter.start(args.buf, 'xonsh')
    end
  end,
})

5. (Optional) UV project Python detection

If you use uv for project management, you can auto-detect the correct Python interpreter and notify the backend via workspace/didChangeConfiguration in your on_attach:

vim.lsp.config('xonsh_lsp', {
  cmd = { 'uvx', '-n', 'xonsh-lsp' },
  filetypes = { 'xonsh' },
  root_markers = { '.xonshrc', 'xonshrc', '.git' },
  init_options = { pythonBackend = 'ty' },
  settings = {
    environment = { python = vim.fn.exepath('python3') },
  },
  on_attach = function(client, bufnr)
    local filepath = vim.api.nvim_buf_get_name(bufnr)
    local dir = vim.fn.fnamemodify(filepath, ':h')
    vim.system(
      { 'uv', 'python', 'find' },
      { text = true, timeout = 1000, cwd = dir },
      vim.schedule_wrap(function(result)
        if not result or not result.stdout then return end
        local py = result.stdout:match('^%s*(.-)%s*$')
        if py == '' or vim.fn.executable(py) ~= 1 then return end
        client.settings = vim.tbl_deep_extend('force', client.settings or {}, {
          environment = { python = py },
        })
        client:notify('workspace/didChangeConfiguration', {
          settings = client.settings,
        })
      end)
    )
  end,
})
vim.lsp.enable('xonsh_lsp')

Configuration

Python Backend

xonsh-lsp supports multiple Python analysis backends. The backend handles completions, hover, go-to-definition, references, signature help, and diagnostics for Python code within xonsh files.

Backend Command Description
jedi (default) built-in Uses Jedi for static analysis. Works out of the box, no external process.
pyright pyright-langserver --stdio Microsoft's Pyright type checker. Respects pyrightconfig.json.
basedpyright basedpyright-langserver --stdio Community fork basedpyright with additional features.
pylsp pylsp The Python LSP Server with plugin ecosystem.
ty ty server Astral's ty type checker — extremely fast, written in Rust.
lsp-proxy custom Any LSP server via --backend-command.

The backend can be set via CLI args or editor initializationOptions:

CLI:

xonsh-lsp --python-backend pyright

Editor initializationOptions:

{
  "pythonBackend": "pyright"
}

Initialization Options

The full set of initializationOptions accepted by xonsh-lsp:

{
  // Python analysis backend: "jedi", "pyright", "basedpyright", "pylsp", "ty", or "lsp-proxy"
  "pythonBackend": "jedi",

  // Custom command for the "lsp-proxy" backend (not needed for named backends)
  "pythonBackendCommand": ["my-lsp-server", "--stdio"],

  // Fallback settings sent to the backend during initialization.
  // Normally you should configure your backend via the editor's settings
  // (e.g. Neovim's `settings = { ... }`), which are forwarded transparently.
  // Use this only if your editor doesn't support workspace/configuration.
  "backendSettings": { },

  // Enable LSP semantic tokens for syntax highlighting (default: false).
  // Off by default because most editors already highlight xonsh via
  // tree-sitter or a TextMate grammar, and overlapping semantic tokens can
  // fight with those. Turn on if your editor relies on LSP for highlighting.
  "semanticTokens": false,

  // Inlay-hint preferences. The xonsh server emits native hints for
  // xonsh-specific syntax in addition to whatever the Python backend
  // returns for Python regions.
  "inlayHints": {
    // ": str" / ": CommandPipeline" / ": Path" / ": bool" (from the
    // xonsh env var registry) after $VAR, $(), !(), p"…", etc.
    "xonshTypes": true,
    // " = /home/user" after $HOME (resolved from the server's process env).
    "envVarValues": false,
    // Alias kind after unknown commands (reserved for v2).
    "aliasResolution": false
  }
}

Settings Passthrough

When the child backend requests its configuration (via workspace/configuration), xonsh-lsp forwards the request to your editor and returns the editor's response. This means your existing Python backend settings — pythonPath, python.analysis.*, etc. — work exactly as if the backend were running standalone.

If the editor doesn't respond (or workspace/configuration is not supported), xonsh-lsp falls back to backendSettings from initializationOptions.

How the LSP Proxy Works

When configured with an external backend, xonsh-lsp acts as LSP middleware:

  1. Receives requests from the editor (completions, hover, etc.)
  2. Preprocesses xonsh syntax to valid Python (e.g., $HOME → __xonsh_env__["HOME"])
  3. Forwards the preprocessed Python to the child LSP server
  4. Maps positions in the response back to the original xonsh source
  5. Merges results with xonsh-specific features (env vars, operators, commands)

Xonsh-specific features (environment variable completions, operator hover, subprocess diagnostics, etc.) are always handled natively — only Python analysis is delegated to the backend.

Diagnostics use a two-phase merge:

  • Xonsh diagnostics (syntax errors, undefined env vars, unknown commands) are published immediately
  • Python diagnostics from the backend arrive asynchronously and are merged with the cached xonsh diagnostics

Architecture

flowchart RL
    subgraph xonsh-lsp
        parser[tree-sitter-xonsh parser]

        parser --> python[Python Regions]
        parser --> xonsh[Xonsh Builtins]
        parser --> subprocess[Subprocess Commands]

        python --> backend{PythonBackend}
        backend -->|default| jedi[JediBackend]
        backend -->|proxy| proxy[LspProxyBackend]
        proxy --> child[Child LSP: Pyright / ty / basedpyright / pylsp]
        xonsh --> native1[Native Handler]
        subprocess --> native2[Native Handler]
    end

Contributing

Contributions are welcome! Please feel free to submit issues and pull requests; especially flagging unsupported xonsh syntax since this LSP is young.

License

MIT

  • tree-sitter-xonsh - Tree-sitter grammar for xonsh
  • xonsh - The xonsh shell
  • pygls - Python LSP library
  • jedi - Python static analysis
  • ty - Extremely fast Python type checker and LSP
  • pyright - Static type checker for Python

Metadata

Release files for xonsh-lsp 0.2.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for xonsh-lsp 0.2.2
File Size Uploaded
xonsh_lsp-0.2.2.tar.gz 93.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for xonsh-lsp 0.2.2
File Interpreter ABI Platform
xonsh_lsp-0.2.2-py3-none-any.whl Python 3 none any Details

Total release size: 165.3 kB

Release files / xonsh_lsp-0.2.2.tar.gz

Download URL xonsh_lsp-0.2.2.tar.gz
Size 93.1 kB
Tags Source
SHA-256 checksum
How to use checksums
2506e3561721112c093c7c40e7e415d120bb0803ffe00bf09212c5d8bb1dd087
BLAKE2b-256 checksum
How to use checksums
308e7cf4603e502fce4391470fc80d09086d01c5233b1d50656de70d727d6132
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 22, 2026.

Transparency log

Release files / xonsh_lsp-0.2.2-py3-none-any.whl

Download URL xonsh_lsp-0.2.2-py3-none-any.whl
Size 72.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
98c9391f5301cbee44818176f9dba52738ed7a9c219c013533599109ba856df5
BLAKE2b-256 checksum
How to use checksums
837f568d7887327584092841b52dd652ec1030fa2fa909a4ac0e01dd4cd32296
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 22, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.2 This release

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.7

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page