diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
new file mode 100644
index 000000000..901cad098
--- /dev/null
+++ b/.devcontainer/Dockerfile
@@ -0,0 +1,9 @@
+FROM mcr.microsoft.com/devcontainers/base:jammy
+
+COPY tools.mk /
+
+RUN apt-get update \
+ && export DEBIAN_FRONTEND=noninteractive \
+ && apt-get -y install --no-install-recommends python3-venv \
+ && su vscode -c "make -f tools.mk tools" \
+ && rm tools.mk
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 000000000..aac2d51f9
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,28 @@
+// For format details, see https://aka.ms/devcontainer.json. For config options, see the
+// README at: https://github.com/devcontainers/templates/tree/main/src/ubuntu
+{
+ "name": "Ubuntu",
+ // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
+ "image": "ghcr.io/swyddfa/esbonio-devenv:latest",
+ // "build": {
+ // "dockerfile": "Dockerfile"
+ // },
+ // Features to add to the dev container. More info: https://containers.dev/features.
+ // "features": {},
+ // Use 'forwardPorts' to make a list of ports inside the container available locally.
+ // "forwardPorts": [],
+ // Use 'postCreateCommand' to run commands after the container is created.
+ // "postCreateCommand": "uname -a",
+ // Configure tool-specific properties.
+ "customizations": {
+ "vscode": {
+ "extensions": [
+ "charliermarsh.ruff",
+ "ms-python.python",
+ "tamasfe.even-better-toml"
+ ]
+ }
+ }
+ // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
+ // "remoteUser": "root"
+}
diff --git a/.devcontainer/tools.mk b/.devcontainer/tools.mk
new file mode 100644
index 000000000..4b0c4cc0a
--- /dev/null
+++ b/.devcontainer/tools.mk
@@ -0,0 +1,143 @@
+ARCH ?= $(shell arch)
+BIN ?= $(HOME)/.local/bin
+
+ifeq ($(strip $(ARCH)),)
+$(error Unable to determine platform architecture)
+endif
+
+HATCH_VERSION = 1.10.0
+NODE_VERSION := 18.20.2
+
+# The versions of Python we support
+PYXX_versions := 3.8 3.9 3.10 3.11 3.12
+PY_INTERPRETERS =
+
+# Hatch is not only used for building packages, but bootstrapping any missing
+# interpreters
+HATCH ?= $(or $(shell command -v hatch), $(BIN)/hatch)
+
+$(HATCH):
+ curl -L --output /tmp/hatch.tar.gz https://github.com/pypa/hatch/releases/download/hatch-v$(HATCH_VERSION)/hatch-$(HATCH_VERSION)-$(ARCH)-unknown-linux-gnu.tar.gz
+ tar -xf /tmp/hatch.tar.gz -C /tmp
+ rm /tmp/hatch.tar.gz
+
+ test -d $(BIN) || mkdir -p $(BIN)
+ mv /tmp/hatch-$(HATCH_VERSION)-$(ARCH)-unknown-linux-gnu $(HATCH)
+
+ $@ --version
+ touch $@
+
+# This effectively defines a function `PYXX` that takes a Python version number
+# (e.g. 3.8) and expands it out into a common block of code that will ensure a
+# verison of that interpreter is available to be used.
+#
+# The is perhaps a bit more complicated than I'd like, but it should mean that
+# the project's makefiles are useful both inside and outside of a devcontainer.
+#
+# `PYXX` has the following behavior:
+# - If possible, it will reuse the user's existing version of Python
+# i.e. $(shell command -v pythonX.X)
+#
+# - The user may force a specific interpreter to be used by setting the
+# variable when running make e.g. PYXX=/path/to/pythonX.X make ...
+#
+# - Otherwise, `make` will use `$(HATCH)` to install the given version of
+# Python under `$(BIN)`
+#
+# See: https://www.gnu.org/software/make/manual/html_node/Eval-Function.html
+define PYXX =
+
+PY$(subst .,,$1) ?= $$(shell command -v python$1)
+
+ifeq ($$(strip $$(PY$(subst .,,$1))),)
+
+PY$(subst .,,$1) := $$(BIN)/python$1
+
+$$(PY$(subst .,,$1)): $$(HATCH)
+ $$(HATCH) python find $1 || $$(HATCH) python install $1
+ ln -s $$$$($$(HATCH) python find $1) $$@
+
+ $$@ --version
+ touch $$@
+
+endif
+
+PY_INTERPRETERS += $$(PY$(subst .,,$1))
+endef
+
+# Uncomment the following line to see what this expands into.
+#$(foreach version,$(PYXX_versions),$(info $(call PYXX,$(version))))
+$(foreach version,$(PYXX_versions),$(eval $(call PYXX,$(version))))
+
+# Set a default `python` command if there is not one already
+PY ?= $(shell command -v python3)
+
+ifeq ($(strip $(PY)),)
+PY := $(BIN)/python
+
+$(PY): $(PY312)
+ ln -s $< $@
+ $@ --version
+ touch $@
+endif
+
+PY_INTERPRETERS += $(PY)
+#$(info $(PY_INTERPRETERS))
+
+PIPX ?= $(shell command -v pipx)
+
+ifeq ($(strip $(PIPX)),)
+PIPX := $(BIN)/pipx
+PIPX_VERSION := 1.5.0
+
+$(PIPX):
+ curl -L -o $(BIN)/pipx.pyz https://github.com/pypa/pipx/releases/download/$(PIPX_VERSION)/pipx.pyz
+ echo '#!/bin/bash\nexec $(PY) $(BIN)/pipx.pyz "$$@"' > $(PIPX)
+
+ chmod +x $(PIPX)
+ $@ --version
+ touch $@
+endif
+
+PRE_COMMIT ?= $(shell command -v pre-commit)
+
+ifeq ($(strip $(PRE_COMMIT)),)
+PRE_COMMIT := $(BIN)/pre-commit
+
+$(PRE_COMMIT): $(PIPX)
+ $(PIPX) install pre-commit
+ $@ --version
+ touch $@
+endif
+
+PY_TOOLS := $(HATCH) $(PIPX) $(PRE_COMMIT)
+
+# Node JS
+NPM ?= $(shell command -v npm)
+
+ifeq ($(strip $(NPM)),)
+
+NPM := $(BIN)/npm
+NODE := $(BIN)/node
+NODE_DIR := $(HOME)/.local/node
+
+$(NPM):
+ curl -L --output /tmp/node.tar.xz https://nodejs.org/dist/v$(NODE_VERSION)/node-v$(NODE_VERSION)-linux-x64.tar.xz
+ tar -xJf /tmp/node.tar.xz -C /tmp
+ rm /tmp/node.tar.xz
+
+ [ -d $(NODE_DIR) ] || mkdir -p $(NODE_DIR)
+ mv /tmp/node-v$(NODE_VERSION)-linux-x64/* $(NODE_DIR)
+
+ [ -d $(BIN) ] || mkdir -p $(BIN)
+ ln -s $(NODE_DIR)/bin/node $(NODE)
+ ln -s $(NODE_DIR)/bin/npm $(NPM)
+
+ $(NODE) --version
+ PATH=$(BIN) $(NPM) --version
+
+endif
+
+# One command to bootstrap all tools and check their versions
+tools: $(PY_INTERPRETERS) $(PY_TOOLS) $(NPM)
+ for prog in $^ ; do echo -n "$${prog}\t" ; PATH=$(BIN) $${prog} --version; done
diff --git a/.github/workflows/devenv.yml b/.github/workflows/devenv.yml
new file mode 100644
index 000000000..3133a5c74
--- /dev/null
+++ b/.github/workflows/devenv.yml
@@ -0,0 +1,57 @@
+name: 'Build devcontainer'
+on:
+
+ workflow_dispatch:
+
+ pull_request:
+ branches:
+ - develop
+ paths:
+ - '.devcontainer/**'
+
+ push:
+ branches:
+ - develop
+ paths:
+ - '.devcontainer/**'
+
+env:
+ REGISTRY: ghcr.io
+ IMAGE_NAME: ${{ github.repository }}-devenv
+
+jobs:
+ build-and-push-image:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ packages: write
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Log in to the Container registry
+ if: github.event_name != 'pull_request'
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.REGISTRY }}
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Extract metadata (tags, labels) for Docker
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
+
+ - name: Build and push Docker image
+ id: push
+ uses: docker/build-push-action@v5
+ with:
+ context: .devcontainer/
+ push: ${{ github.event_name != 'pull_request' }}
+ tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
+ labels: ${{ steps.meta.outputs.labels }}
diff --git a/.github/workflows/lsp-pr.yml b/.github/workflows/lsp-pr.yml
index 785c8cd50..cd5246a28 100644
--- a/.github/workflows/lsp-pr.yml
+++ b/.github/workflows/lsp-pr.yml
@@ -64,14 +64,11 @@ jobs:
- run: |
python --version
- python -m pip install --upgrade pip
- python -m pip install --upgrade tox
+ python -m pip install --upgrade hatch
name: Setup Environment
- run: |
cd lib/esbonio
- version=$(echo ${{ matrix.python-version }} | tr -d .)
- python -m tox run -e `tox -l | grep $version | tr '\n' ','`
- shell: bash
+ hatch test -i py=${{ matrix.python-version }}
name: Run Tests
diff --git a/.github/workflows/vscode-pr.yml b/.github/workflows/vscode-pr.yml
index 1b3844683..bb51ddbb4 100644
--- a/.github/workflows/vscode-pr.yml
+++ b/.github/workflows/vscode-pr.yml
@@ -35,7 +35,7 @@ jobs:
- run: |
python --version
python -m pip install --upgrade pip
- python -m pip install --upgrade hatch tox towncrier 'importlib-resources<6'
+ python -m pip install --upgrade hatch towncrier
name: Install Build Tools
- run: |
@@ -57,10 +57,7 @@ jobs:
# Use in-repo version of esbonio for dev builds
echo "whl=${ESBONIO_WHL}"
- ESBONIO_WHL=${ESBONIO_WHL} tox run -e bundle-deps
-
- npm ci --prefer-offline
- npm run package
+ ESBONIO=${ESBONIO_WHL} make dist
id: assets
name: Package Extension
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 25e0683f6..d3c347e6a 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -8,27 +8,16 @@ repos:
- id: end-of-file-fixer
- id: trailing-whitespace
-- repo: https://github.com/psf/black
- rev: 24.4.0
+- repo: https://github.com/astral-sh/ruff-pre-commit
+ rev: v0.4.7
hooks:
- - id: black
+ - id: ruff
+ args: [--fix]
-- repo: https://github.com/PyCQA/flake8
- rev: 7.0.0
- hooks:
- - id: flake8
- exclude: 'scripts/sphinx-app.py'
- args: [--config=lib/esbonio/setup.cfg]
-
-- repo: https://github.com/pycqa/isort
- rev: 5.13.2
- hooks:
- - id: isort
- name: isort (python)
- args: [--settings-file=lib/esbonio/pyproject.toml]
+ - id: ruff-format
- repo: https://github.com/pre-commit/mirrors-mypy
- rev: 'v1.9.0'
+ rev: 'v1.10.0'
hooks:
- id: mypy
name: mypy (scripts)
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index bf6f24fcc..a65ea7964 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -1,8 +1,7 @@
{
"recommendations": [
+ "charliermarsh.ruff",
"ms-python.python",
- "ms-python.black-formatter",
- "ms-python.isort",
- "swyddfa.esbonio"
+ "tamasfe.even-better-toml"
]
}
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 42e8d7c8f..3bd2b184d 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -36,10 +36,7 @@
"outFiles": [
"${workspaceRoot}/code/dist/node/**/*.js"
],
- // "preLaunchTask": "${defaultBuildTask}",
- "env": {
- // "VSCODE_LSP_DEBUG": "true"
- }
+ "preLaunchTask": "${defaultBuildTask}",
},
{
"name": "VSCode Web Extension",
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 25fc86497..d0181bd9d 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -2,9 +2,13 @@
"version": "2.0.0",
"tasks": [
{
- "type": "npm",
- "script": "watch",
+ "label": "npm: watch",
+ "type": "process",
"isBackground": true,
+ "command": "make",
+ "args": [
+ "watch"
+ ],
"options": {
"cwd": "${workspaceRoot}/code"
},
@@ -14,17 +18,10 @@
},
"presentation": {
"panel": "dedicated",
- "reveal": "never"
+ "reveal": "always"
},
"problemMatcher": [
- {
- "base": "$tsc-watch",
- "background": {
- "activeOnStart": true,
- "beginsPattern": "asset .*",
- "endsPattern": "webpack .* compiled .*"
- }
- }
+ "$tsc-watch"
]
},
{
diff --git a/Makefile b/Makefile
index 7f92c86eb..dcb3caa25 100644
--- a/Makefile
+++ b/Makefile
@@ -1,53 +1,13 @@
-.PHONY: preview-completion-docs completion-docs venv npm
+include .devcontainer/tools.mk
-VENV := .env
+.PHONY: lint enable-pre-commit disable-pre-commit
-ifeq ($(CI),true)
- PYTHON=python
-endif
+lint: $(PRE_COMMIT)
+ $(PRE_COMMIT) run --all-files
-# Default python env.
-ifndef PYTHON
- PYTHON=$(VENV)/bin/python
-endif
-# ---------------------------------------- Development Environments -------------------------------------------
-$(VENV)/bin/python:
- python3 -m venv $(VENV)
- $@ -m pip install --upgrade pip
- $@ -m pip install -r docs/requirements.txt
- $@ -m pip install -e lib/esbonio[dev]
- $@ -m pip install -e lib/esbonio-extensions[dev]
+enable-pre-commit: $(PRE_COMMIT)
+ $(PRE_COMMIT) install
-venv: $(VENV)/bin/python
-
-code/node_modules: code/package.json code/package-lock.json
- npm --prefix ./code/ install
-
-npm: code/node_modules
-
-# ---------------------------------------- Tests, Lints, Tools etc. -------------------------------------------
-mypy: $(PYTHON)
- mypy --namespace-packages --explicit-package-bases -p esbonio
-
-# ---------------------------------------- CompletionItem Documentation ---------------------------------------
-DOCUTILS_COMPLETION_DOCS=lib/esbonio/esbonio/lsp/rst/roles.json lib/esbonio/esbonio/lsp/rst/directives.json
-SPHINX_COMPLETION_DOCS=lib/esbonio/esbonio/lsp/sphinx/roles.json lib/esbonio/esbonio/lsp/sphinx/directives.json
-COMPLETION_DOCS=$(SPHINX_COMPLETION_DOCS) $(DOCUTILS_COMPLETION_DOCS)
-
-# '&:' Indicates that the multiple targets listed are 'grouped' and that they are produced together from running
-# the command below once. This prevents the command being run once for each file produced.
-# https://www.gnu.org/software/make/manual/html_node/Multiple-Targets.html
-#
-# '$^' Expands into the list of dependencies of the target (python .... in this case)
-# https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html#Automatic-Variables
-$(DOCUTILS_COMPLETION_DOCS) &: $(PYTHON) scripts/generate_docutils_documentation.py
- $^ -o lib/esbonio/esbonio/lsp/rst/
-
-$(SPHINX_COMPLETION_DOCS) &: $(PYTHON) scripts/generate_sphinx_documentation.py
- $^ -o lib/esbonio/esbonio/lsp/sphinx/
-
-completion-docs: $(COMPLETION_DOCS)
-
-preview-completion-docs: $(COMPLETION_DOCS)
- $(PYTHON) scripts/preview_documentation.py $(COMPLETION_DOCS)
+disable-pre-commit: $(PRE_COMMIT)
+ $(PRE_COMMIT) uninstall
diff --git a/code/Makefile b/code/Makefile
new file mode 100644
index 000000000..227255abb
--- /dev/null
+++ b/code/Makefile
@@ -0,0 +1,48 @@
+include ../.devcontainer/tools.mk
+
+ESBONIO ?= --pre esbonio
+
+.PHONY: dist dev-deps release-deps release
+
+watch: dev-deps $(NPM)
+ -test -d dist && rm -r dist
+ $(NPM) run watch
+
+compile: dev-deps $(NPM)
+ -test -d dist && rm -r dist
+ $(NPM) run compile
+
+dist: release-deps $(NPM)
+ -test -d dist && rm -r dist
+ $(NPM) run package
+
+release: $(TOWNCRIER) $(HATCH) $(PY)
+ $(PY) ../scripts/make_release.py lsp
+ $(PY) ../scripts/make_release.py vscode
+
+# Ensures the version of esbonio in ../lib/esbonio is used.
+dev-deps: node_modules/.installed bundled/libs/.installed
+ -test -d bundled/libs/esbonio-*.dist-info && rm -r bundled/libs/esbonio-*.dist-info
+ -test -L bundled/libs/esbonio || rm -r bundled/libs/esbonio
+ if [ ! -f bundled/libs/esbonio/__main__.py ]; then \
+ test -L bundled/libs/esbonio && rm bundled/libs/esbonio; \
+ ln -s $(shell pwd)/../lib/esbonio/esbonio bundled/libs/esbonio; \
+ fi
+
+# Ensures the latest version of esbonio from PyPi is used.
+release-deps: node_modules/.installed bundled/libs/.installed
+ -test -L bundled/libs/esbonio && rm bundled/libs/esbonio
+ test -d bundled/libs/esbonio-*.dist-info || $(PY38) -m pip install -t ./bundled/libs --no-cache-dir --implementation py --no-deps --upgrade $(ESBONIO)
+
+requirements.txt: $(HATCH) requirements.in
+ $(HATCH) run deps:update
+
+bundled/libs/.installed: $(PY38) requirements.txt
+ -test -d bundled/libs && rm -r bundled/libs
+ $(PY38) --version
+ $(PY38) -m pip install -t ./bundled/libs --no-cache-dir --implementation py --no-deps --upgrade -r ./requirements.txt
+ touch $@
+
+node_modules/.installed: package.json package-lock.json $(NPM)
+ $(NPM) ci
+ touch $@
diff --git a/code/hatch.toml b/code/hatch.toml
new file mode 100644
index 000000000..df070ec3e
--- /dev/null
+++ b/code/hatch.toml
@@ -0,0 +1,10 @@
+[envs.deps]
+python = "3.8"
+dependencies = ["pip-tools"]
+skip-install = true
+
+[envs.deps.scripts]
+update = [
+ "python --version",
+ "pip-compile --resolver=backtracking --generate-hashes --upgrade requirements.in",
+]
diff --git a/code/package-lock.json b/code/package-lock.json
index 83dffd4df..a6d203170 100644
--- a/code/package-lock.json
+++ b/code/package-lock.json
@@ -1,24 +1,24 @@
{
"name": "esbonio",
- "version": "0.93.1",
+ "version": "0.94.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "esbonio",
- "version": "0.93.1",
+ "version": "0.94.0",
"license": "MIT",
"dependencies": {
"@vscode/python-extension": "^1.0.5",
- "semver": "^7.6.0",
+ "semver": "^7.6.2",
"vscode-languageclient": "^9.0.1"
},
"devDependencies": {
"@types/glob": "^8.1.0",
"@types/node": "^18",
"@types/vscode": "1.78.0",
- "@vscode/vsce": "^2.26.0",
- "esbuild": "^0.20.2",
+ "@vscode/vsce": "^2.26.1",
+ "esbuild": "^0.21.4",
"ovsx": "^0.9.1",
"typescript": "^5.4.5"
},
@@ -235,9 +235,9 @@
}
},
"node_modules/@esbuild/aix-ppc64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
- "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.4.tgz",
+ "integrity": "sha512-Zrm+B33R4LWPLjDEVnEqt2+SLTATlru1q/xYKVn8oVTbiRBGmK2VIMoIYGJDGyftnGaC788IuzGFAlb7IQ0Y8A==",
"cpu": [
"ppc64"
],
@@ -251,9 +251,9 @@
}
},
"node_modules/@esbuild/android-arm": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
- "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.4.tgz",
+ "integrity": "sha512-E7H/yTd8kGQfY4z9t3nRPk/hrhaCajfA3YSQSBrst8B+3uTcgsi8N+ZWYCaeIDsiVs6m65JPCaQN/DxBRclF3A==",
"cpu": [
"arm"
],
@@ -267,9 +267,9 @@
}
},
"node_modules/@esbuild/android-arm64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
- "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.4.tgz",
+ "integrity": "sha512-fYFnz+ObClJ3dNiITySBUx+oNalYUT18/AryMxfovLkYWbutXsct3Wz2ZWAcGGppp+RVVX5FiXeLYGi97umisA==",
"cpu": [
"arm64"
],
@@ -283,9 +283,9 @@
}
},
"node_modules/@esbuild/android-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
- "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.4.tgz",
+ "integrity": "sha512-mDqmlge3hFbEPbCWxp4fM6hqq7aZfLEHZAKGP9viq9wMUBVQx202aDIfc3l+d2cKhUJM741VrCXEzRFhPDKH3Q==",
"cpu": [
"x64"
],
@@ -299,9 +299,9 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
- "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.4.tgz",
+ "integrity": "sha512-72eaIrDZDSiWqpmCzVaBD58c8ea8cw/U0fq/PPOTqE3c53D0xVMRt2ooIABZ6/wj99Y+h4ksT/+I+srCDLU9TA==",
"cpu": [
"arm64"
],
@@ -315,9 +315,9 @@
}
},
"node_modules/@esbuild/darwin-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
- "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.4.tgz",
+ "integrity": "sha512-uBsuwRMehGmw1JC7Vecu/upOjTsMhgahmDkWhGLWxIgUn2x/Y4tIwUZngsmVb6XyPSTXJYS4YiASKPcm9Zitag==",
"cpu": [
"x64"
],
@@ -331,9 +331,9 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
- "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.4.tgz",
+ "integrity": "sha512-8JfuSC6YMSAEIZIWNL3GtdUT5NhUA/CMUCpZdDRolUXNAXEE/Vbpe6qlGLpfThtY5NwXq8Hi4nJy4YfPh+TwAg==",
"cpu": [
"arm64"
],
@@ -347,9 +347,9 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
- "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.4.tgz",
+ "integrity": "sha512-8d9y9eQhxv4ef7JmXny7591P/PYsDFc4+STaxC1GBv0tMyCdyWfXu2jBuqRsyhY8uL2HU8uPyscgE2KxCY9imQ==",
"cpu": [
"x64"
],
@@ -363,9 +363,9 @@
}
},
"node_modules/@esbuild/linux-arm": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
- "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.4.tgz",
+ "integrity": "sha512-2rqFFefpYmpMs+FWjkzSgXg5vViocqpq5a1PSRgT0AvSgxoXmGF17qfGAzKedg6wAwyM7UltrKVo9kxaJLMF/g==",
"cpu": [
"arm"
],
@@ -379,9 +379,9 @@
}
},
"node_modules/@esbuild/linux-arm64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
- "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.4.tgz",
+ "integrity": "sha512-/GLD2orjNU50v9PcxNpYZi+y8dJ7e7/LhQukN3S4jNDXCKkyyiyAz9zDw3siZ7Eh1tRcnCHAo/WcqKMzmi4eMQ==",
"cpu": [
"arm64"
],
@@ -395,9 +395,9 @@
}
},
"node_modules/@esbuild/linux-ia32": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
- "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.4.tgz",
+ "integrity": "sha512-pNftBl7m/tFG3t2m/tSjuYeWIffzwAZT9m08+9DPLizxVOsUl8DdFzn9HvJrTQwe3wvJnwTdl92AonY36w/25g==",
"cpu": [
"ia32"
],
@@ -411,9 +411,9 @@
}
},
"node_modules/@esbuild/linux-loong64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
- "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.4.tgz",
+ "integrity": "sha512-cSD2gzCK5LuVX+hszzXQzlWya6c7hilO71L9h4KHwqI4qeqZ57bAtkgcC2YioXjsbfAv4lPn3qe3b00Zt+jIfQ==",
"cpu": [
"loong64"
],
@@ -427,9 +427,9 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
- "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.4.tgz",
+ "integrity": "sha512-qtzAd3BJh7UdbiXCrg6npWLYU0YpufsV9XlufKhMhYMJGJCdfX/G6+PNd0+v877X1JG5VmjBLUiFB0o8EUSicA==",
"cpu": [
"mips64el"
],
@@ -443,9 +443,9 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
- "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.4.tgz",
+ "integrity": "sha512-yB8AYzOTaL0D5+2a4xEy7OVvbcypvDR05MsB/VVPVA7nL4hc5w5Dyd/ddnayStDgJE59fAgNEOdLhBxjfx5+dg==",
"cpu": [
"ppc64"
],
@@ -459,9 +459,9 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
- "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.4.tgz",
+ "integrity": "sha512-Y5AgOuVzPjQdgU59ramLoqSSiXddu7F3F+LI5hYy/d1UHN7K5oLzYBDZe23QmQJ9PIVUXwOdKJ/jZahPdxzm9w==",
"cpu": [
"riscv64"
],
@@ -475,9 +475,9 @@
}
},
"node_modules/@esbuild/linux-s390x": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
- "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.4.tgz",
+ "integrity": "sha512-Iqc/l/FFwtt8FoTK9riYv9zQNms7B8u+vAI/rxKuN10HgQIXaPzKZc479lZ0x6+vKVQbu55GdpYpeNWzjOhgbA==",
"cpu": [
"s390x"
],
@@ -491,9 +491,9 @@
}
},
"node_modules/@esbuild/linux-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
- "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.4.tgz",
+ "integrity": "sha512-Td9jv782UMAFsuLZINfUpoF5mZIbAj+jv1YVtE58rFtfvoKRiKSkRGQfHTgKamLVT/fO7203bHa3wU122V/Bdg==",
"cpu": [
"x64"
],
@@ -507,9 +507,9 @@
}
},
"node_modules/@esbuild/netbsd-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
- "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.4.tgz",
+ "integrity": "sha512-Awn38oSXxsPMQxaV0Ipb7W/gxZtk5Tx3+W+rAPdZkyEhQ6968r9NvtkjhnhbEgWXYbgV+JEONJ6PcdBS+nlcpA==",
"cpu": [
"x64"
],
@@ -523,9 +523,9 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
- "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.4.tgz",
+ "integrity": "sha512-IsUmQeCY0aU374R82fxIPu6vkOybWIMc3hVGZ3ChRwL9hA1TwY+tS0lgFWV5+F1+1ssuvvXt3HFqe8roCip8Hg==",
"cpu": [
"x64"
],
@@ -539,9 +539,9 @@
}
},
"node_modules/@esbuild/sunos-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
- "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.4.tgz",
+ "integrity": "sha512-hsKhgZ4teLUaDA6FG/QIu2q0rI6I36tZVfM4DBZv3BG0mkMIdEnMbhc4xwLvLJSS22uWmaVkFkqWgIS0gPIm+A==",
"cpu": [
"x64"
],
@@ -555,9 +555,9 @@
}
},
"node_modules/@esbuild/win32-arm64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
- "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.4.tgz",
+ "integrity": "sha512-UUfMgMoXPoA/bvGUNfUBFLCh0gt9dxZYIx9W4rfJr7+hKe5jxxHmfOK8YSH4qsHLLN4Ck8JZ+v7Q5fIm1huErg==",
"cpu": [
"arm64"
],
@@ -571,9 +571,9 @@
}
},
"node_modules/@esbuild/win32-ia32": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
- "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.4.tgz",
+ "integrity": "sha512-yIxbspZb5kGCAHWm8dexALQ9en1IYDfErzjSEq1KzXFniHv019VT3mNtTK7t8qdy4TwT6QYHI9sEZabONHg+aw==",
"cpu": [
"ia32"
],
@@ -587,9 +587,9 @@
}
},
"node_modules/@esbuild/win32-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
- "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.4.tgz",
+ "integrity": "sha512-sywLRD3UK/qRJt0oBwdpYLBibk7KiRfbswmWRDabuncQYSlf8aLEEUor/oP6KRz8KEG+HoiVLBhPRD5JWjS8Sg==",
"cpu": [
"x64"
],
@@ -640,9 +640,9 @@
}
},
"node_modules/@vscode/vsce": {
- "version": "2.26.0",
- "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.26.0.tgz",
- "integrity": "sha512-v54ltgMzUG8lGY0kAgaOlry57xse1RlWzes9FotfGEx+Fr05KeR8rZicQzEMDmi9QnOgVWHuiEq+xA2HWkAz+Q==",
+ "version": "2.26.1",
+ "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.26.1.tgz",
+ "integrity": "sha512-QOG6Ht7V93nhwcBxPWcG33UK0qDGEoJdg0xtVeaTN27W6PGdMJUJGTPhB/sNHUIFKwvwzv/zMAHvDgMNXbcwlA==",
"dev": true,
"dependencies": {
"@azure/identity": "^4.1.0",
@@ -1248,9 +1248,9 @@
}
},
"node_modules/esbuild": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
- "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.4.tgz",
+ "integrity": "sha512-sFMcNNrj+Q0ZDolrp5pDhH0nRPN9hLIM3fRPwgbLYJeSHHgnXSnbV3xYgSVuOeLWH9c73VwmEverVzupIv5xuA==",
"dev": true,
"hasInstallScript": true,
"bin": {
@@ -1260,29 +1260,29 @@
"node": ">=12"
},
"optionalDependencies": {
- "@esbuild/aix-ppc64": "0.20.2",
- "@esbuild/android-arm": "0.20.2",
- "@esbuild/android-arm64": "0.20.2",
- "@esbuild/android-x64": "0.20.2",
- "@esbuild/darwin-arm64": "0.20.2",
- "@esbuild/darwin-x64": "0.20.2",
- "@esbuild/freebsd-arm64": "0.20.2",
- "@esbuild/freebsd-x64": "0.20.2",
- "@esbuild/linux-arm": "0.20.2",
- "@esbuild/linux-arm64": "0.20.2",
- "@esbuild/linux-ia32": "0.20.2",
- "@esbuild/linux-loong64": "0.20.2",
- "@esbuild/linux-mips64el": "0.20.2",
- "@esbuild/linux-ppc64": "0.20.2",
- "@esbuild/linux-riscv64": "0.20.2",
- "@esbuild/linux-s390x": "0.20.2",
- "@esbuild/linux-x64": "0.20.2",
- "@esbuild/netbsd-x64": "0.20.2",
- "@esbuild/openbsd-x64": "0.20.2",
- "@esbuild/sunos-x64": "0.20.2",
- "@esbuild/win32-arm64": "0.20.2",
- "@esbuild/win32-ia32": "0.20.2",
- "@esbuild/win32-x64": "0.20.2"
+ "@esbuild/aix-ppc64": "0.21.4",
+ "@esbuild/android-arm": "0.21.4",
+ "@esbuild/android-arm64": "0.21.4",
+ "@esbuild/android-x64": "0.21.4",
+ "@esbuild/darwin-arm64": "0.21.4",
+ "@esbuild/darwin-x64": "0.21.4",
+ "@esbuild/freebsd-arm64": "0.21.4",
+ "@esbuild/freebsd-x64": "0.21.4",
+ "@esbuild/linux-arm": "0.21.4",
+ "@esbuild/linux-arm64": "0.21.4",
+ "@esbuild/linux-ia32": "0.21.4",
+ "@esbuild/linux-loong64": "0.21.4",
+ "@esbuild/linux-mips64el": "0.21.4",
+ "@esbuild/linux-ppc64": "0.21.4",
+ "@esbuild/linux-riscv64": "0.21.4",
+ "@esbuild/linux-s390x": "0.21.4",
+ "@esbuild/linux-x64": "0.21.4",
+ "@esbuild/netbsd-x64": "0.21.4",
+ "@esbuild/openbsd-x64": "0.21.4",
+ "@esbuild/sunos-x64": "0.21.4",
+ "@esbuild/win32-arm64": "0.21.4",
+ "@esbuild/win32-ia32": "0.21.4",
+ "@esbuild/win32-x64": "0.21.4"
}
},
"node_modules/escape-string-regexp": {
@@ -1871,6 +1871,7 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
@@ -2333,12 +2334,9 @@
"dev": true
},
"node_modules/semver": {
- "version": "7.6.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
- "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
- "dependencies": {
- "lru-cache": "^6.0.0"
- },
+ "version": "7.6.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
+ "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
"bin": {
"semver": "bin/semver.js"
},
@@ -2746,7 +2744,8 @@
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
- "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
},
"node_modules/yauzl": {
"version": "2.10.0",
@@ -2941,163 +2940,163 @@
}
},
"@esbuild/aix-ppc64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
- "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.4.tgz",
+ "integrity": "sha512-Zrm+B33R4LWPLjDEVnEqt2+SLTATlru1q/xYKVn8oVTbiRBGmK2VIMoIYGJDGyftnGaC788IuzGFAlb7IQ0Y8A==",
"dev": true,
"optional": true
},
"@esbuild/android-arm": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
- "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.4.tgz",
+ "integrity": "sha512-E7H/yTd8kGQfY4z9t3nRPk/hrhaCajfA3YSQSBrst8B+3uTcgsi8N+ZWYCaeIDsiVs6m65JPCaQN/DxBRclF3A==",
"dev": true,
"optional": true
},
"@esbuild/android-arm64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
- "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.4.tgz",
+ "integrity": "sha512-fYFnz+ObClJ3dNiITySBUx+oNalYUT18/AryMxfovLkYWbutXsct3Wz2ZWAcGGppp+RVVX5FiXeLYGi97umisA==",
"dev": true,
"optional": true
},
"@esbuild/android-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
- "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.4.tgz",
+ "integrity": "sha512-mDqmlge3hFbEPbCWxp4fM6hqq7aZfLEHZAKGP9viq9wMUBVQx202aDIfc3l+d2cKhUJM741VrCXEzRFhPDKH3Q==",
"dev": true,
"optional": true
},
"@esbuild/darwin-arm64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
- "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.4.tgz",
+ "integrity": "sha512-72eaIrDZDSiWqpmCzVaBD58c8ea8cw/U0fq/PPOTqE3c53D0xVMRt2ooIABZ6/wj99Y+h4ksT/+I+srCDLU9TA==",
"dev": true,
"optional": true
},
"@esbuild/darwin-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
- "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.4.tgz",
+ "integrity": "sha512-uBsuwRMehGmw1JC7Vecu/upOjTsMhgahmDkWhGLWxIgUn2x/Y4tIwUZngsmVb6XyPSTXJYS4YiASKPcm9Zitag==",
"dev": true,
"optional": true
},
"@esbuild/freebsd-arm64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
- "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.4.tgz",
+ "integrity": "sha512-8JfuSC6YMSAEIZIWNL3GtdUT5NhUA/CMUCpZdDRolUXNAXEE/Vbpe6qlGLpfThtY5NwXq8Hi4nJy4YfPh+TwAg==",
"dev": true,
"optional": true
},
"@esbuild/freebsd-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
- "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.4.tgz",
+ "integrity": "sha512-8d9y9eQhxv4ef7JmXny7591P/PYsDFc4+STaxC1GBv0tMyCdyWfXu2jBuqRsyhY8uL2HU8uPyscgE2KxCY9imQ==",
"dev": true,
"optional": true
},
"@esbuild/linux-arm": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
- "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.4.tgz",
+ "integrity": "sha512-2rqFFefpYmpMs+FWjkzSgXg5vViocqpq5a1PSRgT0AvSgxoXmGF17qfGAzKedg6wAwyM7UltrKVo9kxaJLMF/g==",
"dev": true,
"optional": true
},
"@esbuild/linux-arm64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
- "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.4.tgz",
+ "integrity": "sha512-/GLD2orjNU50v9PcxNpYZi+y8dJ7e7/LhQukN3S4jNDXCKkyyiyAz9zDw3siZ7Eh1tRcnCHAo/WcqKMzmi4eMQ==",
"dev": true,
"optional": true
},
"@esbuild/linux-ia32": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
- "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.4.tgz",
+ "integrity": "sha512-pNftBl7m/tFG3t2m/tSjuYeWIffzwAZT9m08+9DPLizxVOsUl8DdFzn9HvJrTQwe3wvJnwTdl92AonY36w/25g==",
"dev": true,
"optional": true
},
"@esbuild/linux-loong64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
- "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.4.tgz",
+ "integrity": "sha512-cSD2gzCK5LuVX+hszzXQzlWya6c7hilO71L9h4KHwqI4qeqZ57bAtkgcC2YioXjsbfAv4lPn3qe3b00Zt+jIfQ==",
"dev": true,
"optional": true
},
"@esbuild/linux-mips64el": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
- "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.4.tgz",
+ "integrity": "sha512-qtzAd3BJh7UdbiXCrg6npWLYU0YpufsV9XlufKhMhYMJGJCdfX/G6+PNd0+v877X1JG5VmjBLUiFB0o8EUSicA==",
"dev": true,
"optional": true
},
"@esbuild/linux-ppc64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
- "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.4.tgz",
+ "integrity": "sha512-yB8AYzOTaL0D5+2a4xEy7OVvbcypvDR05MsB/VVPVA7nL4hc5w5Dyd/ddnayStDgJE59fAgNEOdLhBxjfx5+dg==",
"dev": true,
"optional": true
},
"@esbuild/linux-riscv64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
- "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.4.tgz",
+ "integrity": "sha512-Y5AgOuVzPjQdgU59ramLoqSSiXddu7F3F+LI5hYy/d1UHN7K5oLzYBDZe23QmQJ9PIVUXwOdKJ/jZahPdxzm9w==",
"dev": true,
"optional": true
},
"@esbuild/linux-s390x": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
- "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.4.tgz",
+ "integrity": "sha512-Iqc/l/FFwtt8FoTK9riYv9zQNms7B8u+vAI/rxKuN10HgQIXaPzKZc479lZ0x6+vKVQbu55GdpYpeNWzjOhgbA==",
"dev": true,
"optional": true
},
"@esbuild/linux-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
- "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.4.tgz",
+ "integrity": "sha512-Td9jv782UMAFsuLZINfUpoF5mZIbAj+jv1YVtE58rFtfvoKRiKSkRGQfHTgKamLVT/fO7203bHa3wU122V/Bdg==",
"dev": true,
"optional": true
},
"@esbuild/netbsd-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
- "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.4.tgz",
+ "integrity": "sha512-Awn38oSXxsPMQxaV0Ipb7W/gxZtk5Tx3+W+rAPdZkyEhQ6968r9NvtkjhnhbEgWXYbgV+JEONJ6PcdBS+nlcpA==",
"dev": true,
"optional": true
},
"@esbuild/openbsd-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
- "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.4.tgz",
+ "integrity": "sha512-IsUmQeCY0aU374R82fxIPu6vkOybWIMc3hVGZ3ChRwL9hA1TwY+tS0lgFWV5+F1+1ssuvvXt3HFqe8roCip8Hg==",
"dev": true,
"optional": true
},
"@esbuild/sunos-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
- "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.4.tgz",
+ "integrity": "sha512-hsKhgZ4teLUaDA6FG/QIu2q0rI6I36tZVfM4DBZv3BG0mkMIdEnMbhc4xwLvLJSS22uWmaVkFkqWgIS0gPIm+A==",
"dev": true,
"optional": true
},
"@esbuild/win32-arm64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
- "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.4.tgz",
+ "integrity": "sha512-UUfMgMoXPoA/bvGUNfUBFLCh0gt9dxZYIx9W4rfJr7+hKe5jxxHmfOK8YSH4qsHLLN4Ck8JZ+v7Q5fIm1huErg==",
"dev": true,
"optional": true
},
"@esbuild/win32-ia32": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
- "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.4.tgz",
+ "integrity": "sha512-yIxbspZb5kGCAHWm8dexALQ9en1IYDfErzjSEq1KzXFniHv019VT3mNtTK7t8qdy4TwT6QYHI9sEZabONHg+aw==",
"dev": true,
"optional": true
},
"@esbuild/win32-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
- "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.4.tgz",
+ "integrity": "sha512-sywLRD3UK/qRJt0oBwdpYLBibk7KiRfbswmWRDabuncQYSlf8aLEEUor/oP6KRz8KEG+HoiVLBhPRD5JWjS8Sg==",
"dev": true,
"optional": true
},
@@ -3135,9 +3134,9 @@
"integrity": "sha512-uYhXUrL/gn92mfqhjAwH2+yGOpjloBxj9ekoL4BhUsKcyJMpEg6WlNf3S3si+5x9zlbHHe7FYQNjZEbz1ymI9Q=="
},
"@vscode/vsce": {
- "version": "2.26.0",
- "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.26.0.tgz",
- "integrity": "sha512-v54ltgMzUG8lGY0kAgaOlry57xse1RlWzes9FotfGEx+Fr05KeR8rZicQzEMDmi9QnOgVWHuiEq+xA2HWkAz+Q==",
+ "version": "2.26.1",
+ "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.26.1.tgz",
+ "integrity": "sha512-QOG6Ht7V93nhwcBxPWcG33UK0qDGEoJdg0xtVeaTN27W6PGdMJUJGTPhB/sNHUIFKwvwzv/zMAHvDgMNXbcwlA==",
"dev": true,
"requires": {
"@azure/identity": "^4.1.0",
@@ -3596,34 +3595,34 @@
"dev": true
},
"esbuild": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
- "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.4.tgz",
+ "integrity": "sha512-sFMcNNrj+Q0ZDolrp5pDhH0nRPN9hLIM3fRPwgbLYJeSHHgnXSnbV3xYgSVuOeLWH9c73VwmEverVzupIv5xuA==",
"dev": true,
"requires": {
- "@esbuild/aix-ppc64": "0.20.2",
- "@esbuild/android-arm": "0.20.2",
- "@esbuild/android-arm64": "0.20.2",
- "@esbuild/android-x64": "0.20.2",
- "@esbuild/darwin-arm64": "0.20.2",
- "@esbuild/darwin-x64": "0.20.2",
- "@esbuild/freebsd-arm64": "0.20.2",
- "@esbuild/freebsd-x64": "0.20.2",
- "@esbuild/linux-arm": "0.20.2",
- "@esbuild/linux-arm64": "0.20.2",
- "@esbuild/linux-ia32": "0.20.2",
- "@esbuild/linux-loong64": "0.20.2",
- "@esbuild/linux-mips64el": "0.20.2",
- "@esbuild/linux-ppc64": "0.20.2",
- "@esbuild/linux-riscv64": "0.20.2",
- "@esbuild/linux-s390x": "0.20.2",
- "@esbuild/linux-x64": "0.20.2",
- "@esbuild/netbsd-x64": "0.20.2",
- "@esbuild/openbsd-x64": "0.20.2",
- "@esbuild/sunos-x64": "0.20.2",
- "@esbuild/win32-arm64": "0.20.2",
- "@esbuild/win32-ia32": "0.20.2",
- "@esbuild/win32-x64": "0.20.2"
+ "@esbuild/aix-ppc64": "0.21.4",
+ "@esbuild/android-arm": "0.21.4",
+ "@esbuild/android-arm64": "0.21.4",
+ "@esbuild/android-x64": "0.21.4",
+ "@esbuild/darwin-arm64": "0.21.4",
+ "@esbuild/darwin-x64": "0.21.4",
+ "@esbuild/freebsd-arm64": "0.21.4",
+ "@esbuild/freebsd-x64": "0.21.4",
+ "@esbuild/linux-arm": "0.21.4",
+ "@esbuild/linux-arm64": "0.21.4",
+ "@esbuild/linux-ia32": "0.21.4",
+ "@esbuild/linux-loong64": "0.21.4",
+ "@esbuild/linux-mips64el": "0.21.4",
+ "@esbuild/linux-ppc64": "0.21.4",
+ "@esbuild/linux-riscv64": "0.21.4",
+ "@esbuild/linux-s390x": "0.21.4",
+ "@esbuild/linux-x64": "0.21.4",
+ "@esbuild/netbsd-x64": "0.21.4",
+ "@esbuild/openbsd-x64": "0.21.4",
+ "@esbuild/sunos-x64": "0.21.4",
+ "@esbuild/win32-arm64": "0.21.4",
+ "@esbuild/win32-ia32": "0.21.4",
+ "@esbuild/win32-x64": "0.21.4"
}
},
"escape-string-regexp": {
@@ -4083,6 +4082,7 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
"requires": {
"yallist": "^4.0.0"
}
@@ -4449,12 +4449,9 @@
"dev": true
},
"semver": {
- "version": "7.6.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
- "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
- "requires": {
- "lru-cache": "^6.0.0"
- }
+ "version": "7.6.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
+ "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w=="
},
"set-blocking": {
"version": "2.0.0",
@@ -4772,7 +4769,8 @@
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
- "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
},
"yauzl": {
"version": "2.10.0",
diff --git a/code/package.json b/code/package.json
index 9d738186d..17c70e16f 100644
--- a/code/package.json
+++ b/code/package.json
@@ -31,15 +31,15 @@
],
"dependencies": {
"@vscode/python-extension": "^1.0.5",
- "semver": "^7.6.0",
+ "semver": "^7.6.2",
"vscode-languageclient": "^9.0.1"
},
"devDependencies": {
"@types/glob": "^8.1.0",
"@types/node": "^18",
"@types/vscode": "1.78.0",
- "@vscode/vsce": "^2.26.0",
- "esbuild": "^0.20.2",
+ "@vscode/vsce": "^2.26.1",
+ "esbuild": "^0.21.4",
"ovsx": "^0.9.1",
"typescript": "^5.4.5"
},
diff --git a/code/requirements.txt b/code/requirements.txt
index fcd9a23a3..24d3a5fc5 100644
--- a/code/requirements.txt
+++ b/code/requirements.txt
@@ -2,21 +2,21 @@
# This file is autogenerated by pip-compile with Python 3.8
# by the following command:
#
-# pip-compile --generate-hashes ./requirements.in
+# pip-compile --generate-hashes requirements.in
#
aiosqlite==0.20.0 \
--hash=sha256:36a1deaca0cac40ebe32aac9977a6e2bbc7f5189f23f4a54d5908986729e5bd6 \
--hash=sha256:6d35c8c256637f4672f843c31021464090805bf925385ac39473fb16eaaca3d7
# via -r requirements.in
-attrs==23.1.0 \
- --hash=sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04 \
- --hash=sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015
+attrs==23.2.0 \
+ --hash=sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30 \
+ --hash=sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1
# via
# cattrs
# lsprotocol
-cattrs==23.2.2 \
- --hash=sha256:66064e2060ea207c5a48d065ab1910c10bb8108c28f3df8d1a7b1aa6b19d191b \
- --hash=sha256:b790b1c2be1ce042611e33f740e343c2593918bbf3c1cc88cdddac4defc09655
+cattrs==23.2.3 \
+ --hash=sha256:0341994d94971052e9ee70662542699a3162ea1e0c62f7ce1b4a57f563685108 \
+ --hash=sha256:a934090d95abaa9e911dac357e3a8699e0b4b14f8529bcc7d2b1ad9d51672b9f
# via
# lsprotocol
# pygls
@@ -24,17 +24,17 @@ docutils==0.20.1 \
--hash=sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6 \
--hash=sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b
# via -r requirements.in
-exceptiongroup==1.2.0 \
- --hash=sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14 \
- --hash=sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68
+exceptiongroup==1.2.1 \
+ --hash=sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad \
+ --hash=sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16
# via cattrs
lsprotocol==2023.0.1 \
--hash=sha256:c75223c9e4af2f24272b14c6375787438279369236cd568f596d4951052a60f2 \
--hash=sha256:cc5c15130d2403c18b734304339e51242d3018a05c4f7d0f198ad6e0cd21861d
# via pygls
-platformdirs==4.2.0 \
- --hash=sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068 \
- --hash=sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768
+platformdirs==4.2.2 \
+ --hash=sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee \
+ --hash=sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3
# via -r requirements.in
pygls==1.3.1 \
--hash=sha256:140edceefa0da0e9b3c533547c892a42a7d2fd9217ae848c330c53d266a55018 \
@@ -44,9 +44,9 @@ tomli==2.0.1 \
--hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
--hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
# via -r requirements.in
-typing-extensions==4.8.0 \
- --hash=sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0 \
- --hash=sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef
+typing-extensions==4.11.0 \
+ --hash=sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0 \
+ --hash=sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a
# via
# aiosqlite
# cattrs
diff --git a/code/tox.ini b/code/tox.ini
deleted file mode 100644
index 0296ac9d1..000000000
--- a/code/tox.ini
+++ /dev/null
@@ -1,23 +0,0 @@
-[tox]
-min_version = 4.0
-
-[testenv:bundle-deps]
-basepython = python3.8
-description = Install dependencies
-skip_install = true
-commands =
- python --version
- python -c "import sys; v = sys.version_info; sys.exit(v.major != 3 or v.minor != 8)"
- python -m pip install -t ./bundled/libs --no-cache-dir --implementation py --no-deps --upgrade -r ./requirements.txt
- python -m pip install -t ./bundled/libs --no-cache-dir --implementation py --no-deps --upgrade {env:ESBONIO_WHL}
-
-[testenv:update-deps]
-basepython = python3.8
-description = Update bundled dependency versions
-skip_install = true
-deps =
- pip-tools
-commands =
- python --version
- python -c "import sys; v = sys.version_info; sys.exit(v.major != 3 or v.minor != 8)"
- pip-compile --resolver=backtracking --generate-hashes --upgrade ./requirements.in
diff --git a/docs/ext/domain.py b/docs/ext/domain.py
index 6dda6bbf3..39b4d8ba9 100644
--- a/docs/ext/domain.py
+++ b/docs/ext/domain.py
@@ -1,4 +1,6 @@
-from typing import Dict
+from __future__ import annotations
+
+import typing
from docutils.parsers.rst import directives
from sphinx import addnodes
@@ -7,7 +9,19 @@
from sphinx.domains import Domain
from sphinx.domains import ObjType
from sphinx.roles import XRefRole
-from sphinx.util.typing import OptionSpec
+from sphinx.util.nodes import make_id
+from sphinx.util.nodes import make_refnode
+
+if typing.TYPE_CHECKING:
+ from typing import Dict
+ from typing import Optional
+ from typing import Tuple
+
+ from docutils.nodes import Element
+ from sphinx.addnodes import pending_xref
+ from sphinx.builders import Builder
+ from sphinx.environment import BuildEnvironment
+ from sphinx.util.typing import OptionSpec
def config_scope(argument: str):
@@ -45,6 +59,15 @@ def handle_signature(self, sig: str, signode: addnodes.desc_signature) -> str:
signode += addnodes.desc_name(sig, sig)
return sig
+ def add_target_and_index(
+ self, name: str, sig: str, signode: addnodes.desc_signature
+ ) -> None:
+ node_id = make_id(self.env, self.state.document, term=name)
+ signode["ids"].append(node_id)
+
+ domain: EsbonioDomain = self.env.domains["esbonio"]
+ domain.config_values[name] = (self.env.docname, node_id)
+
class EsbonioDomain(Domain):
"""A domain dedicated to documenting the esbonio language server"""
@@ -64,6 +87,33 @@ class EsbonioDomain(Domain):
"conf": XRefRole(),
}
+ initial_data = {
+ "config_values": {},
+ }
+
+ @property
+ def config_values(self) -> dict[str, Tuple[str, str]]:
+ return self.data.setdefault("config_values", {})
+
+ def resolve_xref(
+ self,
+ env: BuildEnvironment,
+ fromdocname: str,
+ builder: Builder,
+ type: str,
+ target: str,
+ node: pending_xref,
+ contnode: Element,
+ ) -> Optional[Element]:
+ """Resolve cross references"""
+
+ if (entry := self.config_values.get(target, None)) is None:
+ return None
+
+ return make_refnode(
+ builder, fromdocname, entry[0], entry[1], [contnode], target
+ )
+
def setup(app: Sphinx):
app.add_domain(EsbonioDomain)
diff --git a/docs/lsp/howto/migrate-to-v1.rst b/docs/lsp/howto/migrate-to-v1.rst
index 16fed68ba..fb9d0d9ba 100644
--- a/docs/lsp/howto/migrate-to-v1.rst
+++ b/docs/lsp/howto/migrate-to-v1.rst
@@ -15,13 +15,15 @@ This guide covers the breaking changes between the ``v0.x`` and ``v1.x`` version
Installation Changes
--------------------
-Previously, you would have had to install ``esbonio`` as a development dependency for every project you wished to use it in.
-In ``v1.x`` this is no longer necessary, in fact, it's recommended you remove it from all of your project specific environments::
+Previously, it was recommended to install ``esbonio`` as a development dependency for each project you wished to use with.
+This was because ``esbonio`` would run Sphinx as part of its own process and therefore need access to your project's dependencies.
+
+In ``v1.x`` Sphinx is now run in a separate process so this is no longer necessary, in fact, it's recommended you remove it from your project specific environments::
(env) $ pip uninstall esbonio
-Instead, you should now have a single, global installation that can be reused across projects.
-We recommend that you use `pipx `__ to manage this installation for you::
+Instead, you can now have a single, global installation that is reused across projects.
+It's recommended that you use `pipx `__ to manage this installation for you::
$ pipx install esbonio # Installs esbonio globally in an isolated environment
$ pipx upgrade esbonio # Upgrade esbonio and its dependencies
@@ -37,7 +39,7 @@ Configuration Changes
With the release of ``v1.x``, Esbonio's configuration system has been overhauled, see :ref:`lsp-configuration` for all of the available configuration options and methods.
-While ``esbonio`` can now be installed globally, it still needs access to your project's development environment in order to properly understand it.
+While ``esbonio`` can now be installed globally, it still needs access to your project's development environment in order launch Sphinx correctly.
This means the two most imporant configuration values are
- :esbonio:conf:`esbonio.sphinx.pythonCommand`: For telling ``esbonio`` the command it needs to run in order to use the correct Python environment.
@@ -45,32 +47,44 @@ This means the two most imporant configuration values are
The following table outlines the configuration options that have been removed in ``v1.x`` and what their correpsonding replacement is
-+-----------------------------------------+-------------------------------------------------+-------------+
-| Removed Option | Replacement | Notes |
-+=========================================+=================================================+=============+
-| - ``esbonio.server.hideSphinxOutput`` | :esbonio:conf:`esbonio.sphinx.buildCommand` | |
-| - ``esbonio.sphinx.buildDir`` | | |
-| - ``esbonio.sphinx.builderName`` | | |
-| - ``esbonio.sphinx.confDir`` | | |
-| - ``esbonio.sphinx.doctreeDir`` | | |
-| - ``esbonio.sphinx.forceFullBuild`` | | |
-| - ``esbonio.sphinx.keepGoing`` | | |
-| - ``esbonio.sphinx.makeMode`` | | |
-| - ``esbonio.sphinx.numJobs`` | | |
-| - ``esbonio.sphinx.quiet`` | | |
-| - ``esbonio.sphinx.silent`` | | |
-| - ``esbonio.sphinx.srcDir`` | | |
-| - ``esbonio.sphinx.tags`` | | |
-| - ``esbonio.sphinx.verbosity`` | | |
-| - ``esbonio.sphinx.warningIsError`` | | |
-+-----------------------------------------+-------------------------------------------------+-------------+
-| ``esbonio.server.logLevel`` | :esbonio:conf:`esbonio.logging.level` | |
-+-----------------------------------------+-------------------------------------------------+-------------+
-| ``esbonio.server.logFilter`` | :esbonio:conf:`esbonio.logging.config` | |
-+-----------------------------------------+-------------------------------------------------+-------------+
-| ``esbonio.server.enabledInPyFiles`` | :esbonio:conf:`esbonio.server.documentSelector` | VSCode only |
-+-----------------------------------------+-------------------------------------------------+-------------+
-| - ``esbonio.server.installBehavior`` | N/A | VSCode only,|
-| - ``esbonio.server.updateBehavior`` | | no longer |
-| - ``esbonio.server.updateFrequency`` | | required. |
-+-----------------------------------------+-------------------------------------------------+-------------+
++-----------------------------------------+-------------------------------------------------+--------------------------------------------------------------+
+| Removed Option | Replacement | Notes |
++=========================================+=================================================+==============================================================+
+| - ``esbonio.sphinx.builderName`` | :esbonio:conf:`esbonio.sphinx.buildCommand` | Pass ``-b `` to |
+| - ``esbonio.sphinx.srcDir`` | | ``sphinx-build`` |
+| - ``esbonio.sphinx.buildDir`` | | |
++-----------------------------------------+-------------------------------------------------+--------------------------------------------------------------+
+| ``esbonio.sphinx.confDir`` | :esbonio:conf:`esbonio.sphinx.buildCommand` | Use ``-c `` |
++-----------------------------------------+-------------------------------------------------+--------------------------------------------------------------+
+| ``esbonio.sphinx.doctreeDir`` | :esbonio:conf:`esbonio.sphinx.buildCommand` | Use ``-d `` |
++-----------------------------------------+-------------------------------------------------+--------------------------------------------------------------+
+| ``esbonio.sphinx.forceFullBuild`` | :esbonio:conf:`esbonio.sphinx.buildCommand` | Use ``-E`` |
++-----------------------------------------+-------------------------------------------------+--------------------------------------------------------------+
+| ``esbonio.sphinx.keepGoing`` | :esbonio:conf:`esbonio.sphinx.buildCommand` | Use ``--keep-going`` |
++-----------------------------------------+-------------------------------------------------+--------------------------------------------------------------+
+| ``esbonio.sphinx.makeMode`` | :esbonio:conf:`esbonio.sphinx.buildCommand` | Pass ``-M `` to |
+| | | ``sphinx-build`` |
++-----------------------------------------+-------------------------------------------------+--------------------------------------------------------------+
+| ``esbonio.sphinx.numJobs`` | :esbonio:conf:`esbonio.sphinx.buildCommand` | Use ``-j `` |
++-----------------------------------------+-------------------------------------------------+--------------------------------------------------------------+
+| ``esbonio.sphinx.quiet`` | :esbonio:conf:`esbonio.sphinx.buildCommand` | Use ``-q`` |
++-----------------------------------------+-------------------------------------------------+--------------------------------------------------------------+
+| ``esbonio.sphinx.tags`` | :esbonio:conf:`esbonio.sphinx.buildCommand` | Use ``-t`` |
++-----------------------------------------+-------------------------------------------------+--------------------------------------------------------------+
+| ``esbonio.sphinx.verbosity`` | :esbonio:conf:`esbonio.sphinx.buildCommand` | Use ``-v`` |
++-----------------------------------------+-------------------------------------------------+--------------------------------------------------------------+
+| ``esbonio.sphinx.warningIsError`` | :esbonio:conf:`esbonio.sphinx.buildCommand` | Use ``-W`` |
++-----------------------------------------+-------------------------------------------------+--------------------------------------------------------------+
+| - ``esbonio.server.hideSphinxOutput`` | :esbonio:conf:`esbonio.sphinx.buildCommand` | Use ``-Q`` |
+| - ``esbonio.sphinx.silent`` | | |
++-----------------------------------------+-------------------------------------------------+--------------------------------------------------------------+
+| ``esbonio.server.logLevel`` | :esbonio:conf:`esbonio.logging.level` | |
++-----------------------------------------+-------------------------------------------------+--------------------------------------------------------------+
+| ``esbonio.server.logFilter`` | :esbonio:conf:`esbonio.logging.config` | |
++-----------------------------------------+-------------------------------------------------+--------------------------------------------------------------+
+| ``esbonio.server.enabledInPyFiles`` | :esbonio:conf:`esbonio.server.documentSelector` | VSCode only |
++-----------------------------------------+-------------------------------------------------+--------------------------------------------------------------+
+| - ``esbonio.server.installBehavior`` | N/A | VSCode only, no longer required. |
+| - ``esbonio.server.updateBehavior`` | | |
+| - ``esbonio.server.updateFrequency`` | | |
++-----------------------------------------+-------------------------------------------------+--------------------------------------------------------------+
diff --git a/docs/lsp/reference/configuration.rst b/docs/lsp/reference/configuration.rst
index d377e60c6..e58cf7b8d 100644
--- a/docs/lsp/reference/configuration.rst
+++ b/docs/lsp/reference/configuration.rst
@@ -286,17 +286,6 @@ Preview
The following options control the behavior of the preview
-.. esbonio:config:: esbonio.sphinx.enableSyncScrolling
- :scope: project
- :type: boolean
-
- Enable support for syncronsied scrolling between the editor and preview pane
-
- .. note::
-
- In order to use syncronised scrolling, dedicated support for it needs to be implemented by your language client.
- See :ref:`lsp-feat-sync-scrolling` for details.
-
.. esbonio:config:: esbonio.preview.bind
:scope: project
:type: string
diff --git a/lib/esbonio/.gitignore b/lib/esbonio/.gitignore
index b2be92b7d..3b0d2daec 100644
--- a/lib/esbonio/.gitignore
+++ b/lib/esbonio/.gitignore
@@ -1 +1,2 @@
+.coverage*
result
diff --git a/lib/esbonio/Makefile b/lib/esbonio/Makefile
index 03bcea6f2..5573879f5 100644
--- a/lib/esbonio/Makefile
+++ b/lib/esbonio/Makefile
@@ -1,9 +1,17 @@
-PY ?= 310
+include ../../.devcontainer/tools.mk
-.PHONY: develop test
-develop:
- nix develop .#py$(PY)
+# Global flags to pass to hatch, e.g. -v, --no-color etc.
+HATCH_ARGS =
-test:
- nix develop .#py$(PY) --command pytest
+.PHONY: dist release test
+
+dist: $(HATCH)
+ $(HATCH) $(HATCH_ARGS) build
+
+test: ARGS ?= -i py=$(shell $(PY) -c 'import sys;v=sys.version_info;print(f"{v.major}.{v.minor}")')
+test: $(HATCH) $(PY)
+ $(HATCH) $(HATCH_ARGS) test $(ARGS)
+
+release: $(TOWNCRIER) $(HATCH) $(PY)
+ $(PY) ../scripts/make_release.py lsp
diff --git a/lib/esbonio/changes/413.api.md b/lib/esbonio/changes/413.api.md
new file mode 100644
index 000000000..a8fa3c4fd
--- /dev/null
+++ b/lib/esbonio/changes/413.api.md
@@ -0,0 +1 @@
+In the server's context `LanguageFeatures` can now define a `CompletionTrigger` which among other things, allows them to declare trigger characters
diff --git a/lib/esbonio/changes/782.fix.md b/lib/esbonio/changes/782.fix.md
new file mode 100644
index 000000000..0941c4744
--- /dev/null
+++ b/lib/esbonio/changes/782.fix.md
@@ -0,0 +1 @@
+The language server should now launch the correct version of the sphinx agent, even if an installation of `esbonio` exists in the target environment
diff --git a/lib/esbonio/changes/799.enhancement.md b/lib/esbonio/changes/799.enhancement.md
new file mode 100644
index 000000000..9f7655f9c
--- /dev/null
+++ b/lib/esbonio/changes/799.enhancement.md
@@ -0,0 +1 @@
+The server now includes the `eval-rst` directive in its completion suggestions for MyST files
diff --git a/lib/esbonio/changes/800.fix.md b/lib/esbonio/changes/800.fix.md
new file mode 100644
index 000000000..ea0a0973b
--- /dev/null
+++ b/lib/esbonio/changes/800.fix.md
@@ -0,0 +1 @@
+The server no longer raises `ValueErrors` when typing `:` characters in markdown files.
diff --git a/lib/esbonio/changes/810.fix.md b/lib/esbonio/changes/810.fix.md
new file mode 100644
index 000000000..f4a5713bd
--- /dev/null
+++ b/lib/esbonio/changes/810.fix.md
@@ -0,0 +1 @@
+The server should no longer throw path mount errors when used across partitions on Windows
diff --git a/lib/esbonio/changes/823.api.md b/lib/esbonio/changes/823.api.md
new file mode 100644
index 000000000..f14bf56a9
--- /dev/null
+++ b/lib/esbonio/changes/823.api.md
@@ -0,0 +1,2 @@
+In the Sphinx process, it is now possible for extensions to declare a role target provider through the `app.esbonio.create_role_target_provider` and `app.esbonio.add_role` methods.
+**Note:** This does require an associated implementation of the target provider on the server side.
diff --git a/lib/esbonio/changes/823.feature.md b/lib/esbonio/changes/823.feature.md
new file mode 100644
index 000000000..28410fcd0
--- /dev/null
+++ b/lib/esbonio/changes/823.feature.md
@@ -0,0 +1 @@
+Implement role target completions for MyST syntax
diff --git a/lib/esbonio/esbonio/server/__init__.py b/lib/esbonio/esbonio/server/__init__.py
index c06bbc762..4b7f4b238 100644
--- a/lib/esbonio/esbonio/server/__init__.py
+++ b/lib/esbonio/esbonio/server/__init__.py
@@ -4,6 +4,7 @@
from .events import EventSource
from .feature import CompletionConfig
from .feature import CompletionContext
+from .feature import CompletionTrigger
from .feature import LanguageFeature
from .server import EsbonioLanguageServer
from .server import EsbonioWorkspace
@@ -15,6 +16,7 @@
"ConfigChangeEvent",
"CompletionConfig",
"CompletionContext",
+ "CompletionTrigger",
"EsbonioLanguageServer",
"EsbonioWorkspace",
"EventSource",
diff --git a/lib/esbonio/esbonio/server/cli.py b/lib/esbonio/esbonio/server/cli.py
index 2de49a408..343a501fb 100644
--- a/lib/esbonio/esbonio/server/cli.py
+++ b/lib/esbonio/esbonio/server/cli.py
@@ -70,6 +70,10 @@ def main(argv: Optional[Sequence[str]] = None):
"esbonio.server.features.preview_manager",
"esbonio.server.features.directives",
"esbonio.server.features.roles",
+ "esbonio.server.features.rst.directives",
+ "esbonio.server.features.rst.roles",
+ "esbonio.server.features.myst.directives",
+ "esbonio.server.features.myst.roles",
"esbonio.server.features.sphinx_support.diagnostics",
"esbonio.server.features.sphinx_support.symbols",
"esbonio.server.features.sphinx_support.directives",
diff --git a/lib/esbonio/esbonio/server/events.py b/lib/esbonio/esbonio/server/events.py
index d44e16334..255ee6299 100644
--- a/lib/esbonio/esbonio/server/events.py
+++ b/lib/esbonio/esbonio/server/events.py
@@ -21,7 +21,6 @@ class EventSource:
# etc know which events are possible etc.
def __init__(self, logger: Optional[logging.Logger] = None):
-
self.logger = logger or logging.getLogger(__name__)
"""The logging instance to use."""
diff --git a/lib/esbonio/esbonio/server/feature.py b/lib/esbonio/esbonio/server/feature.py
index e34712a5d..787610201 100644
--- a/lib/esbonio/esbonio/server/feature.py
+++ b/lib/esbonio/esbonio/server/feature.py
@@ -16,6 +16,7 @@
from typing import Coroutine
from typing import List
from typing import Optional
+ from typing import Set
from typing import Union
from .server import EsbonioLanguageServer
@@ -81,7 +82,7 @@ def document_open(self, params: types.DidOpenTextDocumentParams) -> MaybeAsyncNo
def document_save(self, params: types.DidSaveTextDocumentParams) -> MaybeAsyncNone:
"""Called when a text document is saved."""
- completion_triggers: List["re.Pattern"] = []
+ completion_trigger: Optional[CompletionTrigger] = None
def completion(self, context: CompletionContext) -> CompletionResult:
"""Called when a completion request matches one of the specified triggers."""
@@ -90,13 +91,110 @@ def document_symbol(
self, params: types.DocumentSymbolParams
) -> DocumentSymbolResult:
"""Called when a document symbols request is received."""
- ...
def workspace_symbol(
self, params: types.WorkspaceSymbolParams
) -> WorkspaceSymbolResult:
"""Called when a workspace symbols request is received."""
- ...
+
+
+@attrs.define
+class CompletionTrigger:
+ """Define when the feature's completion method should be called."""
+
+ patterns: List[re.Pattern]
+ """A list of regular expressions to try"""
+
+ languages: Set[str] = attrs.field(factory=set)
+ """Languages in which the completion trigger should fire.
+
+ If empty, the document's language will be ignored.
+ """
+
+ characters: Set[str] = attrs.field(factory=set)
+ """Characters which, when typed, should trigger a completion request.
+
+ If empty, this trigger will ignore any trigger characters.
+ """
+
+ def __call__(
+ self,
+ uri: Uri,
+ params: types.CompletionParams,
+ document: TextDocument,
+ language: str,
+ client_capabilities: types.ClientCapabilities,
+ ) -> Optional[CompletionContext]:
+ """Determine if this completion trigger should fire.
+
+ Parameters
+ ----------
+ uri
+ The uri of the document in which the completion request was made
+
+ params
+ The completion params sent from the client
+
+ document
+ The document in which the completion request was made
+
+ language
+ The language at the point where the completion request was made
+
+ client_capabilities
+ The client's capabilities
+
+ Returns
+ -------
+ Optional[CompletionContext]
+ A completion context, if this trigger has fired
+ """
+
+ if len(self.languages) > 0 and language not in self.languages:
+ return None
+
+ if not self._trigger_characters_match(params):
+ return None
+
+ try:
+ line = document.lines[params.position.line]
+ except IndexError:
+ line = ""
+
+ for pattern in self.patterns:
+ for match in pattern.finditer(line):
+ # Only trigger completions if the position of the request is within the
+ # match.
+ start, stop = match.span()
+ if not (start <= params.position.character <= stop):
+ continue
+
+ return CompletionContext(
+ uri=uri,
+ doc=document,
+ match=match,
+ position=params.position,
+ language=language,
+ capabilities=client_capabilities,
+ )
+
+ return None
+
+ def _trigger_characters_match(self, params: types.CompletionParams) -> bool:
+ """Determine if this trigger's completion characters align with the request."""
+
+ if (context := params.context) is None:
+ # No context available, assume a match
+ return True
+
+ if context.trigger_kind != types.CompletionTriggerKind.TriggerCharacter:
+ # Not a trigger character request, assume a match
+ return True
+
+ if (char := context.trigger_character) is None or len(self.characters) == 0:
+ return True
+
+ return char in self.characters
@attrs.define
@@ -120,12 +218,15 @@ class CompletionContext:
doc: TextDocument
"""The document within which the completion request was made."""
- match: "re.Match"
+ match: re.Match
"""The match object describing the site of the completion request."""
position: types.Position
"""The position at which the completion request was made."""
+ language: str
+ """The language where the completion request was made."""
+
capabilities: types.ClientCapabilities
"""The client's capabilities."""
diff --git a/lib/esbonio/esbonio/server/features/directives/__init__.py b/lib/esbonio/esbonio/server/features/directives/__init__.py
index fdbc4d461..5d03bc22e 100644
--- a/lib/esbonio/esbonio/server/features/directives/__init__.py
+++ b/lib/esbonio/esbonio/server/features/directives/__init__.py
@@ -4,13 +4,8 @@
import typing
import attrs
-from lsprotocol import types
from esbonio import server
-from esbonio.sphinx_agent.types import MYST_DIRECTIVE
-from esbonio.sphinx_agent.types import RST_DIRECTIVE
-
-from . import completion
if typing.TYPE_CHECKING:
from typing import Any
@@ -45,13 +40,16 @@ def suggest_directives(
class DirectiveFeature(server.LanguageFeature):
- """Support for directives."""
+ """'Backend' support for directives.
+
+ It's this language feature's responsibility to provide an API that exposes the
+ information a "frontend" language feature may want.
+ """
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._providers: Dict[int, DirectiveProvider] = {}
- self._insert_behavior = "replace"
def add_provider(self, provider: DirectiveProvider):
"""Register a directive provider.
@@ -63,72 +61,6 @@ def add_provider(self, provider: DirectiveProvider):
"""
self._providers[id(provider)] = provider
- completion_triggers = [RST_DIRECTIVE, MYST_DIRECTIVE]
-
- def initialized(self, params: types.InitializedParams):
- """Called once the initial handshake between client and server has finished."""
- self.configuration.subscribe(
- "esbonio.server.completion",
- server.CompletionConfig,
- self.update_configuration,
- )
-
- def update_configuration(
- self, event: server.ConfigChangeEvent[server.CompletionConfig]
- ):
- """Called when the user's configuration is updated."""
- self._insert_behavior = event.value.preferred_insert_behavior
-
- async def completion(
- self, context: server.CompletionContext
- ) -> Optional[List[types.CompletionItem]]:
- """Provide completion suggestions for directives."""
-
- groups = context.match.groupdict()
-
- # Are we completing a directive's options?
- if "directive" not in groups:
- return await self.complete_options(context)
-
- # Don't offer completions for targets
- if (groups["name"] or "").startswith("_"):
- return None
-
- # Are we completing the directive's argument?
- directive_end = context.match.span()[0] + len(groups["directive"])
- complete_directive = groups["directive"].endswith(("::", "}"))
-
- if complete_directive and directive_end < context.position.character:
- return await self.complete_arguments(context)
-
- return await self.complete_directives(context)
-
- async def complete_options(self, context: server.CompletionContext):
- return None
-
- async def complete_arguments(self, context: server.CompletionContext):
- return None
-
- async def complete_directives(
- self, context: server.CompletionContext
- ) -> Optional[List[types.CompletionItem]]:
- """Return completion suggestions for the available directives."""
-
- language = self.server.get_language_at(context.doc, context.position)
- render_func = completion.get_directive_renderer(language, self._insert_behavior)
- if render_func is None:
- return None
-
- items = []
- for directive in await self.suggest_directives(context):
- if (item := render_func(context, directive)) is not None:
- items.append(item)
-
- if len(items) > 0:
- return items
-
- return None
-
async def suggest_directives(
self, context: server.CompletionContext
) -> List[Directive]:
diff --git a/lib/esbonio/esbonio/server/features/myst/__init__.py b/lib/esbonio/esbonio/server/features/myst/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/lib/esbonio/esbonio/server/features/myst/directives.py b/lib/esbonio/esbonio/server/features/myst/directives.py
new file mode 100644
index 000000000..aec577aca
--- /dev/null
+++ b/lib/esbonio/esbonio/server/features/myst/directives.py
@@ -0,0 +1,107 @@
+from __future__ import annotations
+
+import typing
+
+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
+
+if typing.TYPE_CHECKING:
+ from typing import List
+ from typing import Optional
+
+
+class MystDirectives(server.LanguageFeature):
+ """A frontend to directives for MyST syntax."""
+
+ def __init__(self, directives: DirectiveFeature, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+ self.directives = directives
+ self._insert_behavior = "replace"
+
+ completion_trigger = server.CompletionTrigger(
+ patterns=[MYST_DIRECTIVE],
+ languages={"markdown"},
+ characters={".", "`", "/"},
+ )
+
+ def initialized(self, params: types.InitializedParams):
+ """Called once the initial handshake between client and server has finished."""
+ self.configuration.subscribe(
+ "esbonio.server.completion",
+ server.CompletionConfig,
+ self.update_configuration,
+ )
+
+ def update_configuration(
+ self, event: server.ConfigChangeEvent[server.CompletionConfig]
+ ):
+ """Called when the user's configuration is updated."""
+ self._insert_behavior = event.value.preferred_insert_behavior
+
+ async def completion(
+ self, context: server.CompletionContext
+ ) -> Optional[List[types.CompletionItem]]:
+ """Provide completion suggestions for directives."""
+
+ groups = context.match.groupdict()
+
+ # Are we completing a directive's options?
+ if "directive" not in groups:
+ return await self.complete_options(context)
+
+ # Don't offer completions for targets
+ if (groups["name"] or "").startswith("_"):
+ return None
+
+ # Are we completing the directive's argument?
+ directive_end = context.match.span()[0] + len(groups["directive"])
+ complete_directive = groups["directive"].endswith("}")
+
+ if complete_directive and directive_end < context.position.character:
+ return await self.complete_arguments(context)
+
+ return await self.complete_directives(context)
+
+ async def complete_options(self, context: server.CompletionContext):
+ return None
+
+ async def complete_arguments(self, context: server.CompletionContext):
+ return None
+
+ async def complete_directives(
+ self, context: server.CompletionContext
+ ) -> Optional[List[types.CompletionItem]]:
+ """Return completion suggestions for the available directives."""
+
+ render_func = completion.get_directive_renderer(
+ context.language, self._insert_behavior
+ )
+ if render_func is None:
+ return None
+
+ items = []
+
+ # Include the special `eval-rst` directive
+ eval_rst = Directive("eval-rst", implementation=None)
+ if (item := render_func(context, eval_rst)) is not None:
+ items.append(item)
+
+ for directive in await self.directives.suggest_directives(context):
+ if (item := render_func(context, directive)) is not None:
+ items.append(item)
+
+ if len(items) > 0:
+ return items
+
+ return None
+
+
+def esbonio_setup(esbonio: server.EsbonioLanguageServer, directives: DirectiveFeature):
+ myst_directives = MystDirectives(directives, esbonio)
+ esbonio.add_feature(myst_directives)
diff --git a/lib/esbonio/esbonio/server/features/myst/roles.py b/lib/esbonio/esbonio/server/features/myst/roles.py
new file mode 100644
index 000000000..dbc01bda0
--- /dev/null
+++ b/lib/esbonio/esbonio/server/features/myst/roles.py
@@ -0,0 +1,109 @@
+from __future__ import annotations
+
+import typing
+
+from lsprotocol import types
+
+from esbonio import server
+from esbonio.server.features.roles import RolesFeature
+from esbonio.server.features.roles import completion
+from esbonio.sphinx_agent.types import MYST_ROLE
+
+if typing.TYPE_CHECKING:
+ from typing import List
+ from typing import Optional
+
+
+class MystRoles(server.LanguageFeature):
+ """A frontend to roles for MyST syntax."""
+
+ def __init__(self, roles: RolesFeature, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+ self.roles = roles
+ self._insert_behavior = "replace"
+
+ completion_trigger = server.CompletionTrigger(
+ patterns=[MYST_ROLE],
+ languages={"markdown"},
+ characters={"{", "`", "<", "/"},
+ )
+
+ def initialized(self, params: types.InitializedParams):
+ """Called once the initial handshake between client and server has finished."""
+ self.configuration.subscribe(
+ "esbonio.server.completion",
+ server.CompletionConfig,
+ self.update_configuration,
+ )
+
+ def update_configuration(
+ self, event: server.ConfigChangeEvent[server.CompletionConfig]
+ ):
+ """Called when the user's configuration is updated."""
+ self._insert_behavior = event.value.preferred_insert_behavior
+
+ async def completion(
+ self, context: server.CompletionContext
+ ) -> Optional[List[types.CompletionItem]]:
+ """Provide completion suggestions for roles."""
+
+ groups = context.match.groupdict()
+ target = groups["target"]
+
+ # All text matched by the regex
+ text = context.match.group(0)
+ start, end = context.match.span()
+
+ if target:
+ target_index = start + text.find(target)
+
+ # Only trigger target completions if the request was made from within
+ # the target part of the role.
+ if target_index <= context.position.character <= end:
+ return await self.complete_targets(context)
+
+ return await self.complete_roles(context)
+
+ async def complete_targets(self, context: server.CompletionContext):
+ """Provide completion suggestions for role targets."""
+
+ render_func = completion.get_role_target_renderer(
+ context.language, self._insert_behavior
+ )
+ if render_func is None:
+ return None
+
+ items = []
+ role_name = context.match.group("name")
+ for target in await self.roles.suggest_targets(context, role_name):
+ if (item := render_func(context, target)) is not None:
+ items.append(item)
+
+ return items if len(items) > 0 else None
+
+ async def complete_roles(
+ self, context: server.CompletionContext
+ ) -> Optional[List[types.CompletionItem]]:
+ """Return completion suggestions for the available roles"""
+
+ render_func = completion.get_role_renderer(
+ context.language, self._insert_behavior
+ )
+ if render_func is None:
+ return None
+
+ items = []
+ for role in await self.roles.suggest_roles(context):
+ if (item := render_func(context, role)) is not None:
+ items.append(item)
+
+ if len(items) > 0:
+ return items
+
+ return None
+
+
+def esbonio_setup(esbonio: server.EsbonioLanguageServer, roles: RolesFeature):
+ rst_roles = MystRoles(roles, esbonio)
+ esbonio.add_feature(rst_roles)
diff --git a/lib/esbonio/esbonio/server/features/preview_manager/__init__.py b/lib/esbonio/esbonio/server/features/preview_manager/__init__.py
index 8b44002ac..5436926f9 100644
--- a/lib/esbonio/esbonio/server/features/preview_manager/__init__.py
+++ b/lib/esbonio/esbonio/server/features/preview_manager/__init__.py
@@ -146,7 +146,6 @@ async def scroll_view(self, line: int):
self.webview.scroll(line)
async def preview_file(self, params, retry=True):
-
if self.preview is None:
return None
@@ -161,15 +160,12 @@ async def preview_file(self, params, retry=True):
return None
if (build_path := await project.get_build_path(src_uri)) is None:
-
# The client might not have built the project yet.
if client.id not in self.built_clients and retry is True:
-
# Only retry this once.
await self.sphinx.trigger_build(src_uri)
return await self.preview_file(params, retry=False)
else:
-
self.logger.debug(
"Unable to preview file '%s', not included in build output.",
src_uri,
diff --git a/lib/esbonio/esbonio/server/features/preview_manager/preview.py b/lib/esbonio/esbonio/server/features/preview_manager/preview.py
index a69d2aa46..1bae234d1 100644
--- a/lib/esbonio/esbonio/server/features/preview_manager/preview.py
+++ b/lib/esbonio/esbonio/server/features/preview_manager/preview.py
@@ -58,7 +58,6 @@ class PreviewServer:
"""The http server that serves the built content."""
def __init__(self, logger: logging.Logger, config: PreviewConfig, executor: Any):
-
self.config = config
"""The current configuration."""
diff --git a/lib/esbonio/esbonio/server/features/project_manager/manager.py b/lib/esbonio/esbonio/server/features/project_manager/manager.py
index 562eddb7f..052365b71 100644
--- a/lib/esbonio/esbonio/server/features/project_manager/manager.py
+++ b/lib/esbonio/esbonio/server/features/project_manager/manager.py
@@ -26,7 +26,7 @@ def __init__(self, *args, **kwargs):
def register_project(self, scope: str, dbpath: Union[str, pathlib.Path]):
"""Register a project."""
self.logger.debug("Registered project for scope '%s': '%s'", scope, dbpath)
- self.projects[scope] = Project(dbpath)
+ self.projects[scope] = Project(dbpath, self.converter)
def get_project(self, uri: Uri) -> Optional[Project]:
"""Return the project instance for the given uri, if available"""
diff --git a/lib/esbonio/esbonio/server/features/project_manager/project.py b/lib/esbonio/esbonio/server/features/project_manager/project.py
index 8e15d4d64..e06de6226 100644
--- a/lib/esbonio/esbonio/server/features/project_manager/project.py
+++ b/lib/esbonio/esbonio/server/features/project_manager/project.py
@@ -15,13 +15,20 @@
from typing import List
from typing import Optional
from typing import Tuple
+ from typing import Type
+ from typing import TypeVar
from typing import Union
+ import cattrs
+
+ T = TypeVar("T")
+
class Project:
"""Represents a documentation project."""
- def __init__(self, dbpath: Union[str, pathlib.Path]):
+ def __init__(self, dbpath: Union[str, pathlib.Path], converter: cattrs.Converter):
+ self.converter = converter
self.dbpath = dbpath
self._connection: Optional[aiosqlite.Connection] = None
@@ -35,6 +42,9 @@ async def get_db(self) -> aiosqlite.Connection:
return self._connection
+ def load_as(self, o: str, t: Type[T]) -> T:
+ return self.converter.structure(json.loads(o), t)
+
async def get_src_uris(self) -> List[Uri]:
"""Return all known source uris."""
db = await self.get_db()
@@ -76,6 +86,16 @@ async def get_directives(self) -> List[Tuple[str, Optional[str]]]:
cursor = await db.execute(query)
return await cursor.fetchall() # type: ignore[return-value]
+ async def get_role(self, name: str) -> Optional[types.Role]:
+ """Get the roles known to Sphinx."""
+ db = await self.get_db()
+
+ query = "SELECT * FROM roles WHERE name = ?"
+ cursor = await db.execute(query, (name,))
+ result = await cursor.fetchone()
+
+ return types.Role.from_db(self.load_as, *result) if result is not None else None
+
async def get_roles(self) -> List[Tuple[str, Optional[str]]]:
"""Get the roles known to Sphinx."""
db = await self.get_db()
diff --git a/lib/esbonio/esbonio/server/features/roles/__init__.py b/lib/esbonio/esbonio/server/features/roles/__init__.py
index 75b68a092..ad19eb579 100644
--- a/lib/esbonio/esbonio/server/features/roles/__init__.py
+++ b/lib/esbonio/esbonio/server/features/roles/__init__.py
@@ -3,15 +3,10 @@
import inspect
import typing
-import attrs
-from lsprotocol import types
+from lsprotocol import types as lsp
from esbonio import server
-from esbonio.sphinx_agent.types import MYST_ROLE
-from esbonio.sphinx_agent.types import RST_DIRECTIVE
-from esbonio.sphinx_agent.types import RST_ROLE
-
-from . import completion
+from esbonio.sphinx_agent import types
if typing.TYPE_CHECKING:
from typing import Any
@@ -21,38 +16,63 @@
from typing import Optional
from typing import Union
+ from esbonio.server import Uri
-@attrs.define
-class Role:
- """Represents a role."""
- name: str
- """The name of the role, as the user would type in an rst file."""
+class RoleProvider:
+ """Base class for role providers."""
- implementation: Optional[str]
- """The dotted name of the role's implementation."""
+ def get_role(
+ self, uri: Uri, name: str
+ ) -> Union[Optional[types.Role], Coroutine[Any, Any, Optional[types.Role]]]:
+ """Return the definition of the given role, if known.
+ Parameters
+ ----------
+ uri
+ The uri of the document in which the role name appears
-class RoleProvider:
- """Base class for role providers."""
+ name
+ The name of the role, as the user would type in a document
+ """
+ return None
def suggest_roles(
self, context: server.CompletionContext
- ) -> Union[Optional[List[Role]], Coroutine[Any, Any, Optional[List[Role]]]]:
+ ) -> Union[
+ Optional[List[types.Role]], Coroutine[Any, Any, Optional[List[types.Role]]]
+ ]:
"""Givem a completion context, suggest roles that may be used."""
return None
+class RoleTargetProvider:
+ """Base class for role target providers."""
+
+ def suggest_targets(
+ self, context: server.CompletionContext, **kwargs
+ ) -> Union[
+ Optional[List[lsp.CompletionItem]],
+ Coroutine[Any, Any, Optional[List[lsp.CompletionItem]]],
+ ]:
+ """Givem a completion context, suggest role targets that may be used."""
+ return None
+
+
class RolesFeature(server.LanguageFeature):
- """Support for roles."""
+ """Backend support for roles.
+
+ It's this language feature's responsibility to provide an API that exposes the
+ information a frontend feature may want.
+ """
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
- self._providers: Dict[int, RoleProvider] = {}
- self._insert_behavior = "replace"
+ self._role_providers: Dict[int, RoleProvider] = {}
+ self._target_providers: Dict[str, RoleTargetProvider] = {}
- def add_provider(self, provider: RoleProvider):
+ def add_role_provider(self, provider: RoleProvider):
"""Register a role provider.
Parameters
@@ -60,119 +80,126 @@ def add_provider(self, provider: RoleProvider):
provider
The role provider
"""
- self._providers[id(provider)] = provider
-
- def initialized(self, params: types.InitializedParams):
- """Called once the initial handshake between client and server has finished."""
- self.configuration.subscribe(
- "esbonio.server.completion",
- server.CompletionConfig,
- self.update_configuration,
- )
+ self._role_providers[id(provider)] = provider
+
+ def add_target_provider(self, name: str, provider: RoleTargetProvider):
+ """Register a role target provider.
- def update_configuration(
- self, event: server.ConfigChangeEvent[server.CompletionConfig]
- ):
- """Called when the user's configuration is updated."""
- self._insert_behavior = event.value.preferred_insert_behavior
+ Parameters
+ ----------
+ provider
+ The role target provider
+ """
+ if (existing := self._target_providers.get(name)) is not None:
+ raise ValueError(
+ f"RoleTargetProvider {provider!r} conflicts with existing "
+ f"provider: {existing!r}"
+ )
- completion_triggers = [MYST_ROLE, RST_ROLE]
+ self._target_providers[name] = provider
- async def completion(
+ async def suggest_roles(
self, context: server.CompletionContext
- ) -> Optional[List[types.CompletionItem]]:
- """Provide completion suggestions for roles."""
-
- language = self.server.get_language_at(context.doc, context.position)
- groups = context.match.groupdict()
- target = groups["target"]
-
- # All text matched by the regex
- text = context.match.group(0)
- start, end = context.match.span()
-
- if target:
- target_index = start + text.find(target)
-
- # Only trigger target completions if the request was made from within
- # the target part of the role.
- if target_index <= context.position.character <= end:
- return await self.complete_targets(context)
-
- # If there's no indent, or this is a markdown document, then this can only be a
- # role definition
- indent = context.match.group(1)
- if indent == "" or language == "markdown":
- return await self.complete_roles(context)
-
- # Otherwise, search backwards until we find a blank line or an unindent
- # so that we can determine the appropriate context.
- linum = context.position.line - 1
-
- try:
- line = context.doc.lines[linum]
- except IndexError:
- return await self.complete_roles(context)
-
- while linum >= 0 and line.startswith(indent):
- linum -= 1
- line = context.doc.lines[linum]
-
- # Unless we are within a directive's options block, we should offer role
- # suggestions
- if RST_DIRECTIVE.match(line):
- return []
+ ) -> List[types.Role]:
+ """Suggest roles that may be used, given a completion context.
- return await self.complete_roles(context)
+ Parameters
+ ----------
+ context
+ The completion context
+ """
+ items: List[types.Role] = []
- async def complete_targets(self, context: server.CompletionContext):
- return None
+ for provider in self._role_providers.values():
+ try:
+ result: Optional[List[types.Role]] = None
- async def complete_roles(
- self, context: server.CompletionContext
- ) -> Optional[List[types.CompletionItem]]:
- """Return completion suggestions for the available roles"""
+ aresult = provider.suggest_roles(context)
+ if inspect.isawaitable(aresult):
+ result = await aresult
+
+ if result:
+ items.extend(result)
+ except Exception:
+ name = type(provider).__name__
+ self.logger.error("Error in '%s.suggest_roles'", name, exc_info=True)
+
+ return items
+
+ async def get_role(self, uri: Uri, name: str) -> Optional[types.Role]:
+ """Return the definition of the given role name.
+
+ Parameters
+ ----------
+ uri
+ The uri of the document in which the role name appears
+
+ name
+ The name of the role, as the user would type into a document.
- language = self.server.get_language_at(context.doc, context.position)
- render_func = completion.get_role_renderer(language, self._insert_behavior)
- if render_func is None:
- return None
+ Returns
+ -------
+ Optional[types.Role]
+ The role's definition, if known
+ """
+ for provider in self._role_providers.values():
+ try:
+ result: Optional[types.Role] = None
- items = []
- for role in await self.suggest_roles(context):
- if (item := render_func(context, role)) is not None:
- items.append(item)
+ aresult = provider.get_role(uri, name)
+ if inspect.isawaitable(aresult):
+ result = await aresult
- if len(items) > 0:
- return items
+ if result is not None:
+ return result
+ except Exception:
+ name = type(provider).__name__
+ self.logger.error("Error in '%s.get_role'", name, exc_info=True)
return None
- async def suggest_roles(self, context: server.CompletionContext) -> List[Role]:
- """Suggest roles that may be used, given a completion context.
+ async def suggest_targets(
+ self, context: server.CompletionContext, role_name: str
+ ) -> List[lsp.CompletionItem]:
+ """Suggest role targets that may be used, given a completion context.
Parameters
----------
context
The completion context
+
+ role_name
+ The role to suggest targets for
"""
- items: List[Role] = []
+ if (role := await self.get_role(context.uri, role_name)) is None:
+ self.logger.debug("Unknown role '%s'", role_name)
+ return []
+
+ targets = []
+ self.logger.debug(
+ "Suggesting targets for role: '%s' (%s)", role.name, role.implementation
+ )
+
+ for spec in role.target_providers:
+ if (provider := self._target_providers.get(spec.name)) is None:
+ self.logger.error("Unknown target provider: '%s'", spec.name)
+ continue
- for provider in self._providers.values():
try:
- result: Optional[List[Role]] = None
+ result: Optional[List[lsp.CompletionItem]] = None
- aresult = provider.suggest_roles(context)
+ aresult = provider.suggest_targets(context, **spec.kwargs)
if inspect.isawaitable(aresult):
result = await aresult
- if result:
- items.extend(result)
+ if result is not None:
+ targets.extend(result)
+
except Exception:
name = type(provider).__name__
- self.logger.error("Error in '%s.suggest_roles'", name, exc_info=True)
+ self.logger.error("Error in '%s.suggest_targets'", name, exc_info=True)
- return items
+ return targets
def esbonio_setup(server: server.EsbonioLanguageServer):
diff --git a/lib/esbonio/esbonio/server/features/roles/completion.py b/lib/esbonio/esbonio/server/features/roles/completion.py
index 1d4a8818e..6c5db338e 100644
--- a/lib/esbonio/esbonio/server/features/roles/completion.py
+++ b/lib/esbonio/esbonio/server/features/roles/completion.py
@@ -15,20 +15,27 @@
from typing import Optional
from typing import Tuple
- from . import Role
+ from esbonio.sphinx_agent.types import Role
RoleRenderer = Callable[
[server.CompletionContext, Role], Optional[types.CompletionItem]
]
+ RoleTargetRenderer = Callable[
+ [server.CompletionContext, types.CompletionItem], Optional[types.CompletionItem]
+ ]
+
WORD = re.compile("[a-zA-Z]+")
_ROLE_RENDERERS: Dict[Tuple[str, str], RoleRenderer] = {}
"""CompletionItem rendering functions for roles."""
+_ROLE_TARGET_RENDERERS: Dict[Tuple[str, str], RoleTargetRenderer] = {}
+"""CompletionItem rendering functions for role targets."""
-def renderer(*, language: str, insert_behavior: str):
- """Define a new rendering function."""
+
+def role_renderer(*, language: str, insert_behavior: str):
+ """Define a new rendering function for roles."""
def fn(f: RoleRenderer) -> RoleRenderer:
_ROLE_RENDERERS[(language, insert_behavior)] = f
@@ -37,6 +44,16 @@ def fn(f: RoleRenderer) -> RoleRenderer:
return fn
+def role_target_renderer(*, language: str, insert_behavior: str):
+ """Define a new rendering function for role targets."""
+
+ def fn(f: RoleTargetRenderer) -> RoleTargetRenderer:
+ _ROLE_TARGET_RENDERERS[(language, insert_behavior)] = f
+ return f
+
+ return fn
+
+
def get_role_renderer(language: str, insert_behavior: str) -> Optional[RoleRenderer]:
"""Return the role renderer to use.
@@ -56,7 +73,28 @@ def get_role_renderer(language: str, insert_behavior: str) -> Optional[RoleRende
return _ROLE_RENDERERS.get((language, insert_behavior), None)
-@renderer(language="rst", insert_behavior="insert")
+def get_role_target_renderer(
+ language: str, insert_behavior: str
+) -> Optional[RoleTargetRenderer]:
+ """Return the role target renderer to use.
+
+ Parameters
+ ----------
+ language
+ The source language the completion item will be inserted into
+
+ insert_behavior
+ How the completion should behave when inserted.
+
+ Returns
+ -------
+ Optional[RoleTargetRenderer]
+ The rendering function to use that matches the given criteria, if available.
+ """
+ return _ROLE_TARGET_RENDERERS.get((language, insert_behavior), None)
+
+
+@role_renderer(language="rst", insert_behavior="insert")
def render_rst_role_with_insert_text(
context: server.CompletionContext, role: Role
) -> Optional[types.CompletionItem]:
@@ -104,7 +142,31 @@ def render_rst_role_with_insert_text(
return item
-@renderer(language="markdown", insert_behavior="insert")
+@role_target_renderer(language="rst", insert_behavior="replace")
+def render_rst_target_with_text_edit(
+ context: server.CompletionContext, item: types.CompletionItem
+) -> Optional[types.CompletionItem]:
+ """Render a ``CompletionItem`` using ``insertText``.
+
+ This implements the ``replace`` insert behavior for role targets.
+
+ Parameters
+ ----------
+ context
+ The context in which the completion is being generated.
+
+ item
+ The ``CompletionItem`` representing the role target.
+
+ Returns
+ -------
+ Optional[types.CompletionItem]
+ The rendered completion item, or ``None`` if the item should be skipped
+ """
+ return item
+
+
+@role_renderer(language="markdown", insert_behavior="insert")
def render_myst_role_with_insert_text(
context: server.CompletionContext, role: Role
) -> Optional[types.CompletionItem]:
@@ -152,7 +214,7 @@ def render_myst_role_with_insert_text(
return item
-@renderer(language="rst", insert_behavior="replace")
+@role_renderer(language="rst", insert_behavior="replace")
def render_rst_role_with_text_edit(
context: server.CompletionContext, role: Role
) -> Optional[types.CompletionItem]:
@@ -195,7 +257,7 @@ def render_rst_role_with_text_edit(
return item
-@renderer(language="markdown", insert_behavior="replace")
+@role_renderer(language="markdown", insert_behavior="replace")
def render_myst_role_with_text_edit(
context: server.CompletionContext, role: Role
) -> Optional[types.CompletionItem]:
@@ -238,6 +300,30 @@ def render_myst_role_with_text_edit(
return item
+@role_target_renderer(language="markdown", insert_behavior="replace")
+def render_myst_target_with_text_edit(
+ context: server.CompletionContext, item: types.CompletionItem
+) -> Optional[types.CompletionItem]:
+ """Render a ``CompletionItem`` using ``textEdit``.
+
+ This implements the ``replace`` insert behavior for role targets.
+
+ Parameters
+ ----------
+ context
+ The context in which the completion is being generated.
+
+ item
+ The ``CompletionItem`` representing the role target.
+
+ Returns
+ -------
+ Optional[types.CompletionItem]
+ The rendered completion item, or ``None`` if the item should be skipped
+ """
+ return item
+
+
def _render_role_common(role: Role) -> types.CompletionItem:
"""Render the common fields of a role's completion item."""
return types.CompletionItem(
diff --git a/lib/esbonio/esbonio/server/features/rst/__init__.py b/lib/esbonio/esbonio/server/features/rst/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/lib/esbonio/esbonio/server/features/rst/directives.py b/lib/esbonio/esbonio/server/features/rst/directives.py
new file mode 100644
index 000000000..a125e147c
--- /dev/null
+++ b/lib/esbonio/esbonio/server/features/rst/directives.py
@@ -0,0 +1,100 @@
+from __future__ import annotations
+
+import typing
+
+from lsprotocol import types
+
+from esbonio import server
+from esbonio.server.features.directives import DirectiveFeature
+from esbonio.server.features.directives import completion
+from esbonio.sphinx_agent.types import RST_DIRECTIVE
+
+if typing.TYPE_CHECKING:
+ from typing import List
+ from typing import Optional
+
+
+class RstDirectives(server.LanguageFeature):
+ """A frontend to directives for reStructuredText syntax."""
+
+ def __init__(self, directives: DirectiveFeature, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+ self.directives = directives
+ self._insert_behavior = "replace"
+
+ completion_trigger = server.CompletionTrigger(
+ patterns=[RST_DIRECTIVE],
+ languages={"rst"},
+ characters={".", "`"},
+ )
+
+ def initialized(self, params: types.InitializedParams):
+ """Called once the initial handshake between client and server has finished."""
+ self.configuration.subscribe(
+ "esbonio.server.completion",
+ server.CompletionConfig,
+ self.update_configuration,
+ )
+
+ def update_configuration(
+ self, event: server.ConfigChangeEvent[server.CompletionConfig]
+ ):
+ """Called when the user's configuration is updated."""
+ self._insert_behavior = event.value.preferred_insert_behavior
+
+ async def completion(
+ self, context: server.CompletionContext
+ ) -> Optional[List[types.CompletionItem]]:
+ """Provide completion suggestions for directives."""
+
+ groups = context.match.groupdict()
+
+ # Are we completing a directive's options?
+ if "directive" not in groups:
+ return await self.complete_options(context)
+
+ # Don't offer completions for targets
+ if (groups["name"] or "").startswith("_"):
+ return None
+
+ # Are we completing the directive's argument?
+ directive_end = context.match.span()[0] + len(groups["directive"])
+ complete_directive = groups["directive"].endswith("::")
+
+ if complete_directive and directive_end < context.position.character:
+ return await self.complete_arguments(context)
+
+ return await self.complete_directives(context)
+
+ async def complete_options(self, context: server.CompletionContext):
+ return None
+
+ async def complete_arguments(self, context: server.CompletionContext):
+ return None
+
+ async def complete_directives(
+ self, context: server.CompletionContext
+ ) -> Optional[List[types.CompletionItem]]:
+ """Return completion suggestions for the available directives."""
+
+ render_func = completion.get_directive_renderer(
+ context.language, self._insert_behavior
+ )
+ if render_func is None:
+ return None
+
+ items = []
+ for directive in await self.directives.suggest_directives(context):
+ if (item := render_func(context, directive)) is not None:
+ items.append(item)
+
+ if len(items) > 0:
+ return items
+
+ return None
+
+
+def esbonio_setup(esbonio: server.EsbonioLanguageServer, directives: DirectiveFeature):
+ rst_directives = RstDirectives(directives, esbonio)
+ esbonio.add_feature(rst_directives)
diff --git a/lib/esbonio/esbonio/server/features/rst/roles.py b/lib/esbonio/esbonio/server/features/rst/roles.py
new file mode 100644
index 000000000..6c31d0499
--- /dev/null
+++ b/lib/esbonio/esbonio/server/features/rst/roles.py
@@ -0,0 +1,136 @@
+from __future__ import annotations
+
+import typing
+
+from lsprotocol import types
+
+from esbonio import server
+from esbonio.server.features.roles import RolesFeature
+from esbonio.server.features.roles import completion
+from esbonio.sphinx_agent.types import RST_DIRECTIVE
+from esbonio.sphinx_agent.types import RST_ROLE
+
+if typing.TYPE_CHECKING:
+ from typing import List
+ from typing import Optional
+
+
+class RstRoles(server.LanguageFeature):
+ """A frontend to roles for reStructuredText syntax."""
+
+ def __init__(self, roles: RolesFeature, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+ self.roles = roles
+ self._insert_behavior = "replace"
+
+ completion_trigger = server.CompletionTrigger(
+ patterns=[RST_ROLE],
+ languages={"rst"},
+ characters={":", "`", "<", "/"},
+ )
+
+ def initialized(self, params: types.InitializedParams):
+ """Called once the initial handshake between client and server has finished."""
+ self.configuration.subscribe(
+ "esbonio.server.completion",
+ server.CompletionConfig,
+ self.update_configuration,
+ )
+
+ def update_configuration(
+ self, event: server.ConfigChangeEvent[server.CompletionConfig]
+ ):
+ """Called when the user's configuration is updated."""
+ self._insert_behavior = event.value.preferred_insert_behavior
+
+ async def completion(
+ self, context: server.CompletionContext
+ ) -> Optional[List[types.CompletionItem]]:
+ """Provide completion suggestions for roles."""
+
+ groups = context.match.groupdict()
+ target = groups["target"]
+
+ # All text matched by the regex
+ text = context.match.group(0)
+ start, end = context.match.span()
+
+ if target:
+ target_index = start + text.find(target)
+
+ # Only trigger target completions if the request was made from within
+ # the target part of the role.
+ if target_index <= context.position.character <= end:
+ return await self.complete_targets(context)
+
+ # If there's no indent, then this can only be a
+ # role definition
+ indent = context.match.group(1)
+ if indent == "":
+ return await self.complete_roles(context)
+
+ # Otherwise, search backwards until we find a blank line or an unindent
+ # so that we can determine the appropriate context.
+ linum = context.position.line - 1
+
+ try:
+ line = context.doc.lines[linum]
+ except IndexError:
+ return await self.complete_roles(context)
+
+ while linum >= 0 and line.startswith(indent):
+ linum -= 1
+ line = context.doc.lines[linum]
+
+ # Unless we are within a directive's options block, we should offer role
+ # suggestions
+ if RST_DIRECTIVE.match(line):
+ return []
+
+ return await self.complete_roles(context)
+
+ async def complete_targets(
+ self, context: server.CompletionContext
+ ) -> Optional[List[types.CompletionItem]]:
+ """Provide completion suggestions for role targets."""
+
+ render_func = completion.get_role_target_renderer(
+ context.language, self._insert_behavior
+ )
+ if render_func is None:
+ return None
+
+ items = []
+ role_name = context.match.group("name")
+ for target in await self.roles.suggest_targets(context, role_name):
+ if (item := render_func(context, target)) is not None:
+ items.append(item)
+
+ return items if len(items) > 0 else None
+
+ async def complete_roles(
+ self, context: server.CompletionContext
+ ) -> Optional[List[types.CompletionItem]]:
+ """Return completion suggestions for the available roles"""
+
+ render_func = completion.get_role_renderer(
+ context.language, self._insert_behavior
+ )
+ if render_func is None:
+ return None
+
+ items = []
+ for role in await self.roles.suggest_roles(context):
+ if (item := render_func(context, role)) is not None:
+ items.append(item)
+
+ if len(items) > 0:
+ return items
+
+ return None
+
+
+def esbonio_setup(esbonio: server.EsbonioLanguageServer, roles: RolesFeature):
+ rst_roles = RstRoles(roles, esbonio)
+ esbonio.add_feature(rst_roles)
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 06fca953d..a8305e4b1 100644
--- a/lib/esbonio/esbonio/server/features/sphinx_manager/client_subprocess.py
+++ b/lib/esbonio/esbonio/server/features/sphinx_manager/client_subprocess.py
@@ -14,9 +14,9 @@
from pygls.client import JsonRPCClient
from pygls.protocol import JsonRPCProtocol
-import esbonio.sphinx_agent.types as types
from esbonio.server import EventSource
from esbonio.server import Uri
+from esbonio.sphinx_agent import types
from .client import ClientState
from .config import SphinxConfig
@@ -59,7 +59,7 @@ def __init__(
*args,
**kwargs,
):
- super().__init__(protocol_cls=protocol_cls, *args, **kwargs) # type: ignore[misc]
+ super().__init__(*args, protocol_cls=protocol_cls, **kwargs) # type: ignore[misc]
self.id = str(uuid4())
"""The client's id."""
@@ -204,7 +204,6 @@ async def start(self) -> SphinxClient:
params = types.CreateApplicationParams(
command=self.config.build_command,
- enable_sync_scrolling=self.config.enable_sync_scrolling,
)
self.sphinx_info = await self.protocol.send_request_async(
@@ -273,7 +272,7 @@ async def forward_stderr(server: asyncio.subprocess.Process):
# EOF is signalled with an empty bytestring
while (line := await server.stderr.readline()) != b"":
- sphinx_logger.info(line.decode().strip())
+ sphinx_logger.info(line.decode().rstrip())
def make_subprocess_sphinx_client(
@@ -317,7 +316,7 @@ def make_test_sphinx_client(config: SphinxConfig) -> SubprocessSphinxClient:
@client.feature("window/logMessage")
def _(params):
- print(params.message, file=sys.stderr)
+ print(params.message, file=sys.stderr) # noqa: T201
@client.feature("$/progress")
def _on_progress(params):
@@ -354,14 +353,19 @@ def get_start_command(config: SphinxConfig, logger: logging.Logger):
if config.enable_dev_tools:
# Assumes that the user has `lsp-devtools` available on their PATH
# TODO: Windows support
- result = subprocess.run(["command", "-v", "lsp-devtools"], capture_output=True)
+ result = subprocess.run(
+ ["command", "-v", "lsp-devtools"], # noqa: S607
+ capture_output=True,
+ check=False,
+ )
+
if result.returncode == 0:
lsp_devtools = result.stdout.decode("utf8").strip()
command.extend([lsp_devtools, "agent", "--"])
else:
stderr = result.stderr.decode("utf8").strip()
- logger.debug("Unable to locate lsp-devtools command", stderr)
+ logger.debug("Unable to locate lsp-devtools command\n%s", stderr)
command.extend([*config.python_command, "-m", "sphinx_agent"])
return command
diff --git a/lib/esbonio/esbonio/server/features/sphinx_manager/config.py b/lib/esbonio/esbonio/server/features/sphinx_manager/config.py
index ba92489f0..855f53d38 100644
--- a/lib/esbonio/esbonio/server/features/sphinx_manager/config.py
+++ b/lib/esbonio/esbonio/server/features/sphinx_manager/config.py
@@ -45,9 +45,6 @@ class SphinxConfig:
enable_dev_tools: bool = attrs.field(default=False)
"""Flag to enable dev tools."""
- enable_sync_scrolling: bool = attrs.field(default=True)
- """Flag to enable sync scrolling."""
-
python_command: List[str] = attrs.field(factory=list)
"""The command to use when launching the python interpreter."""
@@ -106,7 +103,6 @@ def resolve(
return SphinxConfig(
enable_dev_tools=self.enable_dev_tools,
- enable_sync_scrolling=self.enable_sync_scrolling,
cwd=cwd,
env_passthrough=self.env_passthrough,
python_command=self.python_command,
@@ -227,7 +223,7 @@ def _resolve_build_command(self, uri: Uri, logger: logging.Logger) -> List[str]:
logger.debug("Trying path: %s", current)
if conf_py.exists():
cache = platformdirs.user_cache_dir("esbonio", "swyddfa")
- project = hashlib.md5(str(current).encode()).hexdigest()
+ project = hashlib.md5(str(current).encode()).hexdigest() # noqa: S324
build_dir = str(pathlib.Path(cache, project))
return ["sphinx-build", "-M", "dirhtml", str(current), str(build_dir)]
diff --git a/lib/esbonio/esbonio/server/features/sphinx_support/roles.py b/lib/esbonio/esbonio/server/features/sphinx_support/roles.py
index fcd4d6c7c..582b17d92 100644
--- a/lib/esbonio/esbonio/server/features/sphinx_support/roles.py
+++ b/lib/esbonio/esbonio/server/features/sphinx_support/roles.py
@@ -1,36 +1,91 @@
from __future__ import annotations
+import logging
import typing
-from lsprotocol import types
+from lsprotocol import types as lsp
from esbonio import server
from esbonio.server.features import roles
from esbonio.server.features.project_manager import ProjectManager
+from esbonio.sphinx_agent import types
if typing.TYPE_CHECKING:
from typing import List
from typing import Optional
+ from esbonio.server import Uri
+ from esbonio.server.features.project_manager import Project
-class SphinxRoles(roles.RoleProvider):
- """Support for roles in a sphinx project."""
- def __init__(self, manager: ProjectManager):
+TARGET_KINDS = {
+ "attribute": lsp.CompletionItemKind.Field,
+ "doc": lsp.CompletionItemKind.File,
+ "class": lsp.CompletionItemKind.Class,
+ "envvar": lsp.CompletionItemKind.Variable,
+ "function": lsp.CompletionItemKind.Function,
+ "method": lsp.CompletionItemKind.Method,
+ "module": lsp.CompletionItemKind.Module,
+ "term": lsp.CompletionItemKind.Text,
+}
+
+
+class ObjectsProvider(roles.RoleTargetProvider):
+ """Expose domain objects as potential role targets"""
+
+ def __init__(self, logger: logging.Logger, manager: ProjectManager):
self.manager = manager
+ self.logger = logger
- async def suggest_roles(
- self, context: server.CompletionContext
- ) -> Optional[List[roles.Role]]:
- """Given a completion context, suggest roles that may be used."""
+ async def suggest_targets( # type: ignore[override]
+ self,
+ context: server.CompletionContext,
+ *,
+ obj_types: List[str],
+ ) -> Optional[List[lsp.CompletionItem]]:
+ self.logger.debug("Suggesting targets for types: %s", obj_types)
if (project := self.manager.get_project(context.uri)) is None:
return None
+ db = await project.get_db()
+ query = " ".join(
+ [
+ "SELECT name, display, objtype FROM objects",
+ "WHERE printf('%s:%s', domain, objtype) IN (",
+ ", ".join("?" for _ in range(len(obj_types))),
+ ");",
+ ]
+ )
+
+ items = []
+ cursor = await db.execute(query, tuple(obj_types))
+ for name, display, type_ in await cursor.fetchall():
+ kind = TARGET_KINDS.get(type_, lsp.CompletionItemKind.Reference)
+ items.append(
+ lsp.CompletionItem(
+ label=name,
+ detail=display,
+ kind=kind,
+ ),
+ )
+
+ return items
+
+
+class SphinxRoles(roles.RoleProvider):
+ """Support for roles in a sphinx project."""
+
+ def __init__(self, manager: ProjectManager):
+ self.manager = manager
+
+ async def get_default_domain(self, project: Project, uri: Uri) -> str:
+ """Get the name of the default domain for the given document"""
+
# Does the document have a default domain set?
results = await project.find_symbols(
- uri=str(context.uri.resolve()),
- kind=types.SymbolKind.Class.value,
+ uri=str(uri.resolve()),
+ kind=lsp.SymbolKind.Class.value,
detail="default-domain",
)
if len(results) > 0:
@@ -39,32 +94,63 @@ async def suggest_roles(
default_domain = None
primary_domain = await project.get_config_value("primary_domain")
- active_domain = default_domain or primary_domain or "py"
+ return default_domain or primary_domain or "py"
+
+ async def get_role(self, uri: Uri, name: str) -> Optional[types.Role]:
+ """Return the role with the given name."""
+
+ if (project := self.manager.get_project(uri)) is None:
+ return None
+
+ if (role := await project.get_role(name)) is not None:
+ return role
+
+ if (role := await project.get_role(f"std:{name}")) is not None:
+ return role
+
+ default_domain = await self.get_default_domain(project, uri)
+ return await project.get_role(f"{default_domain}:{name}")
- result: List[roles.Role] = []
+ async def suggest_roles(
+ self, context: server.CompletionContext
+ ) -> Optional[List[types.Role]]:
+ """Given a completion context, suggest roles that may be used."""
+
+ if (project := self.manager.get_project(context.uri)) is None:
+ return None
+
+ default_domain = await self.get_default_domain(project, context.uri)
+
+ result: List[types.Role] = []
for name, implementation in await project.get_roles():
# std: directives can be used unqualified
if name.startswith("std:"):
short_name = name.replace("std:", "")
result.append(
- roles.Role(name=short_name, implementation=implementation)
+ types.Role(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}:", "")
+ elif name.startswith(f"{default_domain}:"):
+ short_name = name.replace(f"{default_domain}:", "")
result.append(
- roles.Role(name=short_name, implementation=implementation)
+ types.Role(name=short_name, implementation=implementation)
)
- result.append(roles.Role(name=name, implementation=implementation))
+ result.append(types.Role(name=name, implementation=implementation))
return result
def esbonio_setup(
+ esbonio: server.EsbonioLanguageServer,
project_manager: ProjectManager,
roles_feature: roles.RolesFeature,
):
- provider = SphinxRoles(project_manager)
- roles_feature.add_provider(provider)
+ role_provider = SphinxRoles(project_manager)
+ obj_provider = ObjectsProvider(
+ esbonio.logger.getChild("ObjectsProvider"), project_manager
+ )
+
+ roles_feature.add_role_provider(role_provider)
+ roles_feature.add_target_provider("objects", obj_provider)
diff --git a/lib/esbonio/esbonio/server/features/sphinx_support/symbols.py b/lib/esbonio/esbonio/server/features/sphinx_support/symbols.py
index c50413ad7..0d39b6a05 100644
--- a/lib/esbonio/esbonio/server/features/sphinx_support/symbols.py
+++ b/lib/esbonio/esbonio/server/features/sphinx_support/symbols.py
@@ -77,7 +77,7 @@ async def workspace_symbol(
uri = Uri.parse(uri_str)
range_ = self.converter.structure(json.loads(range_json), types.Range)
- if detail != "" and name != detail:
+ if detail not in {"", name}:
display_name = f"{name} {detail}"
else:
display_name = name
diff --git a/lib/esbonio/esbonio/server/server.py b/lib/esbonio/esbonio/server/server.py
index e41a6bb91..e90754a9d 100644
--- a/lib/esbonio/esbonio/server/server.py
+++ b/lib/esbonio/esbonio/server/server.py
@@ -174,8 +174,8 @@ def load_extension(self, name: str, setup: Callable):
from esbonio.lsp.roles import Roles
from esbonio.lsp.sphinx import SphinxLanguageServer
- def esbonio_setup(rst: SphinxLanguageServer, roles: Roles):
- ...
+
+ def esbonio_setup(rst: SphinxLanguageServer, roles: Roles): ...
In this example the setup function is requesting instances of the
:class:`~esbonio.lsp.sphinx.SphinxLanguageServer` and the
@@ -447,7 +447,7 @@ def _get_setup_arguments(
args[name] = server
continue
- from .feature import LanguageFeature # noqa: F402
+ from .feature import LanguageFeature
if issubclass(type_, LanguageFeature):
# Try and obtain an instance of the requested language feature.
diff --git a/lib/esbonio/esbonio/server/setup.py b/lib/esbonio/esbonio/server/setup.py
index edca13a11..38f10cb2f 100644
--- a/lib/esbonio/esbonio/server/setup.py
+++ b/lib/esbonio/esbonio/server/setup.py
@@ -8,12 +8,12 @@
from typing import Dict
from typing import Iterable
from typing import List
+from typing import Set
from typing import Type
from lsprotocol import types
from . import Uri
-from .feature import CompletionContext
if typing.TYPE_CHECKING:
from .server import EsbonioLanguageServer
@@ -39,10 +39,13 @@ def create_language_server(
for module in modules:
_load_module(server, module)
- return _configure_lsp_methods(server)
+ _configure_lsp_methods(server)
+ _configure_completion(server)
+
+ return server
-def _configure_lsp_methods(server: EsbonioLanguageServer) -> EsbonioLanguageServer:
+def _configure_lsp_methods(server: EsbonioLanguageServer):
"""Configure method handlers for the portions of the LSP spec we support."""
@server.feature(types.INITIALIZE)
@@ -90,81 +93,6 @@ async def on_document_save(
await call_features(ls, "document_save", params)
- @server.feature(
- types.TEXT_DOCUMENT_COMPLETION,
- types.CompletionOptions(
- trigger_characters=[">", ".", ":", "`", "<", "/", "{", "}"],
- resolve_provider=True,
- ),
- )
- async def on_completion(ls: EsbonioLanguageServer, params: types.CompletionParams):
- uri = params.text_document.uri
- pos = params.position
- doc = ls.workspace.get_text_document(uri)
-
- try:
- line = doc.lines[pos.line]
- except IndexError:
- line = ""
-
- items = []
-
- for cls, feature in ls:
- for pattern in feature.completion_triggers:
- for match in pattern.finditer(line):
- if not match:
- continue
-
- # Only trigger completions if the position of the request is within
- # the match.
- start, stop = match.span()
- if not (start <= pos.character <= stop):
- continue
-
- context = CompletionContext(
- uri=Uri.parse(uri),
- doc=doc,
- match=match,
- position=pos,
- capabilities=ls.client_capabilities,
- )
-
- ls.logger.debug("%s", context)
- name = f"{cls.__name__}"
-
- try:
- result = feature.completion(context)
- if inspect.isawaitable(result):
- result = await result
- except Exception:
- ls.logger.error(
- "Error in '%s.complete' handler", name, exc_info=True
- )
- continue
-
- for item in result or []:
- item.data = {"source_feature": name, **(item.data or {})} # type: ignore
- items.append(item)
-
- if len(items) > 0:
- return types.CompletionList(is_incomplete=False, items=items)
-
- @server.feature(types.COMPLETION_ITEM_RESOLVE)
- def on_completion_resolve(
- ls: EsbonioLanguageServer, item: types.CompletionItem
- ) -> types.CompletionItem:
- # source = (item.data or {}).get("source_feature", "") # type: ignore
- # feature = ls.get_feature(source)
-
- # if not feature:
- # ls.logger.error(
- # "Unable to resolve completion item, unknown source: '%s'", source
- # )
- # return item
-
- # return feature.completion_resolve(item)
- return item
-
@server.feature(
types.TEXT_DOCUMENT_DIAGNOSTIC,
types.DiagnosticOptions(
@@ -248,7 +176,81 @@ async def on_did_change_watched_files(
paths = [pathlib.Path(Uri.parse(event.uri)) for event in params.changes]
await ls.configuration.update_file_configuration(paths)
- return server
+
+def _configure_completion(server: EsbonioLanguageServer):
+ """Configuration completion handlers."""
+
+ trigger_characters: Set[str] = set()
+
+ for _, feature in server:
+ if feature.completion_trigger is None:
+ continue
+
+ trigger_characters.update(feature.completion_trigger.characters)
+
+ @server.feature(
+ types.TEXT_DOCUMENT_COMPLETION,
+ types.CompletionOptions(
+ trigger_characters=list(trigger_characters),
+ resolve_provider=True,
+ ),
+ )
+ async def on_completion(ls: EsbonioLanguageServer, params: types.CompletionParams):
+ uri = params.text_document.uri
+ pos = params.position
+ doc = ls.workspace.get_text_document(uri)
+ language = ls.get_language_at(doc, pos)
+
+ items = []
+
+ for cls, feature in ls:
+ if not feature.completion_trigger:
+ continue
+
+ context = feature.completion_trigger(
+ uri=Uri.parse(uri),
+ params=params,
+ document=doc,
+ language=language,
+ client_capabilities=ls.client_capabilities,
+ )
+
+ if context is None:
+ continue
+
+ ls.logger.debug("%s", context)
+ name = f"{cls.__name__}"
+
+ try:
+ result = feature.completion(context)
+ if inspect.isawaitable(result):
+ result = await result
+ except Exception:
+ ls.logger.exception("Error in '%s.complete' handler", name)
+ continue
+
+ for item in result or []:
+ item.data = {"source_feature": name, **(item.data or {})} # type: ignore
+ items.append(item)
+
+ if len(items) > 0:
+ return types.CompletionList(is_incomplete=False, items=items)
+
+ @server.feature(types.COMPLETION_ITEM_RESOLVE)
+ def on_completion_resolve(
+ ls: EsbonioLanguageServer, item: types.CompletionItem
+ ) -> types.CompletionItem:
+ # source = (item.data or {}).get("source_feature", "") # type: ignore
+ # feature = ls.get_feature(source)
+
+ # if not feature:
+ # ls.logger.error(
+ # "Unable to resolve completion item, unknown source: '%s'", source
+ # )
+ # return item
+
+ # return feature.completion_resolve(item)
+ return item
async def call_features(ls: EsbonioLanguageServer, method: str, *args, **kwargs):
diff --git a/lib/esbonio/esbonio/sphinx_agent/__main__.py b/lib/esbonio/esbonio/sphinx_agent/__main__.py
index 251925fb5..8ac08d4c0 100644
--- a/lib/esbonio/esbonio/sphinx_agent/__main__.py
+++ b/lib/esbonio/esbonio/sphinx_agent/__main__.py
@@ -1,8 +1,5 @@
import asyncio
-try:
- from esbonio.sphinx_agent.server import main
-except ImportError:
- from .server import main
+from .server import main
asyncio.run(main())
diff --git a/lib/esbonio/esbonio/sphinx_agent/app.py b/lib/esbonio/esbonio/sphinx_agent/app.py
index 90bfdff38..2b3e65bb7 100644
--- a/lib/esbonio/esbonio/sphinx_agent/app.py
+++ b/lib/esbonio/esbonio/sphinx_agent/app.py
@@ -2,22 +2,36 @@
import logging
import pathlib
-from typing import IO
+import typing
from sphinx.application import Sphinx as _Sphinx
from sphinx.util import console
from sphinx.util import logging as sphinx_logging_module
from sphinx.util.logging import NAMESPACE as SPHINX_LOG_NAMESPACE
+from . import types
from .database import Database
from .log import DiagnosticFilter
+if typing.TYPE_CHECKING:
+ from typing import IO
+ from typing import Any
+ from typing import Dict
+ from typing import List
+ from typing import Optional
+ from typing import Set
+ from typing import Tuple
+ from typing import Type
+
+ from sphinx.domains import Domain
+
+ RoleDefinition = Tuple[str, Any, List[types.Role.TargetProvider]]
+
sphinx_logger = logging.getLogger(SPHINX_LOG_NAMESPACE)
sphinx_log_setup = sphinx_logging_module.setup
def setup_logging(app: Sphinx, status: IO, warning: IO):
-
# Run the usual setup
sphinx_log_setup(app, status, warning)
@@ -38,12 +52,52 @@ def __init__(self, dbpath: pathlib.Path, app: _Sphinx):
self.db = Database(dbpath)
self.log = DiagnosticFilter(app)
- # Override sphinx's usual logging setup function
- sphinx_logging_module.setup = setup_logging # type: ignore
+ self._roles: List[RoleDefinition] = []
+ """Roles captured during Sphinx startup."""
+
+ def add_role(
+ self,
+ name: str,
+ role: Any,
+ target_providers: Optional[List[types.Role.TargetProvider]] = None,
+ ):
+ """Register a role with esbonio.
+
+ Parameters
+ ----------
+ name
+ The name of the role, as the user would type in a document
+
+ role
+ The role's implementation
+
+ target_providers
+ A list of target providers for the role
+ """
+ self._roles.append((name, role, target_providers or []))
+
+ @staticmethod
+ def create_role_target_provider(name: str, **kwargs) -> types.Role.TargetProvider:
+ """Create a new role target provider
+
+ Parameters
+ ----------
+ name
+ The name of the provider
+
+ kwargs
+ Additional arguments to pass to the provider instance
+
+ Returns
+ -------
+ types.Role.TargetProvider
+ The target provider
+ """
+ return types.Role.TargetProvider(name, kwargs)
class Sphinx(_Sphinx):
- """A regular sphinx application with a few extra fields."""
+ """An extended sphinx application that integrates with esbonio."""
esbonio: Esbonio
@@ -51,9 +105,38 @@ def __init__(self, *args, **kwargs):
# Disable color codes
console.nocolor()
+ # Add in esbonio specific functionality
self.esbonio = Esbonio(
dbpath=pathlib.Path(kwargs["outdir"], "esbonio.db").resolve(),
app=self,
)
+ # Override sphinx's usual logging setup function
+ sphinx_logging_module.setup = setup_logging # type: ignore
+
super().__init__(*args, **kwargs)
+
+ def add_role(self, name: str, role: Any, override: bool = False):
+ super().add_role(name, role, override)
+ self.esbonio.add_role(name, role)
+
+ def add_domain(self, domain: Type[Domain], override: bool = False) -> None:
+ super().add_domain(domain, override)
+
+ target_types: Dict[str, Set[str]] = {}
+
+ for obj_name, item_type in domain.object_types.items():
+ for role_name in item_type.roles:
+ target_type = f"{domain.name}:{obj_name}"
+ target_types.setdefault(role_name, set()).add(target_type)
+
+ for name, role in domain.roles.items():
+ providers = []
+ if (item_types := target_types.get(name)) is not None:
+ providers.append(
+ self.esbonio.create_role_target_provider(
+ "objects", obj_types=list(item_types)
+ )
+ )
+
+ self.esbonio.add_role(f"{domain.name}:{name}", role, providers)
diff --git a/lib/esbonio/esbonio/sphinx_agent/database.py b/lib/esbonio/esbonio/sphinx_agent/database.py
index d794db924..80be9e26f 100644
--- a/lib/esbonio/esbonio/sphinx_agent/database.py
+++ b/lib/esbonio/esbonio/sphinx_agent/database.py
@@ -88,7 +88,7 @@ def clear_table(self, table: Table, **kwargs):
"""
# TODO: Is there a way to pass the table name as a '?' parameter?
- base_query = f"DELETE FROM {table.name}"
+ base_query = f"DELETE FROM {table.name}" # noqa: S608
where: List[str] = []
parameters: List[Any] = []
@@ -140,5 +140,5 @@ def insert_values(self, table: Table, values: List[Tuple]):
cursor = self.db.cursor()
placeholder = "(" + ",".join(["?" for _ in range(len(values[0]))]) + ")"
- cursor.executemany(f"INSERT INTO {table.name} VALUES {placeholder}", values)
+ cursor.executemany(f"INSERT INTO {table.name} VALUES {placeholder}", values) # noqa: S608
self.db.commit()
diff --git a/lib/esbonio/esbonio/sphinx_agent/handlers/__init__.py b/lib/esbonio/esbonio/sphinx_agent/handlers/__init__.py
index 8bf9f7297..b669b4a64 100644
--- a/lib/esbonio/esbonio/sphinx_agent/handlers/__init__.py
+++ b/lib/esbonio/esbonio/sphinx_agent/handlers/__init__.py
@@ -1,6 +1,5 @@
import inspect
import logging
-import os.path
import pathlib
import sys
import traceback
@@ -34,6 +33,7 @@
f"{__name__}.symbols",
f"{__name__}.directives",
f"{__name__}.roles",
+ f"{__name__}.domains",
)
@@ -124,8 +124,7 @@ def create_sphinx_app(self, request: types.CreateApplicationRequest):
# TODO: Sphinx 7.x has introduced a `include-read` event
# See: https://github.com/sphinx-doc/sphinx/pull/11657
- if request.params.enable_sync_scrolling:
- _enable_sync_scrolling(self.app)
+ _enable_sync_scrolling(self.app)
response = types.CreateApplicationResponse(
id=request.id,
@@ -198,21 +197,11 @@ def _enable_sync_scrolling(app: Sphinx):
"""Given a Sphinx application, configure it so that we can support syncronised
scrolling."""
- # On OSes like Fedora Silverblue where `/home` is a symlink for `/var/home`
- # we could have a situation where `STATIC_DIR` and `app.confdir` have
- # different root dirs... which is enough to cause `os.path.relpath` to return
- # the wrong path.
+ # Inline the JS code we need to enable sync scrolling.
#
- # Fully resolving both `STATIC_DIR` and `app.confdir` should be enough to
- # mitigate this.
- confdir = pathlib.Path(app.confdir).resolve()
+ # Yes this "bloats" every page in the generated docs, but is generally more robust
+ # see: https://github.com/swyddfa/esbonio/issues/810
+ webview_js = STATIC_DIR / "webview.js"
+ app.add_js_file(None, body=webview_js.read_text())
- # Push our folder of static assets into the user's project.
- # Path needs to be relative to their project's confdir.
- reldir = os.path.relpath(str(STATIC_DIR), start=str(confdir))
- app.config.html_static_path.append(reldir)
-
- app.add_js_file("webview.js")
-
- # Inject source line numbers into build output
app.add_transform(LineNumberTransform)
diff --git a/lib/esbonio/esbonio/sphinx_agent/handlers/diagnostics.py b/lib/esbonio/esbonio/sphinx_agent/handlers/diagnostics.py
index 70c47d1b3..cc7b60240 100644
--- a/lib/esbonio/esbonio/sphinx_agent/handlers/diagnostics.py
+++ b/lib/esbonio/esbonio/sphinx_agent/handlers/diagnostics.py
@@ -43,7 +43,7 @@ def setup(app: Sphinx):
app.connect("config-inited", init_db)
app.connect("source-read", clear_diagnostics)
- # TODO
+ # TODO: Support for Sphinx v7+
# app.connect("include-read")
app.connect("build-finished", sync_diagnostics)
diff --git a/lib/esbonio/esbonio/sphinx_agent/handlers/directives.py b/lib/esbonio/esbonio/sphinx_agent/handlers/directives.py
index c5b9b1b1c..5eb69399c 100644
--- a/lib/esbonio/esbonio/sphinx_agent/handlers/directives.py
+++ b/lib/esbonio/esbonio/sphinx_agent/handlers/directives.py
@@ -70,8 +70,8 @@ def index_directives(app: Sphinx):
ignored_directives = {"restructuredtext-test-directive"}
found_directives = {
- **docutils_directives._directive_registry,
- **docutils_directives._directives,
+ **docutils_directives._directive_registry, # type: ignore[attr-defined]
+ **docutils_directives._directives, # type: ignore[attr-defined]
}
for name, directive in found_directives.items():
diff --git a/lib/esbonio/esbonio/sphinx_agent/handlers/domains.py b/lib/esbonio/esbonio/sphinx_agent/handlers/domains.py
new file mode 100644
index 000000000..661f693e7
--- /dev/null
+++ b/lib/esbonio/esbonio/sphinx_agent/handlers/domains.py
@@ -0,0 +1,116 @@
+from __future__ import annotations
+
+import typing
+
+from sphinx import addnodes
+
+from .. import types
+from ..app import Database
+from ..app import Sphinx
+from ..util import as_json
+
+if typing.TYPE_CHECKING:
+ from typing import Dict
+ from typing import Optional
+ from typing import Tuple
+
+OBJECTS_TABLE = Database.Table(
+ "objects",
+ [
+ Database.Column(name="name", dtype="TEXT"),
+ Database.Column(name="display", dtype="TEXT"),
+ Database.Column(name="domain", dtype="TEXT"),
+ Database.Column(name="objtype", dtype="TEXT"),
+ Database.Column(name="docname", dtype="TEXT"),
+ Database.Column(name="description", dtype="TEXT"),
+ Database.Column(name="location", dtype="JSON"),
+ ],
+)
+
+
+class DomainObjects:
+ """Discovers and indexes domain objects."""
+
+ def __init__(self, app: Sphinx):
+ self._info: Dict[
+ Tuple[str, str, str, str], Tuple[Optional[str], Optional[str]]
+ ] = {}
+
+ app.connect("builder-inited", self.init_db)
+ app.connect("object-description-transform", self.object_defined)
+ app.connect("build-finished", self.commit)
+
+ def init_db(self, app: Sphinx):
+ """Prepare the database."""
+ app.esbonio.db.ensure_table(OBJECTS_TABLE)
+
+ def commit(self, app, exc):
+ """Commit changes to the database.
+
+ The only way to guarantee we discover all objects, from all domains correctly,
+ is to call the ``get_objects()`` method on each domain. This means we process
+ every object, every time we build.
+
+ I will be *very* surprised if this never becomes a performance issue, but we
+ will have to think of a smarter approach when it comes to it.
+ """
+ app.esbonio.db.clear_table(OBJECTS_TABLE)
+ rows = []
+
+ for name, domain in app.env.domains.items():
+ for objname, dispname, objtype, docname, _, _ in domain.get_objects():
+ desc, location = self._info.get(
+ (objname, name, objtype, docname), (None, None)
+ )
+ rows.append(
+ (objname, str(dispname), name, objtype, docname, desc, location)
+ )
+
+ app.esbonio.db.insert_values(OBJECTS_TABLE, rows)
+ self._info.clear()
+
+ def object_defined(
+ self, app: Sphinx, domain: str, objtype: str, content: addnodes.desc_content
+ ):
+ """Record additional information about a domain object.
+
+ Despite having a certain amount of structure to them (thanks to the API),
+ domains can still do arbitrary things - take a peek at the implementations of
+ the ``std``, ``py`` and ``cpp`` domains!
+
+ So while this will never be perfect, this method is called each time the
+ ``object-description-transform`` event is fired and attempts to extract the
+ object's description and precise location.
+
+ The trick however, is linking these items up with the correct object
+ """
+
+ sig = content.parent[0]
+
+ try:
+ name = sig["ids"][0] # type: ignore[index]
+ except Exception:
+ return
+
+ docname = app.env.docname
+ description = content.astext()
+
+ if (source := sig.source) is not None and (line := sig.line) is not None:
+ location = as_json(
+ types.Location(
+ uri=str(types.Uri.for_file(source)),
+ range=types.Range(
+ start=types.Position(line=line, character=0),
+ end=types.Position(line=line + 1, character=0),
+ ),
+ )
+ )
+ else:
+ location = None
+
+ key = (name, domain, objtype, docname)
+ self._info[key] = (description, location)
+
+
+def setup(app: Sphinx):
+ DomainObjects(app)
diff --git a/lib/esbonio/esbonio/sphinx_agent/handlers/roles.py b/lib/esbonio/esbonio/sphinx_agent/handlers/roles.py
index 40d83b685..1f96d33b0 100644
--- a/lib/esbonio/esbonio/sphinx_agent/handlers/roles.py
+++ b/lib/esbonio/esbonio/sphinx_agent/handlers/roles.py
@@ -1,6 +1,6 @@
import inspect
from typing import Any
-from typing import List
+from typing import Dict
from typing import Optional
from docutils.parsers.rst import roles as docutils_roles
@@ -16,6 +16,7 @@
Database.Column(name="name", dtype="TEXT"),
Database.Column(name="implementation", dtype="TEXT"),
Database.Column(name="location", dtype="JSON"),
+ Database.Column(name="target_providers", dtype="JSON"),
],
)
@@ -27,8 +28,8 @@ def get_impl_name(role: Any) -> str:
return f"{role.__module__}.{role.__class__.__name__}"
-def get_impl_location(impl: Any) -> Optional[str]:
- """Get the implementation location of the given directive"""
+def get_impl_location(impl: Any) -> Optional[types.Location]:
+ """Get the implementation location of the given role"""
try:
if (filepath := inspect.getsourcefile(impl)) is None:
@@ -45,7 +46,7 @@ def get_impl_location(impl: Any) -> Optional[str]:
),
)
- return as_json(location)
+ return location
except Exception:
# TODO: Log the error somewhere..
return None
@@ -54,32 +55,31 @@ def get_impl_location(impl: Any) -> Optional[str]:
def index_roles(app: Sphinx):
"""Index all the roles that are available to this app."""
- roles: List[types.Directive] = []
+ roles: Dict[str, types.Role] = {}
+
+ # Process the roles registered through Sphinx
+ for name, impl, providers in app.esbonio._roles:
+ roles[name] = types.Role(name, get_impl_name(impl), target_providers=providers)
+
+ # Look any remaining docutils provided roles
found_roles = {
**docutils_roles._roles, # type: ignore[attr-defined]
**docutils_roles._role_registry, # type: ignore[attr-defined]
}
for name, role in found_roles.items():
- if role == docutils_roles.unimplemented_role:
+ if role == docutils_roles.unimplemented_role or name in roles:
continue
- roles.append((name, get_impl_name(role), None))
-
- for prefix, domain in app.env.domains.items():
- for name, role in domain.roles.items():
- roles.append(
- (
- f"{prefix}:{name}",
- get_impl_name(role),
- None,
- )
- )
+ roles[name] = types.Role(name, get_impl_name(role))
app.esbonio.db.ensure_table(ROLES_TABLE)
app.esbonio.db.clear_table(ROLES_TABLE)
- app.esbonio.db.insert_values(ROLES_TABLE, roles)
+ app.esbonio.db.insert_values(
+ ROLES_TABLE, [r.to_db(as_json) for r in roles.values()]
+ )
def setup(app: Sphinx):
- app.connect("builder-inited", index_roles)
+ # Ensure that this runs as late as possibile
+ app.connect("builder-inited", index_roles, priority=999)
diff --git a/lib/esbonio/esbonio/sphinx_agent/handlers/symbols.py b/lib/esbonio/esbonio/sphinx_agent/handlers/symbols.py
index 4364686f1..258ef294a 100644
--- a/lib/esbonio/esbonio/sphinx_agent/handlers/symbols.py
+++ b/lib/esbonio/esbonio/sphinx_agent/handlers/symbols.py
@@ -107,7 +107,7 @@ def astext(self):
return self["text"]
-def dummy_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
+def dummy_role(name, rawtext, text, lineno, inliner, options=None, content=None):
node = a_role()
node.line = lineno
diff --git a/lib/esbonio/esbonio/sphinx_agent/static/webview.js b/lib/esbonio/esbonio/sphinx_agent/static/webview.js
index 5bec76c4d..e9e3f6d91 100644
--- a/lib/esbonio/esbonio/sphinx_agent/static/webview.js
+++ b/lib/esbonio/esbonio/sphinx_agent/static/webview.js
@@ -1,4 +1,4 @@
-// This file gets injected into html pages built with Sphinx (assuming it's enabled of course!)
+// This file gets injected into html pages built with Sphinx
// which allows the webpage to talk with the preview server and coordinate details such as refreshes
// and page scrolling.
function indexScrollTargets() {
diff --git a/lib/esbonio/esbonio/sphinx_agent/types.py b/lib/esbonio/esbonio/sphinx_agent/types/__init__.py
similarity index 76%
rename from lib/esbonio/esbonio/sphinx_agent/types.py
rename to lib/esbonio/esbonio/sphinx_agent/types/__init__.py
index 05d1104d2..47758c9fb 100644
--- a/lib/esbonio/esbonio/sphinx_agent/types.py
+++ b/lib/esbonio/esbonio/sphinx_agent/types/__init__.py
@@ -5,7 +5,6 @@
"""
import dataclasses
-import enum
import os
import pathlib
import re
@@ -17,6 +16,28 @@
from typing import Union
from urllib import parse
+from .lsp import Diagnostic
+from .lsp import DiagnosticSeverity
+from .lsp import Location
+from .lsp import Position
+from .lsp import Range
+from .roles import MYST_ROLE
+from .roles import RST_DEFAULT_ROLE
+from .roles import RST_ROLE
+from .roles import Role
+
+__all__ = (
+ "Diagnostic",
+ "DiagnosticSeverity",
+ "Location",
+ "MYST_ROLE",
+ "Position",
+ "RST_DEFAULT_ROLE",
+ "RST_ROLE",
+ "Range",
+ "Role",
+)
+
MYST_DIRECTIVE: "re.Pattern" = re.compile(
r"""
(\s*) # directives can be indented
@@ -37,52 +58,6 @@
initial declaration.
"""
-MYST_ROLE: "re.Pattern" = re.compile(
- r"""
- ([^\w`]|^\s*) # roles cannot be preceeded by letter chars
- (?P
- { # roles start with a '{'
- (?P[:\w-]+)? # roles have a name
- }? # roles end with a '}'
- )
- (?P
- ` # targets begin with a '`' character
- ((?P[^<`>]*?)<)? # targets may specify an alias
- (?P[!~])? # targets may have a modifier
- (?P