Skip to content

Commit

Permalink
sphinx-agent: Move directives types to a sub-module
Browse files Browse the repository at this point in the history
  • Loading branch information
alcarney committed Jan 6, 2025
1 parent 5753e57 commit 570f2d3
Show file tree
Hide file tree
Showing 8 changed files with 164 additions and 121 deletions.
24 changes: 8 additions & 16 deletions lib/esbonio/esbonio/server/features/directives/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,24 @@
import inspect
import typing

import attrs
from lsprotocol import types as lsp

from esbonio import server
from esbonio.sphinx_agent import types

if typing.TYPE_CHECKING:
from collections.abc import Coroutine
from typing import Any


@attrs.define
class Directive:
"""Represents a directive."""

name: str
"""The name of the directive, as the user would type in an rst file."""

implementation: str | None
"""The dotted name of the directive's implementation."""


class DirectiveProvider:
"""Base class for directive providers"""

def suggest_directives(
self, context: server.CompletionContext
) -> list[Directive] | None | Coroutine[Any, Any, list[Directive] | None]:
) -> (
list[types.Directive] | None | Coroutine[Any, Any, list[types.Directive] | None]
):
"""Given a completion context, suggest directives that may be used."""
return None

Expand Down Expand Up @@ -57,19 +49,19 @@ def add_provider(self, provider: DirectiveProvider):

async def suggest_directives(
self, context: server.CompletionContext
) -> list[Directive]:
) -> list[types.Directive]:
"""Suggest directives that may be used, given a completion context.
Parameters
----------
context
The completion context.
"""
items: list[Directive] = []
items: list[types.Directive] = []

for provider in self._providers.values():
try:
result: list[Directive] | None = None
result: list[types.Directive] | None = None

aresult = provider.suggest_directives(context)
if inspect.isawaitable(aresult):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from typing import Callable
from typing import Optional

from . import Directive
from esbonio.sphinx_agent.types import Directive

DirectiveRenderer = Callable[
[server.CompletionContext, Directive], Optional[types.CompletionItem]
Expand Down
2 changes: 1 addition & 1 deletion lib/esbonio/esbonio/server/features/myst/directives.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
from lsprotocol import types

from esbonio import server
from esbonio.server.features.directives import Directive
from esbonio.server.features.directives import DirectiveFeature
from esbonio.server.features.directives import completion
from esbonio.sphinx_agent.types import MYST_DIRECTIVE
from esbonio.sphinx_agent.types import Directive


class MystDirectives(server.LanguageFeature):
Expand Down
17 changes: 8 additions & 9 deletions lib/esbonio/esbonio/server/features/sphinx_support/directives.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from __future__ import annotations

from lsprotocol import types
from lsprotocol import types as lsp

from esbonio import server
from esbonio.server.features import directives
from esbonio.server.features.project_manager import ProjectManager
from esbonio.sphinx_agent import types


class SphinxDirectives(directives.DirectiveProvider):
Expand All @@ -15,7 +16,7 @@ def __init__(self, manager: ProjectManager):

async def suggest_directives(
self, context: server.CompletionContext
) -> list[directives.Directive] | None:
) -> list[types.Directive] | None:
"""Given a completion context, suggest directives that may be used."""

if (project := self.manager.get_project(context.uri)) is None:
Expand All @@ -24,7 +25,7 @@ async def suggest_directives(
# Does the document have a default domain set?
results = await project.find_symbols(
uri=str(context.uri.resolve()),
kind=types.SymbolKind.Class.value,
kind=lsp.SymbolKind.Class.value,
detail="default-domain",
)
if len(results) > 0:
Expand All @@ -35,25 +36,23 @@ async def suggest_directives(
primary_domain = await project.get_config_value("primary_domain")
active_domain = default_domain or primary_domain or "py"

result: list[directives.Directive] = []
result: list[types.Directive] = []
for name, implementation in await project.get_directives():
# std: directives can be used unqualified
if name.startswith("std:"):
short_name = name.replace("std:", "")
result.append(
directives.Directive(name=short_name, implementation=implementation)
types.Directive(name=short_name, implementation=implementation)
)

# Also suggest unqualified versions of directives from the currently active domain.
elif name.startswith(f"{active_domain}:"):
short_name = name.replace(f"{active_domain}:", "")
result.append(
directives.Directive(name=short_name, implementation=implementation)
types.Directive(name=short_name, implementation=implementation)
)

result.append(
directives.Directive(name=name, implementation=implementation)
)
result.append(types.Directive(name=name, implementation=implementation))

return result

Expand Down
100 changes: 8 additions & 92 deletions lib/esbonio/esbonio/sphinx_agent/types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@
from __future__ import annotations

import dataclasses
import re
from typing import Any
from typing import Optional
from typing import Union

from .directives import MYST_DIRECTIVE
from .directives import RST_DIRECTIVE
from .directives import RST_DIRECTIVE_OPTION
from .directives import Directive
from .lsp import Diagnostic
from .lsp import DiagnosticSeverity
from .lsp import Location
Expand All @@ -27,112 +30,25 @@
__all__ = (
"Diagnostic",
"DiagnosticSeverity",
"Directive",
"IS_WIN",
"Location",
"MYST_DIRECTIVE",
"MYST_ROLE",
"Position",
"RST_DEFAULT_ROLE",
"RST_DIRECTIVE",
"RST_DIRECTIVE_OPTION",
"RST_ROLE",
"Range",
"Role",
"Uri",
)

MYST_DIRECTIVE: re.Pattern = re.compile(
r"""
(\s*) # directives can be indented
(?P<directive>
```(`*)? # directives start with at least 3 ` chars
(?!\w) # -- regular code blocks are not directives
[{]? # followed by an opening brace
(?P<name>[^}]+)? # directives have a name
[}]? # directives are closed with a closing brace
)
(\s+(?P<argument>.*?)\s*$)? # directives may take an argument
""",
re.VERBOSE,
)
"""A regular expression to detect and parse partial and complete MyST directives.
This does **not** include any options or content that may be included with the
initial declaration.
"""


RST_DIRECTIVE: re.Pattern = re.compile(
r"""
(\s*) # directives can be indented
(?P<directive>
\.\. # directives start with a comment
[ ]? # followed by a space
(?P<substitution>\| # this could be a substitution definition
(?P<substitution_text>[^|]+)?
\|?)?
[ ]?
(?P<name>([\w-]|:(?!:))+)? # directives have a name
(::)? # directives end with '::'
)
([\s]+(?P<argument>.*?)\s*$)? # directives may take an argument
""",
re.VERBOSE,
)
"""A regular expression to detect and parse partial and complete directives.
This does **not** include any options or content that may be included underneath
the initial declaration. A number of named capture groups are available.
``name``
The name of the directive, not including the domain prefix.
``directive``
Everything that makes up a directive, from the initial ``..`` up to and including the
``::`` characters.
``argument``
All argument text.
``substitution``
If the directive is part of a substitution definition, this group will contain
"""


RST_DIRECTIVE_OPTION: re.Pattern = re.compile(
r"""
(?P<indent>\s+) # directive options must be indented
(?P<option>
: # options start with a ':'
(?P<name>[\w-]+)? # options have a name
:? # options end with a ':'
)
(\s*
(?P<value>.*) # options can have a value
)?
""",
re.VERBOSE,
)
"""A regular expression used to detect and parse partial and complete directive options.
A number of named capture groups are available
``name``
The name of the option
``option``
The name of the option including the surrounding ``:`` characters.
``indent``
The whitespace characters making preceeding the initial ``:`` character
``value``
The value passed to the option
"""


# -- DB Types
#
# These represent the structure of data as stored in the SQLite database
Directive = tuple[str, Optional[str], Optional[str]]
Symbol = tuple[ # Represents either a document symbol or workspace symbol depending on context.
int, # id
str, # name
Expand Down
Loading

0 comments on commit 570f2d3

Please sign in to comment.