Skip to main content

pylsp-workspace-symbols

CI PyPI PyPI - Python Version Downloads License: MIT

A python-lsp-server plugin that adds workspace symbol search, inlay hints, code lens, semantic tokens, call/type hierarchy, document links, document colors and on-type formatting via Jedi.

Why? pylsp does not implement several LSP features natively. This plugin fills those gaps — enabling "Go to Symbol in Workspace", rich type inference hints, code lens overlays, semantic token highlighting, call/type hierarchy navigation, clickable import links, inline color previews, and intelligent on-type formatting in any LSP client — including CudaText, Neovim, Emacs, and others — with broad client compatibility out of the box.


✨ Features

  • 🔍 Workspace-wide symbol search — find functions, classes, and modules across all files in the project
  • 💡 Inlay hints — inline type annotations inferred by Jedi for assignments, return types, raised exceptions, and parameter names at call sites
  • 🔭 Code lens — per-definition overlays showing reference counts (👥 N references), subclass/override counts (🔗 N implementations), run entry points (▶ Run), and test markers (🧪 Run test)
  • 🎨 Semantic tokens — Jedi-backed token classification for editors that support textDocument/semanticTokens (opt-in, disabled by default)
  • 🌳 Call hierarchy — navigate callers and callees of any function via callHierarchy/incomingCalls and callHierarchy/outgoingCalls
  • 🧬 Type hierarchy — explore supertypes and subtypes of any class via typeHierarchy/supertypes and typeHierarchy/subtypes
  • 🔗 Document links — clickable links for URLs in comments/strings and import statements (resolves to stdlib source when available)
  • 🎨 Document colors — inline color previews for CSS/hex/RGB/HSL color literals in source files
  • ✍️ On-type formatting — smart Python formatting as you type: auto-indent/dedent on Enter, space after : in dict literals and annotations, clause dedent for else/elif/except/finally, f-string promotion on {, PEP 8 space after #, Google-style docstring template on """, and multiline closer alignment for ) ] }
  • 🔌 Broad client compatibility — capabilities announced via proper LSP channel (works with Neovim, eglot, and any client that does not support experimental capabilities), with automatic fallback to the experimental channel
  • ⚡ Fast — results in ~130ms after the first call (Jedi cache warm)
  • 🔤 Case-insensitive substring match — area finds calculate_area, Cal finds Calculator
  • 📁 Smart folder exclusion — automatically skips .git, __pycache__, node_modules, .venv, dist, build, and more
  • ⚙️ Configurable — tune all options via pylsp settings
  • 🐍 Python 3.8+ — compatible with all modern Python versions

📦 Installation

pip install pylsp-workspace-symbols

The plugin is discovered automatically by pylsp via its entry point — no manual configuration needed.

⚙️ Configuration

Add to your LSP client's pylsp settings (e.g. in settings.json or equivalent):

{
  "pylsp": {
    "plugins": {
      "jedi_workspace_symbols": {
        "enabled": true,
        "max_symbols": 500,
        "ignore_folders": []
      },
      "inlay_hints": {
        "enabled": true,
        "show_assign_types": true,
        "show_return_types": true,
        "show_raises": true,
        "show_parameter_hints": true,
        "max_hints_per_file": 200
      },
      "code_lens": {
        "enabled": true,
        "show_references": true,
        "show_implementations": true,
        "cross_file_implementations": false,
        "show_run": true,
        "show_tests": true,
        "max_definitions": 150
      },
      "semantic_tokens": {
        "enabled": false
      },
      "call_hierarchy": {
        "enabled": true
      },
      "type_hierarchy": {
        "enabled": true
      },
      "document_links": {
        "enabled": true
      },
      "document_colors": {
        "enabled": true
      },
      "on_type_formatting": {
        "enabled": true,
        "indent_size": 4,
        "dedent_keywords": true,
        "colon_dedent": true,
        "colon_space": true,
        "bracket_indent": true,
        "auto_format_strings": true,
        "hash_space": true,
        "auto_docstring": true,
        "closer_align": true,
        "debug": false
      }
    }
  }
}

Workspace symbol options

Option Type Default Description
enabled bool true Enable/disable workspace symbol search
max_symbols int 500 Maximum symbols returned. 0 means no limit
ignore_folders list [] Extra folder names to skip (merged with built-in list)

Inlay hint options

Option Type Default Description
enabled bool true Enable/disable all inlay hints
show_assign_types bool true Show inferred types for unannotated assignments (x = 42 → : int)
show_return_types bool true Show inferred return types for unannotated functions (def f(): → -> str)
show_raises bool true Show raised exception types (raise ValueError(...) → Raises: ValueError)
show_parameter_hints bool true Show parameter names at call sites (f(1, 2) → a=1, b=2)
max_hints_per_file int 200 Maximum hints per file. 0 means no limit

Code lens options

Option Type Default Description
enabled bool true Enable/disable all code lenses
show_references bool true Show 👥 N references above every function, method, and class
show_implementations bool true Show 🔗 N implementations on classes with subclasses and methods with overrides
cross_file_implementations bool false Extend 🔗 counts to subclasses/overrides in other files. Opt-in — adds one get_references() call + file I/O per class/method
show_run bool true Show ▶ Run above if __name__ == "__main__": blocks
show_tests bool true Show 🧪 Run test above test_* functions and Test* classes
max_definitions int 150 Maximum number of definitions to process per file

Semantic token options

Option Type Default Description
enabled bool false Enable/disable semantic token highlighting. Opt-in — can be slow on very large files

Call hierarchy options

Option Type Default Description
enabled bool true Enable/disable call hierarchy (callHierarchy/incomingCalls, callHierarchy/outgoingCalls)

Type hierarchy options

Option Type Default Description
enabled bool true Enable/disable type hierarchy (typeHierarchy/supertypes, typeHierarchy/subtypes)
Option Type Default Description
enabled bool true Enable/disable document links (URLs in comments/strings and import resolution)

Document colors options

Option Type Default Description
enabled bool true Enable/disable document color previews (hex, RGB, HSL, CSS named colors)

On-type formatting options

Option Type Default Description
enabled bool true Enable/disable all on-type formatting
indent_size int 4 Fallback indent size when the client omits tabSize in the request options
dedent_keywords bool true Dedent new line after return/pass/raise/break/continue
colon_dedent bool true Align else/elif/except/finally/case to their opener on :
colon_space bool true Insert space after : in dict literals and type annotations
bracket_indent bool true Hanging indent inside unclosed (, [, { on Enter
auto_format_strings bool true Promote plain string to f-string when { is typed inside it
hash_space bool true Insert space between # and comment text (PEP 8)
auto_docstring bool true Insert Google-style docstring template when """ is typed after def/class
closer_align bool true Align multiline ) ] } closer to the indentation of its opener line
debug bool false Log incoming params and outgoing edits (troubleshooting)

Built-in ignored folders

.git, .hg, .svn, __pycache__, .mypy_cache, .ruff_cache, .pytest_cache, node_modules, .venv, venv, .env, env, dist, build, .eggs, egg-info

🚀 Usage

Once installed, your LSP client will receive workspaceSymbolProvider: true in the server capabilities. Use your client's "Go to Symbol in Workspace" command (typically Ctrl+T or # in the symbol picker).

Inlay hints

Your LSP client will receive inlayHintProvider: true in the server capabilities. Hints are rendered inline by the client automatically. The following hint types are supported:

  • Assignment hints — unannotated variable assignments, including self.attr in __init__
  • Return hints — unannotated def and async def functions, inferred from the first return statement
  • Raise hints — raise ExceptionType(...) statements
  • Parameter hints — positional argument names at call sites (keyword args are skipped as self-documenting)

Inlay hints respect type annotations already present in the source — annotated functions and variables are never hinted twice.

Code lens

Your LSP client will receive codeLensProvider: true. The following overlays are shown above definitions:

  • 👥 N references — every top-level function, method, and class. Inheritance usages (class Dog(Animal)) are excluded from the reference count and counted as implementations instead. Cross-file inheritance usages are also excluded when cross_file_implementations=true.
  • 🔗 N implementations — classes with direct subclasses; methods overridden in at least one subclass. Intra-file always; cross-file when cross_file_implementations=true (opt-in).
  • ▶ Run — if __name__ == "__main__": entry point blocks. Fires workspace/executeCommand with command pylsp_workspace_symbols.run_file.
  • 🧪 Run test — test_* functions and Test* / unittest.TestCase subclasses. Fires workspace/executeCommand with command pylsp_workspace_symbols.run_test, passing {"path": ..., "name": ..., "kind": "function"|"class"} as arguments.

Semantic tokens

Your LSP client will receive semanticTokensProvider when semantic_tokens.enabled is true. Token types follow an extended LSP legend: namespace, type, class, enum, interface, struct, typeParameter, parameter, variable, property, enumMember, function, method, macro, keyword, comment, string, number, regexp, operator, decorator, plus Python-specific selfParameter and clsParameter. Modifiers include declaration, readonly, static, deprecated, async, modification, documentation, defaultLibrary, builtin, classMember, and parameter. Disabled by default — enable explicitly if your client supports it and you want Jedi-backed token classification in addition to your editor's built-in lexer.

Call hierarchy

Your LSP client will receive callHierarchyProvider: true. Place the cursor on any function name and invoke "Show Call Hierarchy" to see incoming callers and outgoing callees. Compatible with any LSP client that supports the standard callHierarchy/* requests (Neovim, eglot, CudaText, etc.).

Type hierarchy

Your LSP client will receive typeHierarchyProvider: true. Place the cursor on any class name and invoke "Show Type Hierarchy" to explore supertypes and subtypes. Compatible with any LSP client that supports the standard typeHierarchy/* requests (Neovim, eglot, CudaText, etc.).

Your LSP client will receive documentLinkProvider: true. The following are turned into clickable links:

  • URLs — http:// and https:// links in comments and strings
  • Import statements — resolved to the corresponding .py source file in the system Python's Lib/ directory (when Python is installed and available on PATH); modules without a .py source (C extensions, frozen modules, embedded-only .pyc) are silently skipped
  • Workspace path literals — relative path strings (e.g. "./config.json", "../data/file.csv") and open()/Path() calls that reference files present in the workspace

Document colors

Your LSP client will receive colorProvider: true. Inline color swatches are shown for: hex (#rgb, #rrggbb, #rrggbbaa), rgb()/rgba(), hsl()/hsla(), and CSS named colors.

On-type formatting

Your LSP client will receive documentOnTypeFormattingProvider in the server capabilities. Formatting is applied automatically as you type — no key binding needed beyond the normal typing flow. The following triggers are supported:

  • Enter (\n) — smart indent after block openers (if, def, class, for, while, with, try, async); dedent after return/pass/raise/break/continue; hanging indent inside unclosed (, [, {
  • : — inserts a space after : in dict literals and type annotations; dedents else/elif/except/finally/case to align with their opener
  • { — promotes the enclosing plain string to an f-string (e.g. "hello " → f"hello {")
  • # — inserts a space between # and the following text (PEP 8 E262/E265)
  • " — when the third consecutive " completes """ on the line after a def or class, expands a Google-style docstring template with Args: and Returns: sections inferred from the signature
  • ) ] } — when typed alone on a line, aligns the closer to the indentation of the line containing its matching opener

Each trigger can be disabled independently via on_type_formatting settings.

🔍 How it works

Workspace symbols

pylsp does not define a pylsp_workspace_symbols hookspec, so this plugin uses a two-pronged approach:

  1. Capability injection (preferred) — at import time, monkey-patches PythonLSPServer.capabilities() to insert workspaceSymbolProvider: true and inlayHintProvider directly into the proper LSP capabilities dict. This makes the plugin work out-of-the-box with clients that require proper capabilities, such as Neovim and eglot.
  2. Experimental fallback — if the injection fails (e.g. pylsp changes its internal API), capabilities are announced via pylsp_experimental_capabilities instead. Clients that honour the experimental channel (CudaText, VSCode with pylsp, etc.) will still work.
  3. pylsp_dispatchers — registers a custom JSON-RPC handler for workspace/symbol that calls Jedi's project.complete_search() and filters results client-side by case-insensitive substring match.

Results are limited to files inside the known workspace folders. All open workspace roots are read from the live server at query time via server.workspaces, so folders added after startup (workspace/didChangeWorkspaceFolders) are included correctly. Each root is searched with sys_path=[root], restricting Jedi's indexing to that folder only — avoiding the full Python environment (stdlib + site-packages). This yields an ~80x speedup on complete_search compared to the default Jedi project. A _is_relative_to() guard (Python 3.8-compatible replacement for Path.is_relative_to, which requires 3.9+) provides a second layer of filtering.

Note: workspace/symbol returns module-level definitions (functions, classes, modules). Local variables inside functions are not indexed — this is standard LSP behaviour, consistent with pyright and other Python language servers.

Inlay hints

The plugin handles the textDocument/inlayHint request using a hybrid approach:

  1. Regex scan — fast pass over the source to locate def, assignment, raise, and call patterns.
  2. _literal_type fast-path — resolves common literals ("str", 42, True, [...], etc.) without calling Jedi.
  3. Jedi inference — for non-literal expressions, script.infer() and script.get_signatures() are used to resolve types.
  4. Signature fallback — for self.attr = param assignments, the enclosing def signature is inspected for type annotations or default values.

Code lens

Handled via the native pylsp_code_lens hookspec. Uses a two-pass approach:

  1. AST pass — single ast.walk over the file to collect all definitions and build intra-file maps of subclass relationships (class_subclasses) and method overrides (method_overrides). Also pre-computes inheritance usage positions (intra-file) to correctly separate reference counts from implementation counts.
  2. Jedi reference pass — one script.get_references() call per definition to count non-definition references. Cross-file inheritance positions are excluded from the reference count using _find_cross_file_subclasses (only when cross_file_implementations=True). Results are cached by (uri, hash(source)) so repeated requests on an unchanged file skip all work.

When cross_file_implementations=True, _find_cross_file_subclasses uses script.get_references() with the workspace project to find subclasses in other files, verifying each ref against the AST of the referenced file. A per-request _cf_subclass_cache ensures the I/O is shared between the references filter and the implementations count.

Semantic tokens

Handled via textDocument/semanticTokens/full, full/delta, and range dispatchers. Uses a two-phase O(n) approach — no per-token goto calls:

  1. AST pass — single ast.parse() walk to build lookup tables for token types and modifiers that Jedi alone cannot determine: enumMember, typeParameter, decorator, @classmethod/@staticmethod → static, ClassVar/Final → readonly, deprecated (via decorator or docstring), async, and augmented assignments → modification.
  2. Jedi pass — jedi.Script.get_names(all_scopes=True) provides the base token stream. A two-sub-pass strategy resolves statement references via lookup dicts built in phase 1, avoiding any per-token inference calls.

Beyond the two main passes, a token injection stage handles symbols that neither Jedi nor the AST walk emit directly: @ decorator markers, names inside forward-reference annotation strings, type parameter attributes (e.g. ParamSpec.args), and regexp patterns in re.compile() calls. Each injected token receives the correct type and modifiers using the same lookup tables built in the AST pass. Delta computation uses SequenceMatcher to produce minimal SemanticTokensEdit[] arrays.

Call hierarchy

Handled via callHierarchy/incomingCalls and callHierarchy/outgoingCalls dispatchers. Uses Jedi's script.goto() and script.get_references() to resolve callers and callees, building LSP-compliant CallHierarchyItem structures with correct range information. Functions decorated with @overload are handled correctly — Jedi returns all overload stubs as references to the real implementation (shared qualified name); incoming calls filters them out via ref.is_definition(), which is True for all definition sites and False for actual call sites.

Type hierarchy

Handled via typeHierarchy/supertypes and typeHierarchy/subtypes dispatchers. Uses Jedi's script.goto() and static class inheritance analysis to build the type tree.

Three-pass collection over the source:

  1. URL pass — regex scan for http:// and https:// URLs in comments, docstrings, and string literals.
  2. Import pass — AST parse to find all import and from ... import statements; resolves each module name to a .py file by querying the system Python's sys.prefix via a single cached subprocess call.
  3. Path literal pass — detects relative path strings, open() calls and Path() calls whose argument resolves to an existing file inside the workspace root.

Modules without a .py source (C extensions, frozen modules, .pyc-only embedded builds) are silently skipped.

Document colors

Regex-based scan over the source for color literals: hex (#rgb, #rrggbb, #rrggbbaa), rgb()/rgba(), hsl()/hsla(), and the full set of CSS named colors. Each match is returned as an LSP ColorInformation with normalised [0.0, 1.0] RGBA components.

On-type formatting

Handled via textDocument/onTypeFormatting. A single dispatcher _on_type_format receives the trigger character and delegates to a family of pure functions over the document line list:

  • _otf_newline_edits — computes the correct indentation for the new line using bracket balance (_otf_bracket_balance) and keyword detection; no Jedi calls
  • _otf_colon_space_edits — detects dict literals and annotations using _otf_strip_strings_comments to skip string/comment context and bracket balance to skip slices
  • _otf_colon_edits — scans backwards for the matching opener keyword (if/for/try/match) using _OTF_COLON_PARTNERS and re-indents the clause line
  • _otf_fstring_edits — delegates to _otf_find_enclosing_string to locate the quote token, then inserts f before the opening quote
  • _otf_hash_space_edits — checks for shebang/block-separator exceptions and replaces # with # so the caret lands after the space
  • _otf_docstring_edits — parses the nearest def signature (up to 15 lines back, multi-line aware) to build a Google-style template with correct Args: and Returns: entries; blank lines in the template are emitted without trailing whitespace
  • _otf_closer_align_edits — walks backwards with a bracket balance counter (starting at 1 for the closer) to find the opener line, then replaces the current line's leading whitespace with the opener's indentation

All handlers operate on the already-updated document source (post-didChange) and return standard LSP TextEdit[] arrays.

🧪 Tests

pip install -e ".[dev]"
pytest

🤝 Contributing

Issues and pull requests are welcome! Please open an issue before submitting a large change.

📚 References

👤 Author

Bruno Eduardo — github.com/Hanatarou

📄 License

MIT

Metadata

Release files for pylsp-workspace-symbols 0.7.0

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

Source distribution (sdist)

Source distribution for pylsp-workspace-symbols 0.7.0
File Size Uploaded Provenance Actions
pylsp_workspace_symbols-0.7.0.tar.gz 80.8 kB With provenance Details for pylsp_workspace_symbols-0.7.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for pylsp-workspace-symbols 0.7.0
File Interpreter ABI Platform Actions
pylsp_workspace_symbols-0.7.0-py3-none-any.whl Python 3 none any Details for pylsp_workspace_symbols-0.7.0-py3-none-any.whl

Total release size: 145.2 kB

Release files / pylsp_workspace_symbols-0.7.0.tar.gz

Download URL pylsp_workspace_symbols-0.7.0.tar.gz
Size 80.8 kB
Tags Source
SHA-256 checksum
How to use checksums
46119be525f21c93a712922fb46b9086f280b396156b44cc2c3fe06ec323c732
BLAKE2b-256 checksum
How to use checksums
6c864df09d33854012f7d583abc9815ba7c57e1f3c7e2e2406aa705184632f5d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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 Jun 27, 2026.

Transparency log

Release files / pylsp_workspace_symbols-0.7.0-py3-none-any.whl

Download URL pylsp_workspace_symbols-0.7.0-py3-none-any.whl
Size 64.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4fcf6ba3bc1404cee6a447e5a193e8cc9eb893822be473ca1c2eb9bdd2099f21
BLAKE2b-256 checksum
How to use checksums
ad50fa6e602e5635b75767a746155d2190088bc6b3742bf6f4135f4ee1fe1cc1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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 Jun 27, 2026.

Transparency log

Security for pylsp-workspace-symbols 0.7.0

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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for pylsp_workspace_symbols-0.7.0.tar.gz

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 Jun 27, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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