class LanguageServer(MethodDispatcher): """Implementation of the Language Server Protocol 2.x for natural language. https://github.com/Microsoft/language-server-protocol/blob/master/versions/protocol-2-x.md # noqa 501 """ def __init__(self, rx, tx, check_parent_process=False): self.workspace = None self._jsonrpc_stream_reader = JsonRpcStreamReader(rx) self._jsonrpc_stream_writer = JsonRpcStreamWriter(tx) self._check_parent_process = check_parent_process self._endpoint = Endpoint(self, self._jsonrpc_stream_writer.write, max_workers=MAX_WORKERS) self._dispatchers = [] self._shutdown = False def start(self): """Blocking entry point for the server.""" self._jsonrpc_stream_reader.listen(self._endpoint.consume) def __getitem__(self, item): """Override getitem to check for shutdown.""" if self._shutdown and item != 'exit': # exit is the only allowed method during shutdown log.debug("Ignoring non-exit method during shutdown: %s", item) raise KeyError # MethodDispatcher super will find methods prefixed with "m_" return super(LanguageServer, self).__getitem__(item) def m_shutdown(self, **_kwargs): self._shutdown = True return None def m_exit(self, **_kwargs): self._endpoint.shutdown() self._jsonrpc_stream_reader.close() self._jsonrpc_stream_writer.close() def m_initialize(self, processId=None, rootUri=None, rootPath=None, initializationOptions=None, **_kwargs): log.debug('Language server initialized with %s %s %s %s', processId, rootUri, rootPath, initializationOptions) if rootUri is None: if rootPath is not None: rootUri = uris.from_fs_path(rootPath) else: rootUri = '' self.workspace = Workspace(rootUri, self._endpoint) lang_model.initialize() if self._check_parent_process and processId is not None: def watch_parent_process(pid): # exist when the given pid is not alive if not utils.is_process_alive(pid): log.info("parent process %s is not alive", pid) self.m_exit() log.debug("parent process %s is still alive", pid) threading.Timer(PARENT_PROCESS_WATCH_INTERVAL, watch_parent_process, args=[pid]).start() watching_thread = threading.Thread(target=watch_parent_process, args=(processId,)) watching_thread.daemon = True watching_thread.start() # Get our capabilities return {'capabilities': self.capabilities()} def m_initialized(self, **_kwargs): pass def m_text_document__did_close(self, textDocument=None, **_kwargs): self.workspace.rm_document(textDocument['uri']) def m_text_document__did_open(self, textDocument=None, **_kwargs): self.workspace.put_document(textDocument['uri'], textDocument['text'], version=textDocument.get('version')) def m_text_document__did_change(self, contentChanges=None, textDocument=None, **_kwargs): for change in contentChanges: self.workspace.update_document( textDocument['uri'], change, version=textDocument.get('version') ) def m_text_document__did_save(self, textDocument=None, **_kwargs): # TODO pass def m_text_document__completion(self, textDocument=None, position=None, **_kwargs): # If JSONRPC method handler returns a function, it will be invoked # asynchronously in a thread pool, which is desirable here to avoid # blocking other requests. # Workspace and Document are not thread-safe, so access to them must # happen outside of the handler function. doc = self.workspace.get_document(textDocument['uri']) # TODO magic number text = doc.read_before(position, 1024) last_word = doc.word_at_position(position) return partial(self.completions, text, last_word) def capabilities(self): server_capabilities = { 'completionProvider': { 'resolveProvider': False, 'triggerCharacters': [] }, 'textDocumentSync': constants.TextDocumentSyncKind.INCREMENTAL } log.info('Server capabilities: %s', server_capabilities) return server_capabilities @staticmethod def completions(text, last_word): complete_text = last_word + ' ' + lang_model.generate(text) completions = [{ 'label': complete_text, 'kind': constants.CompletionItemKind.Text }] return { 'isIncomplete': False, 'items': completions }
class PythonLanguageServer(MethodDispatcher): """ Implementation of the Microsoft VSCode Language Server Protocol https://github.com/Microsoft/language-server-protocol/blob/master/versions/protocol-1-x.md """ # pylint: disable=too-many-public-methods,redefined-builtin def __init__(self, rx, tx): self.workspace = None self.config = None self._jsonrpc_stream_reader = JsonRpcStreamReader(rx) self._jsonrpc_stream_writer = JsonRpcStreamWriter(tx) self._endpoint = Endpoint(self, self._jsonrpc_stream_writer.write) self._dispatchers = [] self._shutdown = False def start(self): """Entry point for the server.""" self._jsonrpc_stream_reader.listen(self._endpoint.consume) def __getitem__(self, item): """Override getitem to fallback through multiple dispatchers.""" if self._shutdown and item != 'exit': # exit is the only allowed method during shutdown log.debug("Ignoring non-exit method during shutdown: %s", item) raise KeyError try: return super(PythonLanguageServer, self).__getitem__(item) except KeyError: # Fallback through extra dispatchers for dispatcher in self._dispatchers: try: return dispatcher[item] except KeyError: continue raise KeyError() def m_shutdown(self, **_kwargs): self._shutdown = True return None def m_exit(self, **_kwargs): self._endpoint.shutdown() self._jsonrpc_stream_reader.close() self._jsonrpc_stream_writer.close() def _hook(self, hook_name, doc_uri=None, **kwargs): """Calls hook_name and returns a list of results from all registered handlers""" doc = self.workspace.get_document(doc_uri) if doc_uri else None hook_handlers = self.config.plugin_manager.subset_hook_caller( hook_name, self.config.disabled_plugins) return hook_handlers(config=self.config, workspace=self.workspace, document=doc, **kwargs) def capabilities(self): server_capabilities = { 'codeActionProvider': True, 'codeLensProvider': { 'resolveProvider': False, # We may need to make this configurable }, 'completionProvider': { 'resolveProvider': False, # We know everything ahead of time 'triggerCharacters': ['.'] }, 'documentFormattingProvider': True, 'documentHighlightProvider': True, 'documentRangeFormattingProvider': True, 'documentSymbolProvider': True, 'definitionProvider': True, 'executeCommandProvider': { 'commands': flatten(self._hook('pyls_commands')) }, 'hoverProvider': True, 'referencesProvider': True, 'renameProvider': True, 'signatureHelpProvider': { 'triggerCharacters': ['(', ','] }, 'textDocumentSync': lsp.TextDocumentSyncKind.INCREMENTAL, 'experimental': merge(self._hook('pyls_experimental_capabilities')) } log.info('Server capabilities: %s', server_capabilities) return server_capabilities def m_initialize(self, processId=None, rootUri=None, rootPath=None, initializationOptions=None, **_kwargs): log.debug('Language server initialized with %s %s %s %s', processId, rootUri, rootPath, initializationOptions) if rootUri is None: rootUri = uris.from_fs_path( rootPath) if rootPath is not None else '' self.workspace = Workspace(rootUri, self._endpoint) self.config = config.Config(rootUri, initializationOptions or {}) self._dispatchers = self._hook('pyls_dispatchers') self._hook('pyls_initialize') # Get our capabilities return {'capabilities': self.capabilities()} def m_initialized(self, **_kwargs): pass def code_actions(self, doc_uri, range, context): return flatten( self._hook('pyls_code_actions', doc_uri, range=range, context=context)) def code_lens(self, doc_uri): return flatten(self._hook('pyls_code_lens', doc_uri)) def completions(self, doc_uri, position): completions = self._hook('pyls_completions', doc_uri, position=position) return {'isIncomplete': False, 'items': flatten(completions)} def definitions(self, doc_uri, position): return flatten( self._hook('pyls_definitions', doc_uri, position=position)) def document_symbols(self, doc_uri): return flatten(self._hook('pyls_document_symbols', doc_uri)) def execute_command(self, command, arguments): return self._hook('pyls_execute_command', command=command, arguments=arguments) def format_document(self, doc_uri): return self._hook('pyls_format_document', doc_uri) def format_range(self, doc_uri, range): return self._hook('pyls_format_range', doc_uri, range=range) def highlight(self, doc_uri, position): return flatten( self._hook('pyls_document_highlight', doc_uri, position=position)) or None def hover(self, doc_uri, position): return self._hook('pyls_hover', doc_uri, position=position) or { 'contents': '' } @_utils.debounce(LINT_DEBOUNCE_S, keyed_by='doc_uri') def lint(self, doc_uri, on_change=True): # Since we're debounced, the document may no longer be open if doc_uri in self.workspace.documents: self.workspace.publish_diagnostics( doc_uri, flatten(self._hook('pyls_lint', doc_uri, on_change=on_change))) def references(self, doc_uri, position, exclude_declaration): return flatten( self._hook('pyls_references', doc_uri, position=position, exclude_declaration=exclude_declaration)) def rename(self, doc_uri, position, new_name): return self._hook('pyls_rename', doc_uri, position=position, new_name=new_name) def signature_help(self, doc_uri, position): return self._hook('pyls_signature_help', doc_uri, position=position) def m_text_document__did_close(self, textDocument=None, **_kwargs): self.workspace.rm_document(textDocument['uri']) def m_text_document__did_open(self, textDocument=None, **_kwargs): self.workspace.put_document(textDocument['uri'], textDocument['text'], version=textDocument.get('version')) self._hook('pyls_document_did_open', textDocument['uri']) self.lint(textDocument['uri'], on_change=False) def m_text_document__did_change(self, contentChanges=None, textDocument=None, **_kwargs): for change in contentChanges: self.workspace.update_document(textDocument['uri'], change, version=textDocument.get('version')) self.lint(textDocument['uri']) def m_text_document__did_save(self, textDocument=None, **_kwargs): self.lint(textDocument['uri'], on_change=False) def m_text_document__code_action(self, textDocument=None, range=None, context=None, **_kwargs): return self.code_actions(textDocument['uri'], range, context) def m_text_document__code_lens(self, textDocument=None, **_kwargs): return self.code_lens(textDocument['uri']) def m_text_document__completion(self, textDocument=None, position=None, **_kwargs): return self.completions(textDocument['uri'], position) def m_text_document__definition(self, textDocument=None, position=None, **_kwargs): return self.definitions(textDocument['uri'], position) def m_text_document__document_highlight(self, textDocument=None, position=None, **_kwargs): return self.highlight(textDocument['uri'], position) def m_text_document__hover(self, textDocument=None, position=None, **_kwargs): return self.hover(textDocument['uri'], position) def m_text_document__document_symbol(self, textDocument=None, **_kwargs): return self.document_symbols(textDocument['uri']) def m_text_document__formatting(self, textDocument=None, _options=None, **_kwargs): # For now we're ignoring formatting options. return self.format_document(textDocument['uri']) def m_text_document__rename(self, textDocument=None, position=None, newName=None, **_kwargs): return self.rename(textDocument['uri'], position, newName) def m_text_document__range_formatting(self, textDocument=None, range=None, _options=None, **_kwargs): # Again, we'll ignore formatting options for now. return self.format_range(textDocument['uri'], range) def m_text_document__references(self, textDocument=None, position=None, context=None, **_kwargs): exclude_declaration = not context['includeDeclaration'] return self.references(textDocument['uri'], position, exclude_declaration) def m_text_document__signature_help(self, textDocument=None, position=None, **_kwargs): return self.signature_help(textDocument['uri'], position) def m_workspace__did_change_configuration(self, settings=None): self.config.update((settings or {}).get('pyls', {})) for doc_uri in self.workspace.documents: self.lint(doc_uri) def m_workspace__did_change_watched_files(self, **_kwargs): # Externally changed files may result in changed diagnostics for doc_uri in self.workspace.documents: self.lint(doc_uri) def m_workspace__execute_command(self, command=None, arguments=None): return self.execute_command(command, arguments)
class Mdls(MethodDispatcher): def __init__(self, rx, tx): self._logger = get_logger_by_class(self.__class__) self._jsonrpc_stream_reader = JsonRpcStreamReader(rx) self._jsonrpc_stream_writer = JsonRpcStreamWriter(tx) self._endpoint = Endpoint(self, self._jsonrpc_stream_writer.write, max_workers=64) self._completion_providers = [ HeadingLinkProvider(), FootnoteLinkProvider(), ] self._client_capabilities = {} self._workspace = None self._shutdown = False def start(self): self._jsonrpc_stream_reader.listen(self._endpoint.consume) def _capabilities(self): return { 'completionProvider': { 'resolveProvider': False, 'triggerCharacters': [ c for p in self._completion_providers for c in p.get_trigger_characters() ], } } def m_initialize(self, processId=None, rootUri=None, rootPath=None, initializationOptions=None, capabilities=None, workspaceFolders=None, **kwargs): self._logger.debug('Received initialization request') if rootUri is None: rootUri = Path(rootPath).as_uri() if rootPath is not None else '' self._logger.debug('Using rootUri %s', rootUri) self.workspace = Workspace(rootUri) self._client_capabilities = capabilities server_capabilities = self._capabilities() self._logger.debug('Returning capabilities:\n%s', server_capabilities) return {'capabilities': server_capabilities} def m_initialized(self, **_kwargs): pass def m_shutdown(self, **_kwargs): self._shutdown = True return None def m_exit(self, **_kwargs): self._endpoint.shutdown() self._jsonrpc_stream_reader.close() self._jsonrpc_stream_writer.close() def m_text_document__did_open(self, textDocument=None, **_kwargs): self.workspace.put_document( Document(textDocument['uri'], textDocument['text'], version=textDocument.get('version'))) def m_text_document__did_change(self, contentChanges=None, textDocument=None, **_kwargs): for change in contentChanges: self._logger.debug('Applying change %s to document with URI %s', change, textDocument['uri']) document = self.workspace.get_document(textDocument['uri']) self._logger.debug('Contents before change:\n%s', document.text) document.update(change, version=textDocument.get('version')) self._logger.debug('Contents after change:\n%s', document.text) def m_text_document__did_close(self, textDocument=None, **_kwargs): self.workspace.remove_document(textDocument['uri']) def m_text_document__did_save(self, textDocument=None, **_kwargs): pass def m_text_document__completion(self, textDocument=None, position=None, **_kwargs): document = self.workspace.get_document(textDocument['uri']) completions = [] for provider in self._completion_providers: completions.extend(provider.provide(document, position)) self._logger.debug('Collected completions:\n%s', completions) return { 'isIncomplete': False, 'items': completions, }