diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
deleted file mode 100644
index 6dca44f..0000000
--- a/.github/workflows/docs.yml
+++ /dev/null
@@ -1,59 +0,0 @@
-name: Documentation
-on:
- pull_request:
- branches:
- - release
- - develop
- paths:
- - 'docs/**'
- - 'lib/pytest-lsp/**'
- push:
- branches:
- - release
- - develop
- paths:
- - 'docs/**'
- - 'lib/pytest-lsp/**'
-
-jobs:
- docs:
- name: Documentation
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v3
-
- - uses: actions/setup-python@v4
- with:
- python-version: "3.10"
-
- - run: |
- set -e
-
- python --version
- python -m pip install --upgrade pip
- python -m pip install -r docs/requirements.txt
-
- name: Setup Environment
-
- - id: build
- run: |
- set -e
-
- cd docs
- make html
- name: Build Docs
-
- - name: 'Upload Aritfact'
- uses: actions/upload-artifact@v3
- with:
- name: 'docs'
- path: 'docs/_build/${{ steps.build.outputs.version }}'
-
- - name: 'Publish Docs'
- uses: JamesIves/github-pages-deploy-action@v4
- with:
- branch: gh-pages
- folder: docs/_build/${{ steps.build.outputs.version }}
- target-folder: docs/${{ steps.build.outputs.version }}
- clean: true
- if: success() && ( startsWith(github.ref, 'refs/heads/release') || startsWith(github.ref, 'refs/heads/develop') )
diff --git a/.github/workflows/lsp-devtools-pr.yml b/.github/workflows/lsp-devtools-pr.yml
index 44d1d9e..70ee63f 100644
--- a/.github/workflows/lsp-devtools-pr.yml
+++ b/.github/workflows/lsp-devtools-pr.yml
@@ -13,7 +13,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
- python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
+ python-version: ["3.8", "3.9", "3.10", "3.11"]
os: [ubuntu-latest]
steps:
diff --git a/.github/workflows/lsp-devtools-release.yml b/.github/workflows/lsp-devtools-release.yml
new file mode 100644
index 0000000..05d20b2
--- /dev/null
+++ b/.github/workflows/lsp-devtools-release.yml
@@ -0,0 +1,66 @@
+name: 'Release: lsp-devtools'
+
+on:
+ push:
+ branches:
+ - release
+ paths:
+ - 'lib/lsp-devtools/**'
+
+jobs:
+ release:
+ name: lsp-devtools release
+ runs-on: ubuntu-latest
+ environment:
+ name: pypi
+ url: https://pypi.org/p/lsp-devtools
+ permissions:
+ id-token: write
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - uses: actions/setup-python@v4
+ with:
+ python-version: "3.10"
+
+ - run: |
+ sudo apt update
+ sudo apt install pandoc
+
+ python --version
+ python -m pip install --upgrade pip
+ python -m pip install build bump2version towncrier docutils
+ name: Install Build Tools
+
+ - run: |
+ set -e
+
+ ./scripts/make-release.sh lsp-devtools
+ name: Set Version
+ id: info
+
+ - name: Package
+ run: |
+ cd lib/lsp-devtools
+ python -m build
+
+ - name: 'Upload Artifact'
+ uses: actions/upload-artifact@v3
+ with:
+ name: 'dist'
+ path: lib/lsp-devtools/dist
+
+ - name: Publish
+ uses: pypa/gh-action-pypi-publish@release/v1
+ with:
+ packages-dir: lib/lsp-devtools/dist/
+
+ - name: Create Release
+ run: |
+ gh release create "${RELEASE_TAG}" \
+ --title "lsp-devtools v${VERSION} - ${RELEASE_DATE}" \
+ -F lib/lsp-devtools/.changes.html \
+ ./lib/lsp-devtools/dist/*
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/pytest-lsp-pr.yml b/.github/workflows/pytest-lsp-pr.yml
index 116aa12..cad5b0c 100644
--- a/.github/workflows/pytest-lsp-pr.yml
+++ b/.github/workflows/pytest-lsp-pr.yml
@@ -13,8 +13,8 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
- python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
- os: [ubuntu-latest]
+ python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
+ os: [ubuntu-latest, windows-latest]
steps:
- uses: actions/checkout@v3
@@ -23,6 +23,7 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
+ allow-prereleases: true
- run: |
python --version
@@ -37,24 +38,25 @@ jobs:
# dev version number e.g. v1.2.3-dev4
./scripts/make-release.sh pytest-lsp
name: Set Version
- if: matrix.python-version == '3.10'
+ if: matrix.python-version == '3.10' && matrix.os == 'ubuntu-latest'
- run: |
cd lib/pytest-lsp
version=$(echo ${{ matrix.python-version }} | tr -d .)
- python -m tox -e `tox -l | grep $version | tr '\n' ','`
+ python -m tox run -f "py${version}"
+ shell: bash
name: Test
- name: Package
run: |
cd lib/pytest-lsp
python -m build
- if: always() && matrix.python-version == '3.10'
+ if: always() && matrix.python-version == '3.10' && matrix.os == 'ubuntu-latest'
- name: 'Upload Artifact'
uses: actions/upload-artifact@v3
with:
name: 'dist'
path: lib/pytest-lsp/dist
- if: always() && matrix.python-version == '3.10'
+ if: always() && matrix.python-version == '3.10' && matrix.os == 'ubuntu-latest'
diff --git a/.github/workflows/pytest-lsp-release.yml b/.github/workflows/pytest-lsp-release.yml
new file mode 100644
index 0000000..4903dd5
--- /dev/null
+++ b/.github/workflows/pytest-lsp-release.yml
@@ -0,0 +1,67 @@
+name: 'Release: pytest-lsp'
+
+on:
+ push:
+ branches:
+ - release
+ paths:
+ - 'lib/pytest-lsp/**'
+
+jobs:
+ release:
+ name: pytest-lsp release
+ runs-on: ubuntu-latest
+ environment:
+ name: pypi
+ url: https://pypi.org/p/pytest-lsp
+ permissions:
+ id-token: write
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - uses: actions/setup-python@v4
+ with:
+ python-version: "3.10"
+
+ - run: |
+ sudo apt update
+ sudo apt install pandoc
+
+ python --version
+ python -m pip install --upgrade pip
+ python -m pip install build bump2version towncrier docutils
+
+ name: Install Build Tools
+
+ - run: |
+ set -e
+
+ ./scripts/make-release.sh pytest-lsp
+ name: Set Version
+ id: info
+
+ - name: Package
+ run: |
+ cd lib/pytest-lsp
+ python -m build
+
+ - name: 'Upload Artifact'
+ uses: actions/upload-artifact@v3
+ with:
+ name: 'dist'
+ path: lib/pytest-lsp/dist
+
+ - name: Publish
+ uses: pypa/gh-action-pypi-publish@release/v1
+ with:
+ packages-dir: lib/pytest-lsp/dist/
+
+ - name: Create Release
+ run: |
+ gh release create "${RELEASE_TAG}" \
+ --title "pytest-lsp v${VERSION} - ${RELEASE_DATE}" \
+ -F lib/pytest-lsp/.changes.html \
+ ./lib/pytest-lsp/dist/*
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
deleted file mode 100644
index d3a8814..0000000
--- a/.github/workflows/release.yml
+++ /dev/null
@@ -1,157 +0,0 @@
-name: Release
-
-on:
- push:
- branches:
- - release
-
-jobs:
- # Simple job the checks to see which parts we actually have to build.
- trigger:
- name: Trigger
- runs-on: ubuntu-latest
- outputs:
- lsp-devtools: ${{steps.check-lsp-devtools.outputs.build}}
- pytest-lsp: ${{steps.check-pytest-lsp.outputs.build}}
- steps:
- - uses: actions/checkout@v3
- with:
- fetch-depth: 0
-
- - run: |
- if [ -z "${BASE_REF}" ]; then
- echo "BASE=HEAD^" >> $GITHUB_ENV
- else
- echo "BASE=origin/${BASE_REF}" >> $GITHUB_ENV
- fi
- name: Determine base
- env:
- BASE_REF: ${{ github.base_ref }}
-
- - id: check-lsp-devtools
- run: |
- set -e
- echo ${BASE}
-
- ./scripts/should-build.sh lsp-devtools
- name: "Build lsp-devtools?"
-
- - id: check-pytest-lsp
- run: |
- set -e
- echo ${BASE}
-
- ./scripts/should-build.sh pytest-lsp
- name: "Build pytest-lsp?"
-
- lsp-devtools:
- name: lsp-devtools
- needs: trigger
- if: always() && needs.trigger.outputs.lsp-devtools
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/checkout@v3
-
- - uses: actions/setup-python@v4
- with:
- python-version: "3.10"
-
- - run: |
- sudo apt update
- sudo apt install pandoc
-
- python --version
- python -m pip install --upgrade pip
- python -m pip install build bump2version towncrier docutils
- name: Install Build Tools
-
- - run: |
- set -e
-
- ./scripts/make-release.sh lsp-devtools
- name: Set Version
- id: info
-
- - name: Package
- run: |
- cd lib/lsp-devtools
- python -m build
-
- - name: 'Upload Artifact'
- uses: actions/upload-artifact@v3
- with:
- name: 'dist'
- path: lib/lsp-devtools/dist
-
- - name: Publish
- id: assets
- run: |
- cd lib/lsp-devtools
- python -m pip install twine
- python -m twine upload dist/* -u alcarney -p ${{ secrets.PYPI_PASS }}
-
- - name: Create Release
- run: |
- gh release create "${RELEASE_TAG}" \
- --title "lsp-devtools v${VERSION} - ${RELEASE_DATE}" \
- -F lib/lsp-devtools/.changes.html \
- ./lib/lsp-devtools/dist/*
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
- pytest-lsp:
- name: pytest-lsp
- needs: trigger
- if: always() && needs.trigger.outputs.pytest-lsp
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/checkout@v3
-
- - uses: actions/setup-python@v4
- with:
- python-version: "3.10"
-
- - run: |
- sudo apt update
- sudo apt install pandoc
-
- python --version
- python -m pip install --upgrade pip
- python -m pip install build bump2version towncrier docutils
-
- name: Install Build Tools
-
- - run: |
- set -e
-
- ./scripts/make-release.sh pytest-lsp
- name: Set Version
- id: info
-
- - name: Package
- run: |
- cd lib/pytest-lsp
- python -m build
-
- - name: 'Upload Artifact'
- uses: actions/upload-artifact@v3
- with:
- name: 'dist'
- path: lib/pytest-lsp/dist
-
- - name: Publish
- run: |
- cd lib/pytest-lsp
- python -m pip install twine
- python -m twine upload dist/* -u alcarney -p ${{ secrets.PYPI_PASS }}
-
- - name: Create Release
- run: |
- gh release create "${RELEASE_TAG}" \
- --title "pytest-lsp v${VERSION} - ${RELEASE_DATE}" \
- -F lib/pytest-lsp/.changes.html \
- ./lib/pytest-lsp/dist/*
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
index f117763..dfd8b34 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+.coverage
.env
.tox
*.pyc
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 6bc33d3..c5c5f50 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -4,17 +4,18 @@ repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
+ - id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/psf/black
- rev: 23.3.0
+ rev: 23.9.1
hooks:
- id: black
exclude: 'lib/pytest-lsp/pytest_lsp/gen.py'
- repo: https://github.com/PyCQA/flake8
- rev: 6.0.0
+ rev: 6.1.0
hooks:
- id: flake8
args: [--config=lib/lsp-devtools/setup.cfg]
@@ -34,17 +35,18 @@ repos:
exclude: 'lib/pytest-lsp/pytest_lsp/gen.py'
- repo: https://github.com/pre-commit/mirrors-mypy
- rev: 'v1.3.0'
+ rev: 'v1.5.1'
hooks:
- id: mypy
name: mypy (pytest-lsp)
args: [--explicit-package-bases,--check-untyped-defs]
additional_dependencies:
- importlib-resources
+ - platformdirs
- pygls
- pytest
- pytest-asyncio
- - types-appdirs
+ - websockets
files: 'lib/pytest-lsp/pytest_lsp/.*\.py'
- id: mypy
@@ -54,8 +56,9 @@ repos:
- aiosqlite
- attrs
- importlib-resources
+ - platformdirs
- pygls
+ - stamina
- textual
- - types-appdirs
- websockets
files: 'lib/lsp-devtools/lsp_devtools/.*\.py'
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
new file mode 100644
index 0000000..411b29d
--- /dev/null
+++ b/.readthedocs.yaml
@@ -0,0 +1,23 @@
+# .readthedocs.yaml
+# Read the Docs configuration file
+# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
+
+# Required
+version: 2
+
+# Set the OS, Python version and other tools you might need
+build:
+ os: ubuntu-22.04
+ tools:
+ python: "3.11"
+
+# Build documentation in the "docs/" directory with Sphinx
+sphinx:
+ configuration: docs/conf.py
+
+# Optional but recommended, declare the Python requirements required
+# to build your documentation
+# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
+python:
+ install:
+ - requirements: docs/requirements.txt
diff --git a/docs/Makefile b/docs/Makefile
index 4ae965d..b16343c 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -14,7 +14,7 @@ endif
html-build:
BUILDDIR=$(BUILDDIR) sphinx-build -b html . _build/$(BUILDDIR)/en/
- echo "version=$(BUILDDIR)" >> $GITHUB_OUTPUT
+ echo "version=$(BUILDDIR)" >> $(GITHUB_OUTPUT)
html-local:
diff --git a/docs/_static/custom.css b/docs/_static/custom.css
new file mode 100644
index 0000000..2f2a077
--- /dev/null
+++ b/docs/_static/custom.css
@@ -0,0 +1,4 @@
+.scrollable-svg {
+ max-height: 450px;
+ overflow: auto;
+}
diff --git a/docs/conf.py b/docs/conf.py
index eaec57c..538e610 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -13,6 +13,9 @@
from docutils import nodes # noqa: E402
from sphinx.application import Sphinx # noqa: E402
+DEV_BUILD = os.getenv("BUILDDIR", None) == "latest"
+BRANCH = "develop" if DEV_BUILD else "release"
+
project = "LSP Devtools"
copyright = "2023, Alex Carney"
author = "Alex Carney"
@@ -24,6 +27,7 @@
"sphinx.ext.autodoc",
"sphinx.ext.napoleon",
"sphinx.ext.intersphinx",
+ "sphinx_copybutton",
"sphinx_design",
"supported_clients",
]
@@ -33,6 +37,7 @@
autodoc_typehints = "description"
intersphinx_mapping = {
+ "pygls": ("https://pygls.readthedocs.io/en/latest/", None),
"python": ("https://docs.python.org/3/", None),
"pytest": ("https://docs.pytest.org/en/stable/", None),
}
@@ -46,7 +51,20 @@
html_theme = "furo"
html_title = "LSP Devtools"
-# html_static_path = ["_static"]
+html_static_path = ["_static"]
+html_theme_options = {
+ "source_repository": "https://github.com/swyddfa/lsp-devtools/",
+ "source_branch": BRANCH,
+ "source_directory": "docs/",
+}
+
+if DEV_BUILD:
+ html_theme_options["announcement"] = (
+ "This is the unstable version of the documentation, features may change or "
+ "be removed without warning. "
+ 'Click here '
+ "to view the released version"
+ )
def lsp_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
@@ -60,4 +78,5 @@ def lsp_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
def setup(app: Sphinx):
+ app.add_css_file("custom.css")
app.add_role("lsp", lsp_role)
diff --git a/docs/images/lsp-devtools-architecture.svg b/docs/images/lsp-devtools-architecture.svg
new file mode 100644
index 0000000..58d2267
--- /dev/null
+++ b/docs/images/lsp-devtools-architecture.svg
@@ -0,0 +1,17 @@
+
diff --git a/docs/images/record-client-capabilities.svg b/docs/images/record-client-capabilities.svg
new file mode 100644
index 0000000..9108529
--- /dev/null
+++ b/docs/images/record-client-capabilities.svg
@@ -0,0 +1,1311 @@
+
diff --git a/docs/images/record-example.svg b/docs/images/record-example.svg
new file mode 100644
index 0000000..564b19a
--- /dev/null
+++ b/docs/images/record-example.svg
@@ -0,0 +1,192 @@
+
diff --git a/docs/images/record-log-messages.svg b/docs/images/record-log-messages.svg
new file mode 100644
index 0000000..55a89d2
--- /dev/null
+++ b/docs/images/record-log-messages.svg
@@ -0,0 +1,357 @@
+
diff --git a/docs/images/tui-screenshot.svg b/docs/images/tui-screenshot.svg
new file mode 100644
index 0000000..690b7e4
--- /dev/null
+++ b/docs/images/tui-screenshot.svg
@@ -0,0 +1,294 @@
+
diff --git a/docs/index.rst b/docs/index.rst
index 55a6b42..b274137 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -4,13 +4,25 @@ LSP Devtools
The LSP Devtools project provides a number of tools that aim to make the
process of developing language servers and clients easier.
+lsp-devtools
+------------
+
+.. toctree::
+ :hidden:
+ :caption: lsp-devtools
+
+ lsp-devtools/guide
+ lsp-devtools/changelog
+
+
+The `lsp-devtools `_ package provides a collection of CLI utilities that help inspect and visualise the interactions between a language client and a server.
+
+See the :doc:`lsp-devtools/guide/getting-started` guide for details.
pytest-lsp
----------
.. toctree::
- :maxdepth: 1
- :glob:
:hidden:
:caption: pytest-lsp
@@ -18,9 +30,10 @@ pytest-lsp
pytest-lsp/reference
pytest-lsp/changelog
+`pytest-lsp `_ is a pytest plugin for writing end-to-end tests for language servers.
-
-``pytest-lsp`` is a pytest plugin for writing end-to-end tests for language servers.
+.. literalinclude:: ./pytest-lsp/guide/window-log-message-output.txt
+ :language: none
It works by running the language server in a subprocess and communicating with it over stdio, just like a real language client.
This also means ``pytest-lsp`` can be used to test language servers written in any language - not just Python.
diff --git a/docs/lsp-devtools/changelog.rst b/docs/lsp-devtools/changelog.rst
new file mode 100644
index 0000000..9327b09
--- /dev/null
+++ b/docs/lsp-devtools/changelog.rst
@@ -0,0 +1,4 @@
+Changelog
+=========
+
+.. include:: ../../lib/lsp-devtools/CHANGES.rst
diff --git a/docs/lsp-devtools/guide.rst b/docs/lsp-devtools/guide.rst
new file mode 100644
index 0000000..01be0ae
--- /dev/null
+++ b/docs/lsp-devtools/guide.rst
@@ -0,0 +1,9 @@
+User Guide
+----------
+
+.. toctree::
+ :maxdepth: 2
+
+ guide/getting-started
+ guide/record-command
+ guide/tui-command
diff --git a/docs/lsp-devtools/guide/example-to-file-output.json b/docs/lsp-devtools/guide/example-to-file-output.json
new file mode 100644
index 0000000..87a4408
--- /dev/null
+++ b/docs/lsp-devtools/guide/example-to-file-output.json
@@ -0,0 +1,27 @@
+{'jsonrpc': '2.0', 'id': 1, 'params': {'rootUri': 'file:///var/home/username/Projects/lsp-devtools', 'workspaceFolders': [{'uri': 'file:///var/home/username/Projects/lsp-devtools', 'name': '/var/home/username/Projects/lsp-devtools'}], 'capabilities': {'window': {'workDoneProgress': True, 'showMessage': {'messageActionItem': {'additionalPropertiesSupport': False}}, 'showDocument': {'support': True}}, 'workspace': {'semanticTokens': {'refreshSupport': True}, 'workspaceFolders': True, 'applyEdit': True, 'configuration': True, 'symbol': {'dynamicRegistration': False, 'symbolKind': {'valueSet': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26]}, 'hierarchicalWorkspaceSymbolSupport': True}, 'didChangeWatchedFiles': {'relativePatternSupport': True, 'dynamicRegistration': False}, 'workspaceEdit': {'resourceOperations': ['rename', 'create', 'delete']}}, 'textDocument': {'typeDefinition': {'linkSupport': True}, 'definition': {'linkSupport': True}, 'signatureHelp': {'signatureInformation': {'activeParameterSupport': True, 'parameterInformation': {'labelOffsetSupport': True}, 'documentationFormat': ['markdown', 'plaintext']}, 'dynamicRegistration': False}, 'callHierarchy': {'dynamicRegistration': False}, 'declaration': {'linkSupport': True}, 'synchronization': {'didSave': True, 'willSave': True, 'dynamicRegistration': False, 'willSaveWaitUntil': True}, 'semanticTokens': {'requests': {'range': False, 'full': {'delta': True}}, 'formats': ['relative'], 'overlappingTokenSupport': True, 'dynamicRegistration': False, 'tokenTypes': ['namespace', 'type', 'class', 'enum', 'interface', 'struct', 'typeParameter', 'parameter', 'variable', 'property', 'enumMember', 'event', 'function', 'method', 'macro', 'keyword', 'modifier', 'comment', 'string', 'number', 'regexp', 'operator', 'decorator'], 'augmentsSyntaxTokens': True, 'tokenModifiers': ['declaration', 'definition', 'readonly', 'static', 'deprecated', 'abstract', 'async', 'modification', 'documentation', 'defaultLibrary'], 'multilineTokenSupport': False, 'serverCancelSupport': False}, 'references': {'dynamicRegistration': False}, 'documentHighlight': {'dynamicRegistration': False}, 'documentSymbol': {'dynamicRegistration': False, 'symbolKind': {'valueSet': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26]}, 'hierarchicalDocumentSymbolSupport': True}, 'implementation': {'linkSupport': True}, 'completion': {'dynamicRegistration': False, 'insertTextMode': 1, 'completionList': {'itemDefaults': ['commitCharacters', 'editRange', 'insertTextFormat', 'insertTextMode', 'data']}, 'completionItemKind': {'valueSet': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]}, 'contextSupport': True, 'completionItem': {'resolveSupport': {'properties': ['documentation', 'detail', 'additionalTextEdits']}, 'insertReplaceSupport': True, 'snippetSupport': True, 'commitCharactersSupport': True, 'preselectSupport': True, 'deprecatedSupport': True, 'documentationFormat': ['markdown', 'plaintext'], 'insertTextModeSupport': {'valueSet': [1, 2]}, 'labelDetailsSupport': True, 'tagSupport': {'valueSet': [1]}}}, 'hover': {'contentFormat': ['markdown', 'plaintext'], 'dynamicRegistration': False}, 'publishDiagnostics': {'relatedInformation': True, 'tagSupport': {'valueSet': [1, 2]}}, 'codeAction': {'dynamicRegistration': False, 'isPreferredSupport': True, 'dataSupport': True, 'resolveSupport': {'properties': ['edit']}, 'codeActionLiteralSupport': {'codeActionKind': {'valueSet': ['', 'quickfix', 'refactor', 'refactor.extract', 'refactor.inline', 'refactor.rewrite', 'source', 'source.organizeImports']}}}, 'rename': {'prepareSupport': True, 'dynamicRegistration': False}}}, 'rootPath': '/var/home/username/Projects/lsp-devtools', 'processId': 24997, 'clientInfo': {'version': '0.9.1', 'name': 'Neovim'}, 'initializationOptions': {'server': {'logLevel': 'debug'}, 'sphinx': {'buildDir': '${confDir}/_build'}}, 'trace': 'off'}, 'method': 'initialize'}
+{'params': {'type': 4, 'message': "[esbonio.lsp] Loaded extension 'esbonio.lsp.directives'"}, 'method': 'window/logMessage', 'jsonrpc': '2.0'}
+{'params': {'type': 4, 'message': "[esbonio.lsp] Loaded extension 'esbonio.lsp.roles'"}, 'method': 'window/logMessage', 'jsonrpc': '2.0'}
+{'params': {'type': 4, 'message': "[esbonio.lsp] Loaded extension 'esbonio.lsp.rst.directives'"}, 'method': 'window/logMessage', 'jsonrpc': '2.0'}
+{'params': {'type': 4, 'message': "[esbonio.lsp] Loaded extension 'esbonio.lsp.rst.roles'"}, 'method': 'window/logMessage', 'jsonrpc': '2.0'}
+{'params': {'type': 4, 'message': "[esbonio.lsp] Loaded extension 'esbonio.lsp.sphinx.autodoc'"}, 'method': 'window/logMessage', 'jsonrpc': '2.0'}
+{'params': {'type': 4, 'message': "[esbonio.lsp] Loaded extension 'esbonio.lsp.sphinx.codeblocks'"}, 'method': 'window/logMessage', 'jsonrpc': '2.0'}
+{'params': {'type': 4, 'message': "[esbonio.lsp] Loaded extension 'esbonio.lsp.sphinx.domains'"}, 'method': 'window/logMessage', 'jsonrpc': '2.0'}
+{'params': {'type': 4, 'message': "[esbonio.lsp] Loaded extension 'esbonio.lsp.sphinx.directives'"}, 'method': 'window/logMessage', 'jsonrpc': '2.0'}
+{'params': {'type': 4, 'message': "[esbonio.lsp] Loaded extension 'esbonio.lsp.sphinx.images'"}, 'method': 'window/logMessage', 'jsonrpc': '2.0'}
+{'params': {'type': 4, 'message': "[esbonio.lsp] Loaded extension 'esbonio.lsp.sphinx.includes'"}, 'method': 'window/logMessage', 'jsonrpc': '2.0'}
+{'params': {'type': 4, 'message': "[esbonio.lsp] Loaded extension 'esbonio.lsp.sphinx.roles'"}, 'method': 'window/logMessage', 'jsonrpc': '2.0'}
+{'id': 1, 'jsonrpc': '2.0', 'result': {'capabilities': {'textDocumentSync': {'openClose': True, 'change': 2, 'willSave': False, 'willSaveWaitUntil': False, 'save': True}, 'completionProvider': {'triggerCharacters': ['>', '.', ':', '`', '<', '/'], 'resolveProvider': True}, 'hoverProvider': True, 'definitionProvider': True, 'implementationProvider': {}, 'documentSymbolProvider': True, 'codeActionProvider': True, 'documentLinkProvider': {}, 'executeCommandProvider': {'commands': ['esbonio.server.build', 'esbonio.server.configuration', 'esbonio.server.preview']}, 'workspace': {'workspaceFolders': {'supported': True, 'changeNotifications': True}, 'fileOperations': {}}}, 'serverInfo': {'name': 'esbonio', 'version': '0.16.1'}}}
+{'jsonrpc': '2.0', 'method': 'initialized', 'params': {}}
+{'jsonrpc': '2.0', 'method': 'textDocument/didOpen', 'params': {'textDocument': {'uri': 'file:///var/home/username/Projects/lsp-devtools/docs/index.rst', 'text': 'LSP Devtools\n============\n\nThe LSP Devtools project provides a number of tools that aim to make the\nprocess of developing language servers and clients easier.\n\nlsp-devtools\n------------\n\n.. toctree::\n :hidden:\n :caption: lsp-devtools\n\n lsp-devtools/guide\n lsp-devtools/changelog\n\n\nThe `lsp-devtools `_ package provides a collection of CLI utilities that help inspect and visualise the interactions between a language client and a server.\n\nSee the :doc:`lsp-devtools/guide/getting-started` guide for details.\n\npytest-lsp\n----------\n\n.. toctree::\n :hidden:\n :caption: pytest-lsp\n\n pytest-lsp/guide\n pytest-lsp/reference\n pytest-lsp/changelog\n\n`pytest-lsp `_ is a pytest plugin for writing end-to-end tests for language servers.\n\n.. literalinclude:: ./pytest-lsp/guide/window-log-message-output.txt\n :language: none\n\nIt works by running the language server in a subprocess and communicating with it over stdio, just like a real language client.\nThis also means ``pytest-lsp`` can be used to test language servers written in any language - not just Python.\n\n``pytest-lsp`` relies on `pygls `__ for its language server protocol implementation.\n\nSee the :doc:`pytest-lsp/guide/getting-started` guide for details on how to write your first test case.\n', 'languageId': 'rst', 'version': 0}}}
+{'params': {'type': 4, 'message': '[esbonio.lsp] User Config {\n "buildDir": "${confDir}/_build"\n}'}, 'method': 'window/logMessage', 'jsonrpc': '2.0'}
+{'params': {'type': 4, 'message': "[esbonio.lsp] Workspace Folder: 'file:///var/home/username/Projects/lsp-devtools'"}, 'method': 'window/logMessage', 'jsonrpc': '2.0'}
+{'params': {'type': 4, 'message': '[esbonio.lsp] Sphinx Args {\n "buildername": "html",\n "confdir": "/var/home/username/Projects/lsp-devtools/docs",\n "confoverrides": {},\n "doctreedir": "/var/home/username/Projects/lsp-devtools/docs/_build/doctrees",\n "freshenv": false,\n "keep_going": false,\n "outdir": "/var/home/username/Projects/lsp-devtools/docs/_build/html",\n "parallel": 1,\n "srcdir": "/var/home/username/Projects/lsp-devtools/docs",\n "status": null,\n "tags": [],\n "verbosity": 0,\n "warning": null,\n "warningiserror": false\n}'}, 'method': 'window/logMessage', 'jsonrpc': '2.0'}
+{'params': {'type': 4, 'message': 'Running Sphinx v6.2.1'}, 'method': 'window/logMessage', 'jsonrpc': '2.0'}
+{'params': {'type': 4, 'message': '[esbonio.lsp] Traceback (most recent call last):\n File "/var/home/username/Projects/esbonio/.env/lib64/python3.11/site-packages/sphinx/registry.py", line 442, in load_extension\n mod = import_module(extname)\n ^^^^^^^^^^^^^^^^^^^^^^\n File "/usr/lib64/python3.11/importlib/__init__.py", line 126, in import_module\n return _bootstrap._gcd_import(name[level:], package, level)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File "", line 1204, in _gcd_import\n File "", line 1176, in _find_and_load\n File "", line 1140, in _find_and_load_unlocked\nModuleNotFoundError: No module named \'sphinx_copybutton\'\n\nThe above exception was the direct cause of the following exception:\n\nTraceback (most recent call last):\n File "/var/home/username/Projects/esbonio/lib/esbonio/esbonio/lsp/sphinx/__init__.py", line 149, in _initialize_sphinx\n return self.create_sphinx_app(self.user_config) # type: ignore\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File "/var/home/username/Projects/esbonio/lib/esbonio/esbonio/lsp/sphinx/__init__.py", line 343, in create_sphinx_app\n app = Sphinx(**self.sphinx_args)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^\n File "/var/home/username/Projects/esbonio/.env/lib64/python3.11/site-packages/sphinx/application.py", line 229, in __init__\n self.setup_extension(extension)\n File "/var/home/username/Projects/esbonio/.env/lib64/python3.11/site-packages/sphinx/application.py", line 404, in setup_extension\n self.registry.load_extension(self, extname)\n File "/var/home/username/Projects/esbonio/.env/lib64/python3.11/site-packages/sphinx/registry.py", line 445, in load_extension\n raise ExtensionError(__(\'Could not import extension %s\') % extname,\nsphinx.errors.ExtensionError: Could not import extension sphinx_copybutton (exception: No module named \'sphinx_copybutton\')'}, 'method': 'window/logMessage', 'jsonrpc': '2.0'}
+{'params': {'type': 4, 'message': '[esbonio.lsp] Publishing 1 diagnostics for: file:///var/home/username/Projects/esbonio/.env/lib64/python3.11/site-packages/sphinx/registry.py'}, 'method': 'window/logMessage', 'jsonrpc': '2.0'}
+{'params': {'uri': 'file:///var/home/username/Projects/esbonio/.env/lib64/python3.11/site-packages/sphinx/registry.py', 'diagnostics': [{'range': {'start': {'line': 444, 'character': 0}, 'end': {'line': 445, 'character': 0}}, 'message': 'Could not import extension sphinx_copybutton', 'severity': 1, 'source': 'conf.py'}]}, 'method': 'textDocument/publishDiagnostics', 'jsonrpc': '2.0'}
+{'method': 'esbonio/buildComplete', 'jsonrpc': '2.0', 'params': {'config': {'sphinx': {'buildDir': '/var/home/username/Projects/lsp-devtools/docs/_build/html', 'confDir': '/var/home/username/Projects/lsp-devtools/docs', 'doctreeDir': '/var/home/username/Projects/lsp-devtools/docs/_build/doctrees', 'srcDir': '/var/home/username/Projects/lsp-devtools/docs', 'command': ['sphinx-build', '-M', 'html', '/var/home/username/Projects/lsp-devtools/docs', '/var/home/username/Projects/lsp-devtools/docs/_build/html', '-d', '/var/home/username/Projects/lsp-devtools/docs/_build/doctrees'], 'version': '6.2.1'}, 'server': {'logLevel': 'debug'}}, 'error': True, 'warnings': 0}}
+{'jsonrpc': '2.0', 'method': 'textDocument/didChange', 'params': {'contentChanges': [{'text': '\n\n', 'range': {'start': {'character': 58, 'line': 4}, 'end': {'character': 0, 'line': 5}}, 'rangeLength': 1}], 'textDocument': {'uri': 'file:///var/home/username/Projects/lsp-devtools/docs/index.rst', 'version': 4}}}
+{'jsonrpc': '2.0', 'id': 2, 'method': 'shutdown'}
+{'id': 2, 'jsonrpc': '2.0', 'result': None}
+{'jsonrpc': '2.0', 'method': 'exit'}
diff --git a/docs/lsp-devtools/guide/getting-started.rst b/docs/lsp-devtools/guide/getting-started.rst
new file mode 100644
index 0000000..6e8aace
--- /dev/null
+++ b/docs/lsp-devtools/guide/getting-started.rst
@@ -0,0 +1,105 @@
+Getting Started
+===============
+
+.. highlight:: none
+
+This guide will introduce you to the tools available in the ``lsp-devtools`` package.
+If you have not done so already, you can install it using ``pipx`` ::
+
+ pipx install lsp-devtools
+
+.. admonition:: Did you say pipx?
+
+ `pipx `_ is a tool that automates the process of installing Python packages into their own isolated Python environments - useful for standalone applications like ``lsp-devtools``
+
+The LSP Agent
+-------------
+
+In order to use most of the tools in ``lsp-devtools`` you need to wrap your language server with the LSP Agent.
+The agent is a simple program that sits inbetween a language client and the server as shown in the diagram below.
+
+.. figure:: /images/lsp-devtools-architecture.svg
+
+ ``lsp-devtools`` architecture
+
+The agent acts as a messenger, forwarding messages from the client to the server and vice versa.
+However, it sends an additional copy of each message over a local TCP connection to some "Server" application - typically another ``lsp-devtools`` command like ``record`` or ``tui``.
+
+In general, using ``lsp-devtools`` can be broken down into a 3 step process.
+
+#. Configure your language client to launch your language server via the agent, rather than launching it directly.
+
+#. Start the server application e.g. ``lsp-devtools record`` or ``lsp-devtools tui``
+
+#. Start your language client.
+
+.. _lsp-devtools-configure-client:
+
+Configuring your client
+^^^^^^^^^^^^^^^^^^^^^^^
+
+In order to wrap your language server with the LSP Agent, you need to be able to modify the command your language client uses to start your language server to the following::
+
+ lsp-devtools agent --
+
+The ``agent`` command will interpret anything given after the double dashes (``--``) to be the command used to invoke your language server.
+By default, the agent will attempt to connect to a server application on ``localhost:8765`` but this can be changed using the ``--host `` and ``--port `` arguments::
+
+ lsp-devtools agent --host 127.0.0.1 --port 1234 --
+
+.. tip::
+
+ Since the agent only requires your server's start command, you can use ``lsp-devtools`` with a server written in any language.
+
+
+As an example, let's configure Neovim to launch the ``esbonio`` language server via the agent.
+Using `nvim-lspconfig `_ a standard configuration might look something like the following
+
+.. code-block:: lua
+
+ lspconfig.esbonio.setup{
+ capabilities = capabilities,
+ cmd = { "esbonio" },
+ filetypes = {"rst"},
+ init_options = {
+ server = {
+ logLevel = "debug"
+ },
+ sphinx = {
+ buildDir = "${confDir}/_build"
+ }
+ },
+ on_attach = on_attach,
+ }
+
+To update this to launch the server via the agent, we need only modify the ``cmd`` field (or add one if it does not exist) to include ``lsp-devtools agent --``
+
+.. code-block:: diff
+
+ lspconfig.esbonio.setup{
+ capabilities = capabilities,
+ - cmd = { "esbonio" },
+ + cmd = { "lsp-devtools", "agent", "--", "esbonio" },
+ ...
+ }
+
+Server Applications
+-------------------
+
+Once you have your client configured, you need to start the application the agent is going to try to connect to.
+Currently ``lsp-devtools`` provides the following applications
+
+``lsp-devtools record``
+ As the name suggests, this command supports recording all (or a subset of) messages in a LSP session to a text file or SQLite database.
+ However, it can also print these messages direct to the console with support for filtering and custom formatting of message contents.
+
+ .. figure:: /images/record-example.svg
+
+ See :doc:`/lsp-devtools/guide/record-command` for details
+
+``lsp-devtools tui``
+ An interactive terminal application, powered by `textual `_.
+
+ .. figure:: /images/tui-screenshot.svg
+
+ See :doc:`/lsp-devtools/guide/tui-command` for details
diff --git a/docs/lsp-devtools/guide/record-command.rst b/docs/lsp-devtools/guide/record-command.rst
new file mode 100644
index 0000000..b195ced
--- /dev/null
+++ b/docs/lsp-devtools/guide/record-command.rst
@@ -0,0 +1,315 @@
+Recording Sessions
+==================
+
+.. important::
+
+ This guide assumes that you have already :ref:`configured your client ` to wrap your language server with the LSP Agent.
+
+.. highlight:: none
+
+.. program:: lsp-devtools record
+
+The ``lsp-devtools record`` command can be used to either record an LSP session to a file, SQLite database or print the received messages direct to the console.
+Running the ``lsp-devtools record`` command you should see a message like the following::
+
+ $ lsp-devtools record
+ Waiting for connection on localhost:8765...
+
+once the agent connects, the record command will by default, start printing all LSP messages to the console, with the JSON contents pretty printed.
+
+.. figure:: /images/record-example.svg
+
+
+Example Commands
+----------------
+
+Here are some example usages of the ``record`` command that you may find useful.
+
+**Capture the client's capabilities**
+
+The following command will only capture and show the ``ClientCapabilities`` sent during the ``initialize`` request - useful for :ref:`adding clients to pytest-lsp `! 😉
+
+::
+
+ lsp-devtools record -f "{.params.clientInfo.name} v{.params.clientInfo.version}\\n{.params.capabilities}"
+
+.. figure:: /images/record-client-capabilities.svg
+ :figclass: scrollable-svg
+
+
+**Format and show any window/logMessages**
+
+This can be used to replicate the ``Output`` log panel in VSCode in editors that do not provide a similar facility.
+
+::
+
+ lsp-devtools record -f "{.params.type|MessageType}: {.params.message}"
+
+.. figure:: /images/record-log-messages.svg
+ :figclass: scrollable-svg
+
+Read on for a comprehensive overview of all the available command line options.
+
+Connection Options
+------------------
+
+By default, the LSP agent and other commands will attempt to connect to each other on ``localhost:8765``.
+The following options can be used to change this behavior
+
+.. option:: --host
+
+ The host to bind to.
+
+.. option:: -p , --port
+
+ The port number to open the connection on.
+
+
+Alternate Destinations
+----------------------
+
+As well as printing to console, the record command supports a number of other output destinations.
+
+.. option:: --to-file
+
+ Saves all collected messages to a plain text file with each line representing a complete JSON-RPC message::
+
+ lsp-devtools record --to-file example.json
+
+ See :download:`here <./example-to-file-output.json>` for example of the output produced by this command.
+
+.. option:: --to-sqlite
+
+ Save messages to a SQLite database::
+
+ lsp-devtools record --to-sqlite example.db
+
+ This database can then be opened in other tools like `datasette `_, `SQLite Browser `_ or even ``lsp-devtools`` own :doc:`/lsp-devtools/guide/tui-command`.
+
+ .. dropdown:: DB Schema
+
+ Here is the schema currently used by ``lsp-devtools``.
+ **Note:** Except perhaps the base ``protocol`` table, this schema is not stable and may change between ``lsp-devtools`` releases.
+
+ .. literalinclude:: ../../../lib/lsp-devtools/lsp_devtools/handlers/dbinit.sql
+ :language: sql
+
+.. option:: --save-output
+
+ Print to console as normal but additionally, the ouput will be saved into a text file using the
+ `export `__
+ feature of rich's ``Console`` object::
+
+ lsp-devtools record --save-output filename.{html,svg,txt}
+
+ Depending on the file extension used, this will save the output as plain text or rendered as an SVG image or HTML webpage - useful for generating screenshots for your documentation!
+
+Filtering Messages
+------------------
+
+Once it gets going, the LSP protocol can generate *a lot* of messages!
+To help you focus on the messages you are interested in the ``record`` command provides the following options for selecting a subset of messages to show.
+
+.. option:: --message-source