Python Language Server Protocol implementation providing code intelligence features for Python development
—
Comprehensive plugin system using Pluggy hooks for extending Python LSP Server functionality. Provides 40+ hook specifications covering all LSP features, document lifecycle, server management, and custom extensions.
Core decorators for plugin development.
hookimpl = pluggy.HookimplMarker("pylsp") # Mark hook implementations
hookspec = pluggy.HookspecMarker("pylsp") # Mark hook specificationsHooks called during document open, save, and update operations.
@hookimpl
def pylsp_document_did_open(config, workspace, document):
"""
Called when a document is opened.
Parameters:
- config: Config instance
- workspace: Workspace instance
- document: Document instance
"""
@hookimpl
def pylsp_document_did_save(config, workspace, document):
"""
Called when a document is saved.
Parameters:
- config: Config instance
- workspace: Workspace instance
- document: Document instance
"""Hooks for implementing core LSP language features.
@hookimpl
def pylsp_completions(config, workspace, document, position, ignored_names):
"""
Provide code completions.
Parameters:
- config: Config instance
- workspace: Workspace instance
- document: Document instance
- position: dict with 'line' and 'character' keys
- ignored_names: list of names to ignore
Returns:
list: Completion items as dicts
"""
@hookimpl(hookwrapper=True) # firstresult hook
def pylsp_completion_item_resolve(config, workspace, document, completion_item):
"""
Resolve additional completion item information.
Parameters:
- config: Config instance
- workspace: Workspace instance
- document: Document instance
- completion_item: dict, completion item to resolve
Returns:
dict: Enhanced completion item
"""
@hookimpl
def pylsp_definitions(config, workspace, document, position):
"""
Provide go-to-definition locations.
Parameters:
- config: Config instance
- workspace: Workspace instance
- document: Document instance
- position: dict with 'line' and 'character' keys
Returns:
list: Location dicts with 'uri' and 'range'
"""
@hookimpl(hookwrapper=True) # firstresult hook
def pylsp_type_definition(config, document, position):
"""
Provide go-to-type-definition locations.
Parameters:
- config: Config instance
- document: Document instance
- position: dict with 'line' and 'character' keys
Returns:
list: Location dicts with 'uri' and 'range'
"""
@hookimpl(hookwrapper=True) # firstresult hook
def pylsp_hover(config, workspace, document, position):
"""
Provide hover information.
Parameters:
- config: Config instance
- workspace: Workspace instance
- document: Document instance
- position: dict with 'line' and 'character' keys
Returns:
dict: Hover response with 'contents' and optional 'range'
"""
@hookimpl(hookwrapper=True) # firstresult hook
def pylsp_signature_help(config, workspace, document, position):
"""
Provide signature help.
Parameters:
- config: Config instance
- workspace: Workspace instance
- document: Document instance
- position: dict with 'line' and 'character' keys
Returns:
dict: Signature help with 'signatures' list
"""
@hookimpl
def pylsp_document_symbols(config, workspace, document):
"""
Provide document symbols.
Parameters:
- config: Config instance
- workspace: Workspace instance
- document: Document instance
Returns:
list: Document symbol dicts
"""
@hookimpl
def pylsp_references(config, workspace, document, position, exclude_declaration):
"""
Find references to symbol.
Parameters:
- config: Config instance
- workspace: Workspace instance
- document: Document instance
- position: dict with 'line' and 'character' keys
- exclude_declaration: bool, exclude declaration from results
Returns:
list: Location dicts with 'uri' and 'range'
"""
@hookimpl(hookwrapper=True) # firstresult hook
def pylsp_rename(config, workspace, document, position, new_name):
"""
Rename symbol.
Parameters:
- config: Config instance
- workspace: Workspace instance
- document: Document instance
- position: dict with 'line' and 'character' keys
- new_name: str, new symbol name
Returns:
dict: WorkspaceEdit with document changes
"""
@hookimpl
def pylsp_document_highlight(config, workspace, document, position):
"""
Highlight occurrences of symbol.
Parameters:
- config: Config instance
- workspace: Workspace instance
- document: Document instance
- position: dict with 'line' and 'character' keys
Returns:
list: DocumentHighlight dicts with 'range' and 'kind'
"""Hooks for code actions, formatting, and refactoring.
@hookimpl
def pylsp_code_actions(config, workspace, document, range, context):
"""
Provide code actions.
Parameters:
- config: Config instance
- workspace: Workspace instance
- document: Document instance
- range: dict with 'start' and 'end' positions
- context: dict with diagnostics and action kinds
Returns:
list: CodeAction dicts
"""
@hookimpl
def pylsp_code_lens(config, workspace, document):
"""
Provide code lenses.
Parameters:
- config: Config instance
- workspace: Workspace instance
- document: Document instance
Returns:
list: CodeLens dicts with 'range' and 'command'
"""
@hookimpl(hookwrapper=True) # firstresult hook
def pylsp_format_document(config, workspace, document, options):
"""
Format entire document.
Parameters:
- config: Config instance
- workspace: Workspace instance
- document: Document instance
- options: dict with formatting options
Returns:
list: TextEdit dicts
"""
@hookimpl(hookwrapper=True) # firstresult hook
def pylsp_format_range(config, workspace, document, range, options):
"""
Format document range.
Parameters:
- config: Config instance
- workspace: Workspace instance
- document: Document instance
- range: dict with 'start' and 'end' positions
- options: dict with formatting options
Returns:
list: TextEdit dicts
"""
@hookimpl
def pylsp_folding_range(config, workspace, document):
"""
Provide folding ranges.
Parameters:
- config: Config instance
- workspace: Workspace instance
- document: Document instance
Returns:
list: FoldingRange dicts
"""Hooks for providing diagnostics and linting information.
@hookimpl
def pylsp_lint(config, workspace, document, is_saved):
"""
Provide linting diagnostics.
Parameters:
- config: Config instance
- workspace: Workspace instance
- document: Document instance
- is_saved: bool, whether document was just saved
Returns:
list: Diagnostic dicts with 'range', 'message', 'severity'
"""Hooks called during server initialization, configuration changes, and shutdown.
@hookimpl
def pylsp_initialize(config, workspace):
"""
Called during server initialization.
Parameters:
- config: Config instance
- workspace: Workspace instance
"""
@hookimpl
def pylsp_initialized():
"""Called after server initialization is complete."""
@hookimpl
def pylsp_shutdown(config, workspace):
"""
Called during server shutdown.
Parameters:
- config: Config instance
- workspace: Workspace instance
"""
@hookimpl
def pylsp_workspace_configuration_changed(config, workspace):
"""
Called when workspace configuration changes.
Parameters:
- config: Config instance
- workspace: Workspace instance
"""Hooks for custom commands and experimental features.
@hookimpl
def pylsp_commands(config, workspace):
"""
Provide custom command names.
Parameters:
- config: Config instance
- workspace: Workspace instance
Returns:
list: Command name strings
"""
@hookimpl(hookwrapper=True) # firstresult hook
def pylsp_execute_command(config, workspace, command, arguments):
"""
Execute custom command.
Parameters:
- config: Config instance
- workspace: Workspace instance
- command: str, command name
- arguments: list, command arguments
Returns:
any: Command result
"""
@hookimpl
def pylsp_dispatchers(config, workspace):
"""
Provide custom message dispatchers.
Parameters:
- config: Config instance
- workspace: Workspace instance
Returns:
dict: Method name to handler mappings
"""
@hookimpl
def pylsp_experimental_capabilities(config, workspace):
"""
Provide experimental server capabilities.
Parameters:
- config: Config instance
- workspace: Workspace instance
Returns:
dict: Experimental capabilities
"""
@hookimpl
def pylsp_settings(config):
"""
Provide plugin settings schema.
Parameters:
- config: Config instance
Returns:
dict: Settings schema
"""Entry points for built-in plugins automatically loaded by the server.
# From pyproject.toml [project.entry-points.pylsp]
BUILTIN_PLUGINS = {
"autopep8": "pylsp.plugins.autopep8_format",
"folding": "pylsp.plugins.folding",
"flake8": "pylsp.plugins.flake8_lint",
"jedi_completion": "pylsp.plugins.jedi_completion",
"jedi_definition": "pylsp.plugins.definition",
"jedi_type_definition": "pylsp.plugins.type_definition",
"jedi_hover": "pylsp.plugins.hover",
"jedi_highlight": "pylsp.plugins.highlight",
"jedi_references": "pylsp.plugins.references",
"jedi_rename": "pylsp.plugins.jedi_rename",
"jedi_signature_help": "pylsp.plugins.signature",
"jedi_symbols": "pylsp.plugins.symbols",
"mccabe": "pylsp.plugins.mccabe_lint",
"preload": "pylsp.plugins.preload_imports",
"pycodestyle": "pylsp.plugins.pycodestyle_lint",
"pydocstyle": "pylsp.plugins.pydocstyle_lint",
"pyflakes": "pylsp.plugins.pyflakes_lint",
"pylint": "pylsp.plugins.pylint_lint",
"rope_completion": "pylsp.plugins.rope_completion",
"rope_autoimport": "pylsp.plugins.rope_autoimport",
"yapf": "pylsp.plugins.yapf_format"
}from pylsp import hookimpl
@hookimpl
def pylsp_lint(config, workspace, document, is_saved):
"""Custom linter that flags TODO comments."""
diagnostics = []
lines = document.source.splitlines()
for line_num, line in enumerate(lines):
if 'TODO' in line:
todo_pos = line.find('TODO')
diagnostics.append({
"range": {
"start": {"line": line_num, "character": todo_pos},
"end": {"line": line_num, "character": todo_pos + 4}
},
"message": "TODO comment found",
"severity": 3, # Information
"source": "todo-linter"
})
return diagnosticsfrom pylsp import hookimpl
@hookimpl
def pylsp_completions(config, workspace, document, position, ignored_names):
"""Provide custom completions for common patterns."""
completions = []
# Add custom completions based on context
word = document.word_at_position(position)
if word.startswith('log'):
completions.append({
"label": "logging.getLogger(__name__)",
"kind": 15, # Snippet
"detail": "Create logger instance",
"insertText": "logging.getLogger(__name__)",
"documentation": "Standard logger initialization pattern"
})
return completionsfrom pylsp import hookimpl
@hookimpl
def pylsp_settings(config):
"""Define plugin settings schema."""
return {
"plugins": {
"my_plugin": {
"type": "object",
"properties": {
"enabled": {"type": "boolean", "default": True},
"severity": {"type": "string", "default": "warning"}
}
}
}
}
@hookimpl
def pylsp_lint(config, workspace, document, is_saved):
"""Use plugin configuration."""
settings = config.plugin_settings("my_plugin", document.path)
if not settings.get("enabled", True):
return []
severity_map = {"error": 1, "warning": 2, "info": 3, "hint": 4}
severity = severity_map.get(settings.get("severity", "warning"), 2)
# Perform linting with configured severity
return [{
"range": {"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 1}},
"message": "Custom diagnostic",
"severity": severity,
"source": "my_plugin"
}]Install with Tessl CLI
npx tessl i tessl/pypi-python-lsp-server