|
| 1 | +from __future__ import annotations |
| 2 | + |
| 3 | +import asyncio |
| 4 | +import logging |
| 5 | +import os |
| 6 | +import typing as typ |
| 7 | + |
| 8 | +from lsprotocol import types as lsptyp |
| 9 | +from pygls import server |
| 10 | + |
| 11 | +from pylsp import PYLSP, __version__ |
| 12 | +from pylsp._utils import flatten, is_process_alive |
| 13 | +from pylsp.server.protocol import LangageServerProtocol |
| 14 | + |
| 15 | +if typ.TYPE_CHECKING: |
| 16 | + from pylsp.server.workspace import Workspace |
| 17 | + |
| 18 | +logger = logging.getLogger(__name__) |
| 19 | + |
| 20 | +MAX_WORKERS = 64 |
| 21 | +PARENT_PROCESS_WATCH_INTERVAL = 10 # 10 s |
| 22 | + |
| 23 | +CONFIG_FILES = ("pycodestyle.cfg", "setup.cfg", "tox.ini", ".flake8") |
| 24 | + |
| 25 | + |
| 26 | +class LanguageServer(server.LanguageServer): |
| 27 | + """Python Language Server.""" |
| 28 | + |
| 29 | + lsp: LangageServerProtocol |
| 30 | + |
| 31 | + @property |
| 32 | + def workspace(self) -> Workspace: |
| 33 | + """Returns in-memory workspace.""" |
| 34 | + return self.lsp.workspace |
| 35 | + |
| 36 | + def check_parent_process(self): |
| 37 | + """Check if the parent process is still alive.""" |
| 38 | + async def watch_parent_process(): |
| 39 | + ppid = os.getppid() |
| 40 | + while True: |
| 41 | + if self._stop_event is not None and self._stop_event.is_set(): |
| 42 | + break |
| 43 | + if not is_process_alive(ppid): |
| 44 | + self.shutdown() |
| 45 | + break |
| 46 | + await asyncio.sleep(PARENT_PROCESS_WATCH_INTERVAL) |
| 47 | + asyncio.create_task(watch_parent_process()) |
| 48 | + |
| 49 | + |
| 50 | +LSP_SERVER = LanguageServer( |
| 51 | + name=PYLSP, |
| 52 | + version=__version__, |
| 53 | + max_workers=MAX_WORKERS, |
| 54 | + protocol_cls=LangageServerProtocol, |
| 55 | + text_document_sync_kind=lsptyp.TextDocumentSyncKind.Incremental, |
| 56 | + notebook_document_sync=lsptyp.NotebookDocumentSyncOptions( |
| 57 | + notebook_selector=[ |
| 58 | + lsptyp.NotebookDocumentSyncOptionsNotebookSelectorType2( |
| 59 | + cells=[ |
| 60 | + lsptyp.NotebookDocumentSyncOptionsNotebookSelectorType2CellsType( |
| 61 | + language="python" |
| 62 | + ), |
| 63 | + ], |
| 64 | + ), |
| 65 | + ], |
| 66 | + ), |
| 67 | +) |
| 68 | + |
| 69 | + |
| 70 | +@LSP_SERVER.feature(lsptyp.INITIALIZE) |
| 71 | +async def initialize(ls: LanguageServer, params: lsptyp.InitializeParams): |
| 72 | + """Handle the initialization request.""" |
| 73 | + # Call the initialization hook |
| 74 | + await ls.lsp.call_hook("pylsp_initialize") |
| 75 | + |
| 76 | +@LSP_SERVER.feature(lsptyp.INITIALIZED) |
| 77 | +async def initialized(ls: LanguageServer, params: lsptyp.InitializedParams): |
| 78 | + """Handle the initialized notification.""" |
| 79 | + # Call the initialized hook |
| 80 | + await ls.lsp.call_hook("pylsp_initialized") |
| 81 | + |
| 82 | +@LSP_SERVER.feature(lsptyp.WORKSPACE_DID_CHANGE_CONFIGURATION) |
| 83 | +async def workspace_did_change_configuration(ls: LanguageServer, params: lsptyp.WorkspaceConfigurationParams): |
| 84 | + """Handle the workspace did change configuration notification.""" |
| 85 | + for config_item in params.items: |
| 86 | + ls.workspace.config.update({config_item.scope_uri: config_item.section}) |
| 87 | + # TODO: Check configuration update is valid and supports this type of update |
| 88 | + await ls.lsp.call_hook("pylsp_workspace_configuration_changed") |
| 89 | + |
| 90 | +@LSP_SERVER.feature(lsptyp.WORKSPACE_DID_CHANGE_WATCHED_FILES) |
| 91 | +def workspace_did_change_watched_files(ls: LanguageServer, params: lsptyp.DidChangeWatchedFilesParams): |
| 92 | + """Handle the workspace did change watched files notification.""" |
| 93 | + for change in params.changes: |
| 94 | + if change.uri.endswith(CONFIG_FILES): |
| 95 | + ls.workspace.config.settings.cache_clear() |
| 96 | + break |
| 97 | + |
| 98 | + # TODO: check if necessary to link files not handled by textDocument/Open |
| 99 | + |
| 100 | +@LSP_SERVER.feature(lsptyp.WORKSPACE_EXECUTE_COMMAND) |
| 101 | +async def workspace_execute_command(ls: LanguageServer, params: lsptyp.ExecuteCommandParams): |
| 102 | + """Handle the workspace execute command request.""" |
| 103 | + # Call the execute command hook |
| 104 | + await ls.lsp.call_hook("pylsp_execute_command", command=params.command, arguments=params.arguments, work_done_token=params.work_done_token) |
| 105 | + |
| 106 | +@LSP_SERVER.feature(lsptyp.NOTEBOOK_DOCUMENT_DID_OPEN) |
| 107 | +async def notebook_document_did_open(ls: LanguageServer, params: lsptyp.DidOpenNotebookDocumentParams): |
| 108 | + """Handle the notebook document did open notification.""" |
| 109 | + await ls.lsp.lint_notebook_document(params.notebook_document.uri) |
| 110 | + |
| 111 | +@LSP_SERVER.feature(lsptyp.NOTEBOOK_DOCUMENT_DID_CHANGE) |
| 112 | +async def notebook_document_did_change(ls: LanguageServer, params: lsptyp.DidChangeNotebookDocumentParams): |
| 113 | + """Handle the notebook document did change notification.""" |
| 114 | + await ls.lsp.lint_notebook_document(params.notebook_document.uri) |
| 115 | + |
| 116 | +@LSP_SERVER.feature(lsptyp.NOTEBOOK_DOCUMENT_DID_SAVE) |
| 117 | +async def notebook_document_did_save(ls: LanguageServer, params: lsptyp.DidSaveNotebookDocumentParams): |
| 118 | + """Handle the notebook document did save notification.""" |
| 119 | + await ls.lsp.lint_notebook_document(params.notebook_document.uri) |
| 120 | + await ls.workspace.save(params.notebook_document.uri) |
| 121 | + |
| 122 | +@LSP_SERVER.feature(lsptyp.NOTEBOOK_DOCUMENT_DID_CLOSE) |
| 123 | +async def notebook_document_did_close(ls: LanguageServer, params: lsptyp.DidCloseNotebookDocumentParams): |
| 124 | + """Handle the notebook document did close notification.""" |
| 125 | + await ls.lsp.cancel_tasks(params.notebook_document.uri) |
| 126 | + |
| 127 | +@LSP_SERVER.feature(lsptyp.TEXT_DOCUMENT_DID_OPEN) |
| 128 | +async def text_document_did_open(ls: LanguageServer, params: lsptyp.DidOpenTextDocumentParams): |
| 129 | + """Handle the text document did open notification.""" |
| 130 | + await ls.lsp.lint_text_document(params.text_document.uri) |
| 131 | + |
| 132 | +@LSP_SERVER.feature(lsptyp.TEXT_DOCUMENT_DID_CHANGE) |
| 133 | +async def text_document_did_change(ls: LanguageServer, params: lsptyp.DidChangeTextDocumentParams): |
| 134 | + """Handle the text document did change notification.""" |
| 135 | + await ls.lsp.lint_text_document(params.text_document.uri) |
| 136 | + |
| 137 | +@LSP_SERVER.feature(lsptyp.TEXT_DOCUMENT_DID_SAVE) |
| 138 | +async def text_document_did_save(ls: LanguageServer, params: lsptyp.DidSaveTextDocumentParams): |
| 139 | + """Handle the text document did save notification.""" |
| 140 | + await ls.lsp.lint_text_document(params.text_document.uri) |
| 141 | + await ls.workspace.save(params.text_document.uri) |
| 142 | + |
| 143 | +@LSP_SERVER.feature(lsptyp.TEXT_DOCUMENT_DID_CLOSE) |
| 144 | +async def text_document_did_close(ls: LanguageServer, params: lsptyp.DidCloseTextDocumentParams): |
| 145 | + """Handle the text document did close notification.""" |
| 146 | + await ls.lsp.cancel_tasks(params.text_document.uri) |
| 147 | + |
| 148 | +@LSP_SERVER.feature(lsptyp.TEXT_DOCUMENT_CODE_ACTION) |
| 149 | +async def text_document_code_action(ls: LanguageServer, params: lsptyp.CodeActionParams) -> typ.List[lsptyp.Command | lsptyp.CodeAction] | None: |
| 150 | + """Handle the text document code action request.""" |
| 151 | + actions: typ.List[lsptyp.Command | lsptyp.CodeAction] | None = flatten(await ls.lsp.call_hook( |
| 152 | + "pylsp_code_action", |
| 153 | + params.text_document.uri, |
| 154 | + range=params.range, |
| 155 | + context=params.context, |
| 156 | + work_done_token=params.work_done_token, |
| 157 | + )) |
| 158 | + return actions |
| 159 | + |
| 160 | +@LSP_SERVER.feature(lsptyp.TEXT_DOCUMENT_CODE_LENS) |
| 161 | +async def text_document_code_lens(ls: LanguageServer, params: lsptyp.CodeLensParams) -> typ.List[lsptyp.CodeLens] | None: |
| 162 | + """Handle the text document code lens request.""" |
| 163 | + lenses: typ.List[lsptyp.CodeLens] | None = flatten(await ls.lsp.call_hook( |
| 164 | + "pylsp_code_lens", |
| 165 | + params.text_document.uri, |
| 166 | + work_done_token=params.work_done_token, |
| 167 | + )) |
| 168 | + return lenses |
| 169 | + |
| 170 | +@LSP_SERVER.feature(lsptyp.TEXT_DOCUMENT_COMPLETION) |
| 171 | +async def text_document_completion(ls: LanguageServer, params: lsptyp.CompletionParams) -> typ.List[lsptyp.CompletionItem] | None: |
| 172 | + """Handle the text document completion request.""" |
| 173 | + completions: typ.List[lsptyp.CompletionItem] | None = flatten(await ls.lsp.call_hook( |
| 174 | + "pylsp_completion", |
| 175 | + params.text_document.uri, |
| 176 | + position=params.position, |
| 177 | + context=params.context, |
| 178 | + work_done_token=params.work_done_token, |
| 179 | + )) |
| 180 | + return completions |
| 181 | + |
| 182 | +@LSP_SERVER.feature(lsptyp.COMPLETION_ITEM_RESOLVE) |
| 183 | +async def completion_item_resolve(ls: LanguageServer, params: lsptyp.CompletionItem) -> lsptyp.CompletionItem | None: |
| 184 | + """Handle the completion item resolve request.""" |
| 185 | + item: lsptyp.CompletionItem | None = await ls.lsp.call_hook( |
| 186 | + "pylsp_completion_item_resolve", |
| 187 | + (params.data or {}).get("doc_uri"), |
| 188 | + completion_item=params, |
| 189 | + ) |
| 190 | + return item |
| 191 | + |
| 192 | +@LSP_SERVER.feature(lsptyp.TEXT_DOCUMENT_DEFINITION) |
| 193 | +async def text_document_definition(ls: LanguageServer, params: lsptyp.DefinitionParams) -> lsptyp.Location | None: |
| 194 | + """Handle the text document definition request.""" |
| 195 | + location: lsptyp.Location | None = await ls.lsp.call_hook( |
| 196 | + "pylsp_definitions", |
| 197 | + params.text_document.uri, |
| 198 | + position=params.position, |
| 199 | + work_done_token=params.work_done_token, |
| 200 | + ) |
| 201 | + return location |
| 202 | + |
| 203 | +@LSP_SERVER.feature(lsptyp.TEXT_DOCUMENT_TYPE_DEFINITION) |
| 204 | +async def text_document_type_definition(ls: LanguageServer, params: lsptyp.TypeDefinitionParams) -> lsptyp.Location | None: |
| 205 | + """Handle the text document type definition request.""" |
| 206 | + location: lsptyp.Location | None = await ls.lsp.call_hook( |
| 207 | + "pylsp_type_definition", |
| 208 | + params.text_document.uri, |
| 209 | + position=params.position, |
| 210 | + work_done_token=params.work_done_token, |
| 211 | + ) |
| 212 | + return location |
| 213 | + |
| 214 | +@LSP_SERVER.feature(lsptyp.TEXT_DOCUMENT_DOCUMENT_HIGHLIGHT) |
| 215 | +async def text_document_document_highlight(ls: LanguageServer, params: lsptyp.DocumentHighlightParams) -> typ.List[lsptyp.DocumentHighlight] | None: |
| 216 | + """Handle the text document document highlight request.""" |
| 217 | + highlights: typ.List[lsptyp.DocumentHighlight] | None = flatten(await ls.lsp.call_hook( |
| 218 | + "pylsp_document_highlight", |
| 219 | + params.text_document.uri, |
| 220 | + position=params.position, |
| 221 | + work_done_token=params.work_done_token, |
| 222 | + )) |
| 223 | + return highlights |
| 224 | + |
| 225 | +@LSP_SERVER.feature(lsptyp.TEXT_DOCUMENT_HOVER) |
| 226 | +async def text_document_hover(ls: LanguageServer, params: lsptyp.HoverParams) -> lsptyp.Hover | None: |
| 227 | + """Handle the text document hover request.""" |
| 228 | + hover: lsptyp.Hover = await ls.lsp.call_hook( |
| 229 | + "pylsp_hover", |
| 230 | + params.text_document.uri, |
| 231 | + position=params.position, |
| 232 | + work_done_token=params.work_done_token, |
| 233 | + ) |
| 234 | + return hover |
| 235 | + |
| 236 | +@LSP_SERVER.feature(lsptyp.TEXT_DOCUMENT_DOCUMENT_SYMBOL) |
| 237 | +async def text_document_document_symbol(ls: LanguageServer, params: lsptyp.DocumentSymbolParams) -> typ.List[lsptyp.DocumentSymbol] | None: |
| 238 | + """Handle the text document document symbol request.""" |
| 239 | + symbols: typ.List[lsptyp.DocumentSymbol] | None = flatten(await ls.lsp.call_hook( |
| 240 | + "pylsp_document_symbols", |
| 241 | + params.text_document.uri, |
| 242 | + work_done_token=params.work_done_token, |
| 243 | + )) |
| 244 | + return symbols |
| 245 | + |
| 246 | +@LSP_SERVER.feature(lsptyp.TEXT_DOCUMENT_FORMATTING) |
| 247 | +async def text_document_formatting(ls: LanguageServer, params: lsptyp.DocumentFormattingParams) -> typ.List[lsptyp.TextEdit] | None: |
| 248 | + """Handle the text document formatting request.""" |
| 249 | + edits: typ.List[lsptyp.TextEdit] | None = flatten(await ls.lsp.call_hook( |
| 250 | + "pylsp_format_document", |
| 251 | + params.text_document.uri, |
| 252 | + options=params.options, |
| 253 | + )) |
| 254 | + return edits |
| 255 | + |
| 256 | +@LSP_SERVER.feature(lsptyp.TEXT_DOCUMENT_RANGE_FORMATTING) |
| 257 | +async def text_document_range_formatting(ls: LanguageServer, params: lsptyp.DocumentRangeFormattingParams) -> typ.List[lsptyp.TextEdit] | None: |
| 258 | + """Handle the text document range formatting request.""" |
| 259 | + edits: typ.List[lsptyp.TextEdit] | None = flatten(await ls.lsp.call_hook( |
| 260 | + "pylsp_format_range", |
| 261 | + params.text_document.uri, |
| 262 | + range=params.range, |
| 263 | + options=params.options, |
| 264 | + )) |
| 265 | + return edits |
| 266 | + |
| 267 | +@LSP_SERVER.feature(lsptyp.TEXT_DOCUMENT_FOLDING_RANGE) |
| 268 | +async def text_document_folding_range(ls: LanguageServer, params: lsptyp.FoldingRangeParams) -> typ.List[lsptyp.FoldingRange] | None: |
| 269 | + """Handle the text document folding range request.""" |
| 270 | + ranges: typ.List[lsptyp.FoldingRange] | None = flatten(await ls.lsp.call_hook( |
| 271 | + "pylsp_folding_range", |
| 272 | + params.text_document.uri, |
| 273 | + work_done_token=params.work_done_token, |
| 274 | + )) |
| 275 | + return ranges |
| 276 | + |
| 277 | +@LSP_SERVER.feature(lsptyp.TEXT_DOCUMENT_REFERENCES) |
| 278 | +async def text_document_references(ls: LanguageServer, params: lsptyp.ReferenceParams) -> typ.List[lsptyp.Location] | None: |
| 279 | + """Handle the text document references request.""" |
| 280 | + locations: typ.List[lsptyp.Location] | None = flatten(await ls.lsp.call_hook( |
| 281 | + "pylsp_references", |
| 282 | + params.text_document.uri, |
| 283 | + position=params.position, |
| 284 | + context=params.context, |
| 285 | + )) |
| 286 | + return locations |
| 287 | + |
| 288 | +@LSP_SERVER.feature(lsptyp.TEXT_DOCUMENT_SIGNATURE_HELP) |
| 289 | +async def text_document_signature_help(ls: LanguageServer, params: lsptyp.SignatureHelpParams) -> lsptyp.SignatureHelp | None: |
| 290 | + """Handle the text document signature help request.""" |
| 291 | + signature_help: lsptyp.SignatureHelp | None = await ls.lsp.call_hook( |
| 292 | + "pylsp_signature_help", |
| 293 | + params.text_document.uri, |
| 294 | + position=params.position, |
| 295 | + work_done_token=params.work_done_token, |
| 296 | + ) |
| 297 | + return signature_help |
| 298 | + |
0 commit comments