From 9fde8525cb53e7d2a9c779148d02fdd67e7bf46a Mon Sep 17 00:00:00 2001 From: Alex Carney Date: Thu, 7 Mar 2024 21:14:48 +0000 Subject: [PATCH 1/8] code: Add `esbonio.server.documentSelector` option --- code/changes/756.breaking.md | 1 + code/changes/756.enhancement.md | 1 + code/package.json | 25 +++++++++++++++++++++---- code/src/node/client.ts | 13 +++++++------ 4 files changed, 30 insertions(+), 10 deletions(-) create mode 100644 code/changes/756.breaking.md create mode 100644 code/changes/756.enhancement.md diff --git a/code/changes/756.breaking.md b/code/changes/756.breaking.md new file mode 100644 index 000000000..e24b6aae1 --- /dev/null +++ b/code/changes/756.breaking.md @@ -0,0 +1 @@ +The `esbonio.server.enabledInPyFiles` configuration option has been removed, use `esbonio.server.documentSelector` instead diff --git a/code/changes/756.enhancement.md b/code/changes/756.enhancement.md new file mode 100644 index 000000000..5db22a0a2 --- /dev/null +++ b/code/changes/756.enhancement.md @@ -0,0 +1 @@ +Added the `esbonio.server.documentSelector` option, granting the user fine grained control over which files the server is enabled in. diff --git a/code/package.json b/code/package.json index b66e29cc3..37aa3773f 100644 --- a/code/package.json +++ b/code/package.json @@ -88,11 +88,28 @@ "default": true, "description": "Enable/Disable the language server" }, - "esbonio.server.enabledInPyFiles": { + "esbonio.server.documentSelector": { "scope": "window", - "type": "boolean", - "default": true, - "description": "Enable/Disable the language server in Python files." + "type": "array", + "items": { + "type": "object", + "properties": { + "scheme": { + "type": "string", + "description": "The URI scheme that this selector applies to" + }, + "language": { + "type": "string", + "description": "The language id this selector will select" + }, + "pattern": { + "type": "string", + "description": "Only select uris that match the given pattern" + } + } + }, + "default": [], + "description": "Override the extension's default document selector" }, "esbonio.server.startupModule": { "scope": "window", diff --git a/code/src/node/client.ts b/code/src/node/client.ts index 2384a3f33..effc30a07 100644 --- a/code/src/node/client.ts +++ b/code/src/node/client.ts @@ -9,7 +9,8 @@ import { LanguageClientOptions, ResponseError, ServerOptions, - State + State, + TextDocumentFilter } from "vscode-languageclient/node"; import { OutputChannelLogger } from "../common/log"; @@ -258,15 +259,15 @@ export class EsbonioClient { */ private getLanguageClientOptions(config: vscode.WorkspaceConfiguration): LanguageClientOptions { - let documentSelector = [ + let defaultDocumentSelector: TextDocumentFilter[] = [ { scheme: 'file', language: 'restructuredtext' }, { scheme: 'file', language: 'markdown' }, + // { scheme: 'file', language: 'python' } ] - if (config.get('server.enabledInPyFiles')) { - documentSelector.push( - { scheme: 'file', language: 'python' } - ) + let documentSelector = config.get("server.documentSelector") + if (!documentSelector || documentSelector.length === 0) { + documentSelector = defaultDocumentSelector } let clientOptions: LanguageClientOptions = { From 68fbce127c84467d99226876c253c0dfc272092c Mon Sep 17 00:00:00 2001 From: Alex Carney Date: Thu, 14 Mar 2024 18:52:05 +0000 Subject: [PATCH 2/8] lsp: Fix traceback formatting --- lib/esbonio/esbonio/server/_configuration.py | 2 +- lib/esbonio/esbonio/server/events.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/esbonio/esbonio/server/_configuration.py b/lib/esbonio/esbonio/server/_configuration.py index c936206c0..fb6ed333a 100644 --- a/lib/esbonio/esbonio/server/_configuration.py +++ b/lib/esbonio/esbonio/server/_configuration.py @@ -233,7 +233,7 @@ def _finish_task(self, subscription: Subscription, task: asyncio.Task[None]): self.logger.error( "Error in async configuration handler '%s'\n%s", subscription.callback, - traceback.format_exception(type(exc), exc, exc.__traceback__), + "".join(traceback.format_exception(type(exc), exc, exc.__traceback__)), ) def get(self, section: str, spec: Type[T], scope: Optional[Uri] = None) -> T: diff --git a/lib/esbonio/esbonio/server/events.py b/lib/esbonio/esbonio/server/events.py index 2e5944db6..d44e16334 100644 --- a/lib/esbonio/esbonio/server/events.py +++ b/lib/esbonio/esbonio/server/events.py @@ -44,7 +44,7 @@ def _finish_task(self, event: str, listener_name: str, task: asyncio.Task[Any]): "Error in '%s' async handler '%s'\n%s", event, listener_name, - traceback.format_exception(type(exc), exc, exc.__traceback__), + "".join(traceback.format_exception(type(exc), exc, exc.__traceback__)), ) def trigger(self, event: str, *args, **kwargs): From 5121119bc9f39280e775b8f43ec06292f3c38298 Mon Sep 17 00:00:00 2001 From: Alex Carney Date: Thu, 14 Mar 2024 18:53:33 +0000 Subject: [PATCH 3/8] lsp: Have the sphinx client, rather than the agent pick the id --- .../features/sphinx_manager/client_subprocess.py | 12 ++++-------- .../esbonio/sphinx_agent/handlers/__init__.py | 2 -- lib/esbonio/esbonio/sphinx_agent/types.py | 3 --- 3 files changed, 4 insertions(+), 13 deletions(-) diff --git a/lib/esbonio/esbonio/server/features/sphinx_manager/client_subprocess.py b/lib/esbonio/esbonio/server/features/sphinx_manager/client_subprocess.py index 6a8a4dd76..5d3aa9d69 100644 --- a/lib/esbonio/esbonio/server/features/sphinx_manager/client_subprocess.py +++ b/lib/esbonio/esbonio/server/features/sphinx_manager/client_subprocess.py @@ -9,6 +9,7 @@ import subprocess import sys import typing +from uuid import uuid4 from pygls.client import JsonRPCClient from pygls.protocol import JsonRPCProtocol @@ -60,6 +61,9 @@ def __init__( ): super().__init__(protocol_cls=protocol_cls, *args, **kwargs) # type: ignore[misc] + self.id = str(uuid4()) + """The client's id.""" + self.config = config """Configuration values.""" @@ -103,14 +107,6 @@ def __await__(self): def converter(self): return self.protocol._converter - @property - def id(self) -> str: - """The id of the Sphinx instance.""" - if self.sphinx_info is None: - raise RuntimeError("sphinx_info is None, has the client been started?") - - return self.sphinx_info.id - @property def builder(self) -> str: """The sphinx application's builder name""" diff --git a/lib/esbonio/esbonio/sphinx_agent/handlers/__init__.py b/lib/esbonio/esbonio/sphinx_agent/handlers/__init__.py index 63d8000b5..08b78abe0 100644 --- a/lib/esbonio/esbonio/sphinx_agent/handlers/__init__.py +++ b/lib/esbonio/esbonio/sphinx_agent/handlers/__init__.py @@ -13,7 +13,6 @@ from typing import Optional from typing import Tuple from typing import Type -from uuid import uuid4 import sphinx.application from sphinx import __version__ as __sphinx_version__ @@ -138,7 +137,6 @@ def create_sphinx_app(self, request: types.CreateApplicationRequest): response = types.CreateApplicationResponse( id=request.id, result=types.SphinxInfo( - id=str(uuid4()), version=__sphinx_version__, conf_dir=str(self.app.confdir), build_dir=str(self.app.outdir), diff --git a/lib/esbonio/esbonio/sphinx_agent/types.py b/lib/esbonio/esbonio/sphinx_agent/types.py index 0ac7fd252..ac313ac00 100644 --- a/lib/esbonio/esbonio/sphinx_agent/types.py +++ b/lib/esbonio/esbonio/sphinx_agent/types.py @@ -534,9 +534,6 @@ class CreateApplicationRequest: class SphinxInfo: """Represents information about an instance of the Sphinx application.""" - id: str - """A unique id representing a particular Sphinx application instance.""" - version: str """The version of Sphinx being used.""" From f306327d4ce39a744cbb05e239059fc030a2fe6a Mon Sep 17 00:00:00 2001 From: Alex Carney Date: Thu, 14 Mar 2024 18:55:21 +0000 Subject: [PATCH 4/8] lsp: Send additional sphinx lifecycle notifications In addition to the `sphinx/appCreated` notification, the server will now send, `sphinx/clientCreated`, `sphinx/clientErrored` and `sphinx/clientDestroyed` notifications. --- lib/esbonio/changes/756.enhancement.md | 1 + .../sphinx_manager/client_subprocess.py | 2 + .../server/features/sphinx_manager/manager.py | 78 ++++++++++++++++++- 3 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 lib/esbonio/changes/756.enhancement.md diff --git a/lib/esbonio/changes/756.enhancement.md b/lib/esbonio/changes/756.enhancement.md new file mode 100644 index 000000000..46cc83076 --- /dev/null +++ b/lib/esbonio/changes/756.enhancement.md @@ -0,0 +1 @@ +The server now emits `sphinx/clientCreated`, `sphinx/clientErrored` and `sphinx/clientDestroyed` notifications that correspond to the lifecycle of the underlying Sphinx process diff --git a/lib/esbonio/esbonio/server/features/sphinx_manager/client_subprocess.py b/lib/esbonio/esbonio/server/features/sphinx_manager/client_subprocess.py index 5d3aa9d69..fe2a6e082 100644 --- a/lib/esbonio/esbonio/server/features/sphinx_manager/client_subprocess.py +++ b/lib/esbonio/esbonio/server/features/sphinx_manager/client_subprocess.py @@ -214,6 +214,8 @@ async def start(self) -> SphinxClient: def _set_state(self, new_state: ClientState): """Change the state of the client.""" old_state, self.state = self.state, new_state + + self.logger.debug("SphinxClient[%s]: %s -> %s", self.id, old_state, new_state) self._events.trigger("state-change", self, old_state, new_state) async def stop(self): diff --git a/lib/esbonio/esbonio/server/features/sphinx_manager/manager.py b/lib/esbonio/esbonio/server/features/sphinx_manager/manager.py index f01a3a090..579320b18 100644 --- a/lib/esbonio/esbonio/server/features/sphinx_manager/manager.py +++ b/lib/esbonio/esbonio/server/features/sphinx_manager/manager.py @@ -1,10 +1,12 @@ from __future__ import annotations import asyncio +import traceback import typing import uuid from functools import partial +import attrs import lsprotocol.types as lsp from esbonio import server @@ -26,6 +28,53 @@ SphinxClientFactory = Callable[["SphinxManager", "SphinxConfig"], "SphinxClient"] +@attrs.define +class ClientCreatedNotification: + """The payload of a ``sphinx/clientCreated`` notification""" + + id: str + """The client's id""" + + scope: str + """The scope at which the client was created.""" + + config: SphinxConfig + """The final configuration.""" + + +@attrs.define +class AppCreatedNotification: + """The payload of a ``sphinx/appCreated`` notification""" + + id: str + """The client's id""" + + application: types.SphinxInfo + """Details about the created application.""" + + +@attrs.define +class ClientErroredNotification: + """The payload of a ``sphinx/clientErrored`` notification""" + + id: str + """The client's id""" + + error: str + """Short description of the error.""" + + detail: str + """Detailed description of the error.""" + + +@attrs.define +class ClientDestroyedNotification: + """The payload of ``sphinx/clientDestroyed`` notification.""" + + id: str + """The client's id""" + + class SphinxManager(server.LanguageFeature): """Responsible for managing Sphinx application instances.""" @@ -188,6 +237,10 @@ async def _create_or_replace_client( # If there was a previous client, stop it. if (previous_client := self.clients.pop(event.scope, None)) is not None: + self.server.lsp.notify( + "sphinx/clientDestroyed", + ClientDestroyedNotification(id=previous_client.id), + ) self.server.run_task(previous_client.stop()) resolved = config.resolve( @@ -200,7 +253,10 @@ async def _create_or_replace_client( self.clients[event.scope] = client = self.client_factory(self, resolved) client.add_listener("state-change", partial(self._on_state_change, event.scope)) - self.server.lsp.notify("sphinx/clientCreated", resolved) + self.server.lsp.notify( + "sphinx/clientCreated", + ClientCreatedNotification(id=client.id, scope=event.scope, config=resolved), + ) self.logger.debug("Client created for scope %s", event.scope) # Start the client @@ -218,7 +274,25 @@ def _on_state_change( if old_state == ClientState.Starting and new_state == ClientState.Running: if (sphinx_info := client.sphinx_info) is not None: self.project_manager.register_project(scope, client.db) - self.server.lsp.notify("sphinx/appCreated", sphinx_info) + self.server.lsp.notify( + "sphinx/appCreated", + AppCreatedNotification(id=client.id, application=sphinx_info), + ) + + if new_state == ClientState.Errored: + error = "" + detail = "" + + if (exc := getattr(client, "exception", None)) is not None: + error = f"{type(exc).__name__}: {exc}" + detail = "".join( + traceback.format_exception(type(exc), exc, exc.__traceback__) + ) + + self.server.lsp.notify( + "sphinx/clientErrored", + ClientErroredNotification(id=client.id, error=error, detail=detail), + ) async def start_progress(self, client: SphinxClient): """Start reporting work done progress for the given client.""" From c08d2a9049076b48098061bbbb6f08bee16d1bb6 Mon Sep 17 00:00:00 2001 From: Alex Carney Date: Thu, 14 Mar 2024 18:59:24 +0000 Subject: [PATCH 5/8] code: Reimplement language status items We now have just two status items - One for showing the current `pythonCommand` for the project. It also allows the user to trigger the `Python: Select Interpreter` command. - One for showing the current (sphinx) `buildCommand`, the item also updates according to the current state of the client. --- code/src/common/constants.ts | 9 +++ code/src/node/client.ts | 91 ++++++++++++++++++--- code/src/node/status.ts | 148 ++++++++++++++++++++++++----------- 3 files changed, 194 insertions(+), 54 deletions(-) diff --git a/code/src/common/constants.ts b/code/src/common/constants.ts index 21b39364d..395c36d41 100644 --- a/code/src/common/constants.ts +++ b/code/src/common/constants.ts @@ -1,5 +1,11 @@ export namespace Server { export const REQUIRED_PYTHON = "3.8.0" + + export const DEFAULT_SELECTOR = [ + { scheme: 'file', language: 'restructuredtext' }, + { scheme: 'file', language: 'markdown' }, + // { scheme: 'file', language: 'python' } + ] } export namespace Commands { @@ -27,5 +33,8 @@ export namespace Notifications { export const SCROLL_EDITOR = "editor/scroll" export const VIEW_SCROLL = "view/scroll" + export const SPHINX_CLIENT_CREATED = "sphinx/clientCreated" + export const SPHINX_CLIENT_ERRORED = "sphinx/clientErrored" + export const SPHINX_CLIENT_DESTROYED = "sphinx/clientDestroyed" export const SPHINX_APP_CREATED = "sphinx/appCreated" } diff --git a/code/src/node/client.ts b/code/src/node/client.ts index effc30a07..b072ace50 100644 --- a/code/src/node/client.ts +++ b/code/src/node/client.ts @@ -15,16 +15,78 @@ import { import { OutputChannelLogger } from "../common/log"; import { PythonManager } from "./python"; -import { Commands, Events, Notifications } from '../common/constants'; +import { Commands, Events, Notifications, Server } from '../common/constants'; -export interface SphinxInfo { +export interface SphinxClientConfig { + + /** + * The python command used to launch the client + */ + pythonCommand: string[] + + /** + * The sphinx-build command in use + */ + buildCommand: string[] + + /** + * The working directory of the client + */ + cwd: string + +} + +export interface ClientCreatedNotification { + /** + * A unique id for this client + */ + id: string /** - * A unique id used to refer to this Sphinx application instance. + * The configuration scope at which the client was created + */ + scope: string + + /** + * The final configuration + */ + config: SphinxClientConfig + +} + +/** + * The payload of a ``sphinx/clientErrored`` notification + */ +export interface ClientErroredNotification { + + /** + * A unique id for the client */ id: string + /** + * Short description of the error. + */ + error: string + + /** + * Detailed description of the error. + */ + detail: string +} + + +export interface ClientDestroyedNotification { + /** + * A unique id for this client + */ + id: string +} + +export interface SphinxInfo { + + /** * Sphinx's version number */ @@ -53,6 +115,18 @@ export interface SphinxInfo { src_dir: string } +export interface AppCreatedNotification { + + /** + * A unique id for this client + */ + id: string + + /** + * Details about the created application. + */ + application: SphinxInfo +} export class EsbonioClient { @@ -244,6 +318,9 @@ export class EsbonioClient { let methods = [ Notifications.SCROLL_EDITOR, Notifications.SPHINX_APP_CREATED, + Notifications.SPHINX_CLIENT_CREATED, + Notifications.SPHINX_CLIENT_ERRORED, + Notifications.SPHINX_CLIENT_DESTROYED, ] for (let method of methods) { @@ -259,15 +336,11 @@ export class EsbonioClient { */ private getLanguageClientOptions(config: vscode.WorkspaceConfiguration): LanguageClientOptions { - let defaultDocumentSelector: TextDocumentFilter[] = [ - { scheme: 'file', language: 'restructuredtext' }, - { scheme: 'file', language: 'markdown' }, - // { scheme: 'file', language: 'python' } - ] + let documentSelector = config.get("server.documentSelector") if (!documentSelector || documentSelector.length === 0) { - documentSelector = defaultDocumentSelector + documentSelector = Server.DEFAULT_SELECTOR } let clientOptions: LanguageClientOptions = { diff --git a/code/src/node/status.ts b/code/src/node/status.ts index 72483fc17..2bad5672f 100644 --- a/code/src/node/status.ts +++ b/code/src/node/status.ts @@ -1,9 +1,16 @@ -import * as vscode from 'vscode'; import * as path from 'path'; +import * as vscode from 'vscode'; +import { TextDocumentFilter } from 'vscode-languageclient'; +import { Events, Notifications, Server } from '../common/constants'; import { OutputChannelLogger } from "../common/log"; -import { EsbonioClient, SphinxInfo } from './client'; -import { Events, Notifications } from '../common/constants'; +import { + AppCreatedNotification, + ClientCreatedNotification, + ClientDestroyedNotification, + ClientErroredNotification, + EsbonioClient +} from './client'; interface StatusItemFields { busy?: boolean @@ -31,35 +38,107 @@ export class StatusManager { client.addHandler( Notifications.SPHINX_APP_CREATED, - (params: SphinxInfo) => { this.createApp(params) } + (params: AppCreatedNotification) => { this.appCreated(params) } + ) + + client.addHandler( + Notifications.SPHINX_CLIENT_CREATED, + (params: ClientCreatedNotification) => { this.clientCreated(params) } + ) + + client.addHandler( + Notifications.SPHINX_CLIENT_ERRORED, + (params: ClientErroredNotification) => { this.clientErrored(params) } + ) + + client.addHandler( + Notifications.SPHINX_CLIENT_DESTROYED, + (params: ClientDestroyedNotification) => { this.clientDestroyed(params) } ) } - private createApp(info: SphinxInfo) { + private clientCreated(params: ClientCreatedNotification) { + this.logger.debug(`${Notifications.SPHINX_CLIENT_CREATED}: ${JSON.stringify(params, undefined, 2)}`) + let sphinxConfig = params.config - let confUri = vscode.Uri.file(info.conf_dir) - let workspaceFolder = vscode.workspace.getWorkspaceFolder(confUri) - if (!workspaceFolder) { - this.logger.error(`Unable to find workspace containing: ${info.conf_dir}`) - return + let config = vscode.workspace.getConfiguration("esbonio.server") + let documentSelector = config.get("documentSelector") + if (!documentSelector || documentSelector.length === 0) { + documentSelector = Server.DEFAULT_SELECTOR } let selector: vscode.DocumentFilter[] = [] + let defaultPattern = path.join(sphinxConfig.cwd, "**", "*") + for (let docSelector of documentSelector) { + selector.push({ + scheme: docSelector.scheme, + language: docSelector.language, + pattern: docSelector.pattern || defaultPattern + }) + } - let confPattern = uriToPattern(confUri) - selector.push({ language: "python", pattern: confPattern }) + this.setStatusItem( + params.id, + "sphinx", + "Sphinx[starting]", + { + selector: selector, + busy: true, + detail: sphinxConfig.buildCommand.join(" "), + severity: vscode.LanguageStatusSeverity.Information + } + ) + this.setStatusItem( + params.id, + "python", + "Python", + { + selector: selector, + detail: sphinxConfig.pythonCommand.join(" "), + command: { title: "Change Interpreter", command: "python.setInterpreter" }, + severity: vscode.LanguageStatusSeverity.Information + } + ) + } - let srcUri = vscode.Uri.file(info.src_dir) - let srcPattern = uriToPattern(srcUri); - selector.push({ language: 'restructuredtext', pattern: srcPattern }) + private clientErrored(params: ClientErroredNotification) { + this.logger.debug(`${Notifications.SPHINX_CLIENT_ERRORED}: ${JSON.stringify(params, undefined, 2)}`) + + this.setStatusItem( + params.id, + "sphinx", + "Sphinx[failed]", + { + busy: false, + detail: params.error, + severity: vscode.LanguageStatusSeverity.Error + } + ) + } - let itemId = `${workspaceFolder.uri}` - let buildUri = vscode.Uri.file(info.build_dir) - this.setStatusItem(itemId, "sphinx", `Sphinx v${info.version}`, { selector: selector }) - this.setStatusItem(itemId, "builder", `Builder - ${info.builder_name}`, { selector: selector }) - this.setStatusItem(itemId, "srcdir", `Source - ${renderPath(workspaceFolder, srcUri)}`, { selector: selector }) - this.setStatusItem(itemId, "confdir", `Config - ${renderPath(workspaceFolder, confUri)}`, { selector: selector }) - this.setStatusItem(itemId, "builddir", `Build - ${renderPath(workspaceFolder, buildUri)}`, { selector: selector }) + private clientDestroyed(params: ClientDestroyedNotification) { + this.logger.debug(`${Notifications.SPHINX_CLIENT_DESTROYED}: ${JSON.stringify(params, undefined, 2)}`) + + for (let [key, item] of this.statusItems.entries()) { + if (key.startsWith(params.id)) { + item.dispose() + this.statusItems.delete(key) + } + } + } + + private appCreated(params: AppCreatedNotification) { + this.logger.debug(`${Notifications.SPHINX_APP_CREATED}: ${JSON.stringify(params, undefined, 2)}`) + let sphinx = params.application + + this.setStatusItem( + params.id, + "sphinx", + `Sphinx[${sphinx.builder_name}] v${sphinx.version}`, + { + busy: false, + } + ) } private serverStop(_params: any) { @@ -70,12 +149,12 @@ export class StatusManager { } private setStatusItem( - sphinxId: string, + id: string, name: string, value: string, params?: StatusItemFields, ) { - let key = `${sphinxId}-${name.toLocaleLowerCase().replace(' ', '-')}` + let key = `${id}-${name.toLocaleLowerCase().replace(' ', '-')}` let statusItem = this.statusItems.get(key) if (!statusItem) { @@ -108,24 +187,3 @@ export class StatusManager { } } } - -function renderPath(workspace: vscode.WorkspaceFolder, uri: vscode.Uri): string { - let workspacePath = workspace.uri.fsPath - let uriPath = uri.fsPath - - let result = uriPath - - if (uriPath.startsWith(workspacePath)) { - result = path.join('.', result.replace(workspacePath, '')) - } - - if (result.length > 50) { - result = '...' + result.slice(result.length - 47) - } - - return result -} - -function uriToPattern(uri: vscode.Uri) { - return path.join(uri.fsPath, "**", "*").replace(/\\/g, '/'); -} From ea4a2c4dd357a8ad79974786b5d53c0ee06622ec Mon Sep 17 00:00:00 2001 From: Alex Carney Date: Thu, 14 Mar 2024 19:41:50 +0000 Subject: [PATCH 6/8] docs: Update main page --- docs/index.rst | 94 ++++++++++-------------------------- docs/lsp/getting-started.rst | 2 +- docs/lsp/reference.rst | 2 + 3 files changed, 28 insertions(+), 70 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 2d3f75796..ae1fc6f3b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,85 +3,42 @@ Esbonio .. rubric:: esbonio -- (v.) to explain -Esbonio aims to make it easier to work with `reStructuredText`_ tools such as -`Sphinx`_ by providing a `Language Server`_ to enhance your editing experience. +Esbonio is a `Language Server`_ for `Sphinx`_ documentation projects. -Language Server ---------------- +Esbonio aids the writing process by resolving references, providing completion suggestions and highlighting errors. +It ensures your local build is always up to date, allowing you to preview your changes in (almost!) real-time. +The server itself can even be extended to better suit the needs of your project. -Here is a quick summary of the features implemented by the language server. +The primary goal of Esbonio is to reduce the friction that comes from trying to remember the specifics of a markup language, so that you can focus on your content and not your tooling. -.. collection:: features +.. grid:: 2 + :gutter: 2 - .. collection-item:: Completion + .. grid-item-card:: Getting Started + :text-align: center + :link: lsp-getting-started + :link-type: ref - The language server implements :lsp:`textDocument/completion` and can - offer suggestions in a variety of contexts. + Using Esbonio for the first time within VSCode. - .. figure:: ../resources/images/completion-demo.gif - :align: center - :target: /_images/completion-demo.gif + .. grid-item-card:: How-To Guides + :text-align: center - .. collection-item:: Definition + Step-by-step guides on integrating Esbonio with other text editors. - The language server implements :lsp:`textDocument/definition` to provide the - location of items referenced by certain roles. Currently only the ``:ref:`` - and ``:doc:`` roles are supported. + .. grid-item-card:: Reference + :text-align: center + :link: lsp-reference + :link-type: ref - .. figure:: ../resources/images/definition-demo.gif - :align: center - :target: /_images/definition-demo.gif + Configuration options, API documentation, architecture diagrams and more. - .. collection-item:: Diagnostics - - The language server implements :lsp:`textDocument/publishDiagnostics` to - report errors/warnings enountered during a build. - - .. figure:: ../resources/images/diagnostic-sphinx-errors-demo.png - :align: center - :target: /_images/diagnostic-sphinx-errors-demo.png - - .. collection-item:: Document Links - - The language server implements :lsp:`textDocument/documentLink` to make references to other files "Ctrl + Clickable" - - .. figure:: ../resources/images/document-links-demo.png - :align: center - :target: /_images/document-links-demo.png - - .. collection-item:: Document Symbols - - The language server implements :lsp:`textDocument/documentSymbol` which - powers features like the "Outline" view in VSCode. - - .. figure:: ../resources/images/document-symbols-demo.png - :align: center - :target: /_images/document-symbols-demo.png - - .. collection-item:: Hover - - The language server implements :lsp:`textDocument/hover` to provide easy access to documentation for roles and directives. - - .. figure:: ../resources/images/hover-demo.png - :align: center - :target: /_images/hover-demo.png - - .. collection-item:: Implementation - - The language server implements :lsp:`textDocument/implementation` so you can easily find the implementation of a given role or directive. - - .. figure:: ../resources/images/implementation-demo.gif - :align: center - :target: /_images/implementation-demo.gif - -- See the :ref:`lsp_getting_started` guide for details on how to get up and - running. - -- For further details on more advanced use cases, see the :ref:`lsp-advanced` section. - -- Interested in adding support for your own Sphinx extensions? - See the section on :ref:`lsp-extending` for more information. + .. grid-item-card:: Extending + :text-align: center + :link: lsp-extending + :link-type: ref + Documentation on extending the language server .. toctree:: :glob: @@ -90,7 +47,6 @@ Here is a quick summary of the features implemented by the language server. :maxdepth: 2 lsp/getting-started - lsp/advanced-usage lsp/extending lsp/howto lsp/reference diff --git a/docs/lsp/getting-started.rst b/docs/lsp/getting-started.rst index f9aad42a5..86665dbfc 100644 --- a/docs/lsp/getting-started.rst +++ b/docs/lsp/getting-started.rst @@ -1,4 +1,4 @@ -.. _lsp_getting_started: +.. _lsp-getting-started: Getting Started =============== diff --git a/docs/lsp/reference.rst b/docs/lsp/reference.rst index 3e9b42173..cd48bea73 100644 --- a/docs/lsp/reference.rst +++ b/docs/lsp/reference.rst @@ -1,3 +1,5 @@ +.. _lsp-reference: + Reference ========= From 2611e64e2bc98b7594546583530054ae93957d25 Mon Sep 17 00:00:00 2001 From: Alex Carney Date: Thu, 14 Mar 2024 19:42:47 +0000 Subject: [PATCH 7/8] docs: Placeholder documentation for custom sphinx notifications --- docs/lsp/reference/notifications.rst | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 docs/lsp/reference/notifications.rst diff --git a/docs/lsp/reference/notifications.rst b/docs/lsp/reference/notifications.rst new file mode 100644 index 000000000..3af90708f --- /dev/null +++ b/docs/lsp/reference/notifications.rst @@ -0,0 +1,18 @@ +Notifications +============= + +In addition to the language server protocol, Esbonio will emit the following notifications + +.. currentmodule:: esbonio.server.features.sphinx_manager.manager + +.. autoclass:: ClientCreatedNotification + :members: + +.. autoclass:: AppCreatedNotification + :members: + +.. autoclass:: ClientErroredNotification + :members: + +.. autoclass:: ClientDestroyedNotification + :members: From 045b473e49b06c377b8f80e3774aa435cd40e485 Mon Sep 17 00:00:00 2001 From: Alex Carney Date: Thu, 14 Mar 2024 20:36:36 +0000 Subject: [PATCH 8/8] lsp: Limit importlib-resources version There seems to be an issue with the latest releases --- lib/esbonio/tox.ini | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/esbonio/tox.ini b/lib/esbonio/tox.ini index 8c54db1cb..361150496 100644 --- a/lib/esbonio/tox.ini +++ b/lib/esbonio/tox.ini @@ -9,6 +9,9 @@ description = "Run the test suite for the server component of esbonio" package = wheel wheel_build_env = .pkg deps = + + py38: importlib-resources>6,<6.2 + coverage[toml] pytest pytest-lsp>=0.3.1