import logging from typing import Optional from jedi import Script from jedi.api.refactoring import RefactoringError from lsprotocol.types import (CompletionItem, CompletionList, CompletionOptions, CompletionParams, DefinitionParams, Hover, HoverParams, InsertTextFormat, Location, ReferenceParams, RenameParams, TextDocumentPositionParams, TypeDefinitionParams, WorkspaceEdit, TEXT_DOCUMENT_COMPLETION, TEXT_DOCUMENT_DEFINITION, TEXT_DOCUMENT_HOVER, TEXT_DOCUMENT_REFERENCES, TEXT_DOCUMENT_RENAME, TEXT_DOCUMENT_TYPE_DEFINITION) from pygls.server import LanguageServer from pygls.workspace import Document from italianswirls.glue import (gen_document_edits, get_jedi_position, get_lsp_completion_kind, get_lsp_locations) LS = LanguageServer('italianswirls', 'v0.0.1') def get_jedi_script(document: Document) -> Script: """Get Jedi Script object from this document.""" return Script(code=document.source, path=document.path) def get_jedi_script_from_params( params: TextDocumentPositionParams, server: LanguageServer ) -> Script: """Get Jedi Script using text document params provided by the client.""" document_uri = params.text_document.uri document = server.workspace.get_document(document_uri) script = get_jedi_script(document) return script @LS.feature(TEXT_DOCUMENT_COMPLETION, CompletionOptions(trigger_characters=["."])) async def do_completion( server: LanguageServer, params: CompletionParams, ) -> CompletionList: """Return completion items.""" script = get_jedi_script_from_params(params, server) jedi_position = get_jedi_position(params.position) jedi_completions = script.complete(*jedi_position) completion_items = [] for jedi_completion in jedi_completions: name = jedi_completion.name item = CompletionItem( label=name, filter_text=name, kind=get_lsp_completion_kind(jedi_completion.type), sort_text=name, insert_text_name=name, insert_text_format=InsertTextFormat.PlainText, ) completion_items.append(item) return CompletionList( is_incomplete=False, items=completion_items, ) @LS.feature(TEXT_DOCUMENT_DEFINITION) async def do_definition( server: LanguageServer, params: DefinitionParams, ) -> Optional[list[Location]]: """Return the definition location(s) of the target symbol.""" script = get_jedi_script_from_params(params, server) jedi_position = get_jedi_position(params.position) jedi_names = script.goto( *jedi_position, follow_imports=True, follow_builtin_imports=True, ) return get_lsp_locations(jedi_names) or None @LS.feature(TEXT_DOCUMENT_TYPE_DEFINITION) async def do_type_definition( server: LanguageServer, params: TypeDefinitionParams, ) -> Optional[list[Location]]: """Return the type definition location(s) of the target symbol.""" script = get_jedi_script_from_params(params, server) jedi_position = get_jedi_position(params.position) jedi_names = script.infer(*jedi_position) return get_lsp_locations(jedi_names) or None @LS.feature(TEXT_DOCUMENT_REFERENCES) async def do_references( server: LanguageServer, params: ReferenceParams, ) -> Optional[list[Location]]: """Return the type definition location(s) of the target symbol.""" script = get_jedi_script_from_params(params, server) jedi_position = get_jedi_position(params.position) jedi_names = script.get_references(*jedi_position) return get_lsp_locations(jedi_names) or None @LS.feature(TEXT_DOCUMENT_HOVER) async def do_hover( server: LanguageServer, params: HoverParams, ) -> Optional[Hover]: """Provide "hover", which is the documentation of the target symbol. Jedi provides a list of names with information, usually only one. We handle them all and concatenate them, separated by a horizontal line. For simplicity, the text is mostly provided untouched, including docstrings, so if your client tries to interpret it as Markdown even though there are rogue `**kwargs` hanging around you might have a few display issues. """ script = get_jedi_script_from_params(params, server) jedi_position = get_jedi_position(params.position) jedi_help_names = script.help(*jedi_position) if not jedi_help_names: return None help_texts = [] for jedi_name in jedi_help_names: text = "" if full_name := jedi_name.full_name: text += f"`{full_name}`\n" if sigs := jedi_name.get_signatures(): text += "\n".join(f"`{sig.to_string()}`" for sig in sigs) + "\n" if docstring := jedi_name.docstring(raw=True): text += "\n" + docstring if text: help_texts.append(text) if not help_texts: return None hover_text = "\n\n---\n\n".join(help_texts) return Hover(contents=hover_text) @LS.feature(TEXT_DOCUMENT_RENAME) async def do_rename( server: LanguageServer, params: RenameParams, ) -> Optional[WorkspaceEdit]: """Ask Jedi to rename a symbol and return the resulting state.""" script = get_jedi_script_from_params(params, server) jedi_position = get_jedi_position(params.position) try: refactoring = script.rename(*jedi_position, new_name=params.new_name) except RefactoringError as exc: logging.error(f"Refactoring failed: {exc}") return None changes = list(gen_document_edits(refactoring, server.workspace)) logging.info(f"changes: {changes}") return WorkspaceEdit(document_changes=changes) if changes else None