Example #1
0
class LanguageServerProtocol(JsonRPCProtocol, metaclass=LSPMeta):
    """A class that represents language server protocol.

    It contains implementations for generic LSP features.

    Attributes:
        workspace(Workspace): In memory workspace
    """

    def __init__(self, server):
        super().__init__(server)

        self.workspace = None
        self.trace = None

        from pygls.progress import Progress
        self.progress = Progress(self)

        self._register_builtin_features()

    def _register_builtin_features(self):
        """Registers generic LSP features from this class."""
        for name in dir(self):
            attr = getattr(self, name)
            if callable(attr) and hasattr(attr, 'method_name'):
                self.fm.add_builtin_feature(attr.method_name, attr)

    def apply_edit(self, edit: WorkspaceEdit, label: str = None) -> ApplyWorkspaceEditResponse:
        """Sends apply edit request to the client."""
        return self.send_request(WORKSPACE_APPLY_EDIT,
                                 ApplyWorkspaceEditParams(edit=edit, label=label))

    @lsp_method(EXIT)
    def lsp_exit(self, *args) -> None:
        """Stops the server process."""
        self.transport.close()
        sys.exit(0 if self._shutdown else 1)

    @lsp_method(INITIALIZE)
    def lsp_initialize(self, params: InitializeParams) -> InitializeResult:
        """Method that initializes language server.
        It will compute and return server capabilities based on
        registered features.
        """
        logger.info('Language server initialized %s', params)

        self._server.process_id = params.process_id

        # Initialize server capabilities
        self.client_capabilities = params.capabilities
        self.server_capabilities = ServerCapabilitiesBuilder(
            self.client_capabilities,
            {**self.fm.features, **self.fm.builtin_features}.keys(),
            self.fm.feature_options,
            list(self.fm.commands.keys()),
            self._server.sync_kind,
        ).build()
        logger.debug('Server capabilities: %s', self.server_capabilities.dict())

        root_path = params.root_path
        root_uri = params.root_uri or from_fs_path(root_path)

        # Initialize the workspace
        workspace_folders = params.workspace_folders or []
        self.workspace = Workspace(root_uri, self._server.sync_kind, workspace_folders)

        self.trace = Trace.Off

        return InitializeResult(capabilities=self.server_capabilities)

    @lsp_method(INITIALIZED)
    def lsp_initialized(self, *args) -> None:
        """Notification received when client and server are connected."""
        pass

    @lsp_method(SHUTDOWN)
    def lsp_shutdown(self, *args) -> None:
        """Request from client which asks server to shutdown."""
        for future in self._client_request_futures.values():
            future.cancel()

        for future in self._server_request_futures.values():
            future.cancel()

        self._shutdown = True
        return None

    @lsp_method(TEXT_DOCUMENT_DID_CHANGE)
    def lsp_text_document__did_change(self, params: DidChangeTextDocumentParams) -> None:
        """Updates document's content.
        (Incremental(from server capabilities); not configurable for now)
        """
        for change in params.content_changes:
            self.workspace.update_document(params.text_document, change)

    @lsp_method(TEXT_DOCUMENT_DID_CLOSE)
    def lsp_text_document__did_close(self, params: DidCloseTextDocumentParams) -> None:
        """Removes document from workspace."""
        self.workspace.remove_document(params.text_document.uri)

    @lsp_method(TEXT_DOCUMENT_DID_OPEN)
    def lsp_text_document__did_open(self, params: DidOpenTextDocumentParams) -> None:
        """Puts document to the workspace."""
        self.workspace.put_document(params.text_document)

    @lsp_method(SET_TRACE_NOTIFICATION)
    def lsp_set_trace(self, params: SetTraceParams) -> None:
        """Changes server trace value."""
        self.trace = params.value

    @lsp_method(WORKSPACE_DID_CHANGE_WORKSPACE_FOLDERS)
    def lsp_workspace__did_change_workspace_folders(
            self, params: DidChangeWorkspaceFoldersParams) -> None:
        """Adds/Removes folders from the workspace."""
        logger.info('Workspace folders changed: %s', params)

        added_folders = params.event.added or []
        removed_folders = params.event.removed or []

        for f_add, f_remove in zip_longest(added_folders, removed_folders):
            if f_add:
                self.workspace.add_folder(f_add)
            if f_remove:
                self.workspace.remove_folder(f_remove.uri)

    @lsp_method(WORKSPACE_EXECUTE_COMMAND)
    def lsp_workspace__execute_command(self, params: ExecuteCommandParams, msg_id: str) -> None:
        """Executes commands with passed arguments and returns a value."""
        cmd_handler = self.fm.commands[params.command]
        self._execute_request(msg_id, cmd_handler, params.arguments)

    def get_configuration(self, params: ConfigurationParams,
                          callback: Optional[ConfigCallbackType] = None) -> Future:
        """Sends configuration request to the client.

        Args:
            params(ConfigurationParams): ConfigurationParams from lsp specs
            callback(callable): Callabe which will be called after
                                response from the client is received
        Returns:
            concurrent.futures.Future object that will be resolved once a
            response has been received
        """
        return self.send_request(WORKSPACE_CONFIGURATION, params, callback)

    def get_configuration_async(self, params: ConfigurationParams) -> asyncio.Future:
        """Calls `get_configuration` method but designed to use with coroutines

        Args:
            params(ConfigurationParams): ConfigurationParams from lsp specs
        Returns:
            asyncio.Future that can be awaited
        """
        return asyncio.wrap_future(self.get_configuration(params))

    def log_trace(self, message: str, verbose: Optional[str] = None) -> None:
        """Sends trace notification to the client."""
        if self.trace == Trace.Off:
            return

        params = LogTraceParams(message=message)
        if verbose and self.trace == Trace.Verbose:
            params.verbose = verbose

        self.notify(LOG_TRACE_NOTIFICATION, params)

    def publish_diagnostics(self, doc_uri: str, diagnostics: List[Diagnostic]) -> None:
        """Sends diagnostic notification to the client."""
        self.notify(TEXT_DOCUMENT_PUBLISH_DIAGNOSTICS,
                    PublishDiagnosticsParams(uri=doc_uri, diagnostics=diagnostics))

    def register_capability(self, params: RegistrationParams,
                            callback: Optional[Callable[[], None]] = None) -> Future:
        """Register a new capability on the client.

        Args:
            params(RegistrationParams): RegistrationParams from lsp specs
            callback(callable): Callabe which will be called after
                                response from the client is received
        Returns:
            concurrent.futures.Future object that will be resolved once a
            response has been received
        """
        return self.send_request(CLIENT_REGISTER_CAPABILITY, params, callback)

    def register_capability_async(self, params: RegistrationParams) -> asyncio.Future:
        """Register a new capability on the client.

        Args:
            params(RegistrationParams): RegistrationParams from lsp specs

        Returns:
            asyncio.Future object that will be resolved once a
            response has been received
        """
        return asyncio.wrap_future(self.register_capability(params, None))

    def semantic_tokens_refresh(self, callback: Optional[Callable[[], None]] = None) -> Future:
        """Requesting a refresh of all semantic tokens.

        Args:
            callback(callable): Callabe which will be called after
                                response from the client is received

        Returns:
            concurrent.futures.Future object that will be resolved once a
            response has been received
        """
        return self.send_request(WORKSPACE_SEMANTIC_TOKENS_REFRESH, callback=callback)

    def semantic_tokens_refresh_async(self) -> asyncio.Future:
        """Requesting a refresh of all semantic tokens.

        Returns:
            asyncio.Future object that will be resolved once a
            response has been received
        """
        return asyncio.wrap_future(self.semantic_tokens_refresh(None))

    def show_document(self, params: ShowDocumentParams,
                      callback: Optional[ShowDocumentCallbackType] = None) -> Future:
        """Display a particular document in the user interface.

        Args:
            params(ShowDocumentParams): ShowDocumentParams from lsp specs
            callback(callable): Callabe which will be called after
                                response from the client is received

        Returns:
            concurrent.futures.Future object that will be resolved once a
            response has been received
        """
        return self.send_request(WINDOW_SHOW_DOCUMENT, params, callback)

    def show_document_async(self, params: ShowDocumentParams) -> asyncio.Future:
        """Display a particular document in the user interface.

        Args:
            params(ShowDocumentParams): ShowDocumentParams from lsp specs

        Returns:
            asyncio.Future object that will be resolved once a
            response has been received
        """
        return asyncio.wrap_future(self.show_document(params, None))

    def show_message(self, message, msg_type=MessageType.Info):
        """Sends message to the client to display message."""
        self.notify(WINDOW_SHOW_MESSAGE, ShowMessageParams(type=msg_type, message=message))

    def show_message_log(self, message, msg_type=MessageType.Log):
        """Sends message to the client's output channel."""
        self.notify(WINDOW_LOG_MESSAGE, LogMessageParams(type=msg_type, message=message))

    def unregister_capability(self, params: UnregistrationParams,
                              callback: Optional[Callable[[], None]] = None) -> Future:
        """Unregister a new capability on the client.

        Args:
            params(UnregistrationParams): UnregistrationParams from lsp specs
            callback(callable): Callabe which will be called after
                                response from the client is received
        Returns:
            concurrent.futures.Future object that will be resolved once a
            response has been received
        """
        return self.send_request(CLIENT_UNREGISTER_CAPABILITY, params, callback)

    def unregister_capability_async(self, params: UnregistrationParams) -> asyncio.Future:
        """Unregister a new capability on the client.

        Args:
            params(UnregistrationParams): UnregistrationParams from lsp specs
            callback(callable): Callabe which will be called after
                                response from the client is received
        Returns:
            asyncio.Future object that will be resolved once a
            response has been received
        """
        return asyncio.wrap_future(self.unregister_capability(params, None))
Example #2
0
class LanguageServerProtocol(JsonRPCProtocol, metaclass=LSPMeta):
    """A class that represents language server protocol.

    It contains implementations for generic LSP features.

    Attributes:
        workspace(Workspace): In memory workspace
    """
    def __init__(self, server):
        super().__init__(server)

        self.workspace = None

        self._register_builtin_features()

    def _register_builtin_features(self):
        """Registers generic LSP features from this class."""
        for name in dir(self):
            attr = getattr(self, name)
            if callable(attr) and name.startswith('bf_'):
                lsp_name = to_lsp_name(name[3:])
                self.fm.add_builtin_feature(lsp_name, attr)

    def apply_edit(self, edit: WorkspaceEdit, label: str = None) -> \
            ApplyWorkspaceEditResponse:
        """Sends apply edit request to the client."""
        return self.send_request(
            WORKSPACE_APPLY_EDIT,
            ApplyWorkspaceEditParams(edit=edit, label=label))

    def bf_exit(self, *args):
        """Stops the server process."""
        self.transport.close()
        sys.exit(0 if self._shutdown else 1)

    def bf_initialize(self, params: InitializeParams):
        """Method that initializes language server.
        It will compute and return server capabilities based on
        registered features.
        """
        logger.info('Language server initialized %s', params)

        self._server.process_id = params.process_id

        # Initialize server capabilities
        self.client_capabilities = params.capabilities
        self.server_capabilities = ServerCapabilitiesBuilder(
            self.client_capabilities,
            self.fm.features.keys(),
            self.fm.feature_options,
            list(self.fm.commands.keys()),
            self._server.sync_kind,
        ).build()
        logger.debug('Server capabilities: %s',
                     self.server_capabilities.dict())

        root_path = params.root_path
        root_uri = params.root_uri or from_fs_path(root_path)

        # Initialize the workspace
        workspace_folders = params.workspace_folders or []
        self.workspace = Workspace(root_uri, self._server.sync_kind,
                                   workspace_folders)

        return InitializeResult(capabilities=self.server_capabilities)

    def bf_initialized(self, *args):
        """Notification received when client and server are connected."""
        pass

    def bf_shutdown(self, *args):
        """Request from client which asks server to shutdown."""
        for future in self._client_request_futures.values():
            future.cancel()

        for future in self._server_request_futures.values():
            future.cancel()

        self._shutdown = True
        return None

    def bf_text_document__did_change(self,
                                     params: DidChangeTextDocumentParams):
        """Updates document's content.
        (Incremental(from server capabilities); not configurable for now)
        """
        for change in params.content_changes:
            self.workspace.update_document(params.text_document, change)

    def bf_text_document__did_close(self, params: DidCloseTextDocumentParams):
        """Removes document from workspace."""
        self.workspace.remove_document(params.text_document.uri)

    def bf_text_document__did_open(self, params: DidOpenTextDocumentParams):
        """Puts document to the workspace."""
        self.workspace.put_document(params.text_document)

    def bf_workspace__did_change_workspace_folders(
            self, params: DidChangeWorkspaceFoldersParams):
        """Adds/Removes folders from the workspace."""
        logger.info('Workspace folders changed: %s', params)

        added_folders = params.event.added or []
        removed_folders = params.event.removed or []

        for f_add, f_remove in zip_longest(added_folders, removed_folders):
            if f_add:
                self.workspace.add_folder(f_add)
            if f_remove:
                self.workspace.remove_folder(f_remove.uri)

    def bf_workspace__execute_command(self, params: ExecuteCommandParams,
                                      msg_id):
        """Executes commands with passed arguments and returns a value."""
        cmd_handler = self.fm.commands[params.command]
        self._execute_request(msg_id, cmd_handler, params.arguments)

    def get_configuration(self, params, callback):
        """Sends configuration request to the client.

        Args:
            params(dict): ConfigurationParams from lsp specs
            callback(callable): Callabe which will be called after
                                response from the client is received
        Returns:
            concurrent.futures.Future object that will be resolved once a
            response has been received
        """
        return self.send_request(WORKSPACE_CONFIGURATION, params, callback)

    def get_configuration_async(self, params):
        """Calls `get_configuration` method but designed to use with coroutines

        Args:
            params(dict): ConfigurationParams from lsp specs
        Returns:
            asyncio.Future that can be awaited
        """
        return asyncio.wrap_future(self.get_configuration(params, None))

    def publish_diagnostics(self, doc_uri: str, diagnostics: List[Diagnostic]):
        """Sends diagnostic notification to the client."""
        self.notify(
            TEXT_DOCUMENT_PUBLISH_DIAGNOSTICS,
            PublishDiagnosticsParams(uri=doc_uri, diagnostics=diagnostics))

    def register_capability(self, params: RegistrationParams, callback):
        """Register a new capability on the client.

        Args:
            params(RegistrationParams): RegistrationParams from lsp specs
            callback(callable): Callabe which will be called after
                                response from the client is received
        Returns:
            concurrent.futures.Future object that will be resolved once a
            response has been received
        """
        return self.send_request(CLIENT_REGISTER_CAPABILITY, params, callback)

    def register_capability_async(self, params: RegistrationParams):
        """Register a new capability on the client.

        Args:
            params(RegistrationParams): RegistrationParams from lsp specs
            callback(callable): Callabe which will be called after
                                response from the client is received
        Returns:
            asyncio.Future object that will be resolved once a
            response has been received
        """
        return asyncio.wrap_future(self.register_capability(params, None))

    def show_message(self, message, msg_type=MessageType.Info):
        """Sends message to the client to display message."""
        self.notify(WINDOW_SHOW_MESSAGE,
                    ShowMessageParams(type=msg_type, message=message))

    def show_message_log(self, message, msg_type=MessageType.Log):
        """Sends message to the client's output channel."""
        self.notify(WINDOW_LOG_MESSAGE,
                    LogMessageParams(type=msg_type, message=message))

    def unregister_capability(self, params: UnregistrationParams, callback):
        """Unregister a new capability on the client.

        Args:
            params(UnregistrationParams): UnregistrationParams from lsp specs
            callback(callable): Callabe which will be called after
                                response from the client is received
        Returns:
            concurrent.futures.Future object that will be resolved once a
            response has been received
        """
        return self.send_request(CLIENT_UNREGISTER_CAPABILITY, params,
                                 callback)

    def unregister_capability_async(self, params: UnregistrationParams):
        """Unregister a new capability on the client.

        Args:
            params(UnregistrationParams): UnregistrationParams from lsp specs
            callback(callable): Callabe which will be called after
                                response from the client is received
        Returns:
            asyncio.Future object that will be resolved once a
            response has been received
        """
        return asyncio.wrap_future(self.unregister_capability(params, None))