Skip to main content

Language Server Protocol implementation for xonsh

Project description

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

Related Projects

  • 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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

xonsh_lsp-0.2.1.tar.gz (87.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

xonsh_lsp-0.2.1-py3-none-any.whl (68.7 kB view details)

Uploaded Python 3

File details

Details for the file xonsh_lsp-0.2.1.tar.gz.

File metadata

  • Download URL: xonsh_lsp-0.2.1.tar.gz
  • Upload date:
  • Size: 87.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for xonsh_lsp-0.2.1.tar.gz
Algorithm Hash digest
SHA256 4cbf0d7c4ffc2be2850bb8ee9d63f7fbd1d6c0d3e2b5052b97e870bd1ad360ed
MD5 df32083137ce585b8c694a63bf6253d8
BLAKE2b-256 e712f6cb642e039720dcd11a3b5ef8bccdea4b07a94d80677977758b9e307f02

See more details on using hashes here.

Provenance

The following attestation bundles were made for xonsh_lsp-0.2.1.tar.gz:

Publisher: release.yml on FoamScience/xonsh-language-server

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file xonsh_lsp-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: xonsh_lsp-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 68.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for xonsh_lsp-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 d60089afb49dea508c1952a37d9a581027a9527dd15f7e5d6ff5aa216ff621fe
MD5 167997a509db7ee17bd4988e58980c09
BLAKE2b-256 7ba2e06c450438f3708dc407c13c59550e791af73656035a4875e7554d3a1359

See more details on using hashes here.

Provenance

The following attestation bundles were made for xonsh_lsp-0.2.1-py3-none-any.whl:

Publisher: release.yml on FoamScience/xonsh-language-server

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page