diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5fb96278d..cac84165d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -110,13 +110,7 @@ jobs: - run: | cd code - rm -r dist - npm run compile - name: Build Extension - - - run: | - cd code npm run package vsix=$(find . -name '*.vsix' -exec basename {} \;) @@ -297,7 +291,9 @@ jobs: - run: | cd lib/esbonio - python -m tox -e py`echo ${{ matrix.python-version }} | tr -d .` + + version=$(echo ${{ matrix.python-version }} | tr -d .) + python -m tox -e `tox -l | grep $version | tr '\n' ','` name: Test - name: Package diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..08e4459d3 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,24 @@ +repos: + +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.0.1 + hooks: + - id: end-of-file-fixer + - id: trailing-whitespace + +- repo: https://github.com/psf/black + rev: 21.5b2 + hooks: + - id: black + +- repo: https://github.com/PyCQA/flake8 + rev: 3.9.2 + hooks: + - id: flake8 + args: [--config=lib/esbonio/setup.cfg] + +- repo: https://github.com/asottile/reorder_python_imports + rev: v2.5.0 + hooks: + - id: reorder-python-imports + args: [--application-directories=lib:esbonio] diff --git a/.vscode/launch.json b/.vscode/launch.json index c422769c1..ebabdfee3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -89,4 +89,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/.vscode/settings.json b/.vscode/settings.json index a176e2fdf..5eddb75fe 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -26,5 +26,11 @@ }, "python.pythonPath": "${workspaceRoot}/.env/bin/python", "python.formatting.provider": "black", + "python.testing.pytestArgs": [ + "lib/esbonio/tests" + ], + "python.testing.unittestEnabled": false, + "python.testing.nosetestsEnabled": false, + "python.testing.pytestEnabled": true, //"esbonio.trace.server": "verbose" -} \ No newline at end of file +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json index a49a998f1..964bf3950 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -88,4 +88,4 @@ } } ] -} \ No newline at end of file +} diff --git a/README.md b/README.md index cd5778305..d3407b5e8 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,12 @@ +![Esbonio logo](./resources/io.github.swyddfa.Esbonio.svg) # Esbonio +[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/swyddfa/esbonio/develop.svg)](https://results.pre-commit.ci/latest/github/swyddfa/esbonio/develop) This is a monorepo containing a number of subprojects | Folder | Description | |-|-| | `code/` | A VSCode extension for editing Sphinx projects
[![Visual Studio Marketplace Version](https://img.shields.io/visual-studio-marketplace/v/swyddfa.esbonio?style=flat-square)![Visual Studio Marketplace Installs](https://img.shields.io/visual-studio-marketplace/i/swyddfa.esbonio?style=flat-square)![Visual Studio Marketplace Downloads](https://img.shields.io/visual-studio-marketplace/d/swyddfa.esbonio?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=swyddfa.esbonio)| -| `lib/esbonio/` | A Language Server for Sphinx projects.
[![PyPI](https://img.shields.io/pypi/v/esbonio?style=flat-square)![PyPI - Downloads](https://img.shields.io/pypi/dm/esbonio?style=flat-square)](https://pypistats.org/packages/esbonio) | +| `lib/esbonio/` | A Language Server for Sphinx projects.
[![PyPI](https://img.shields.io/pypi/v/esbonio?style=flat-square)![PyPI - Downloads](https://img.shields.io/pypi/dm/esbonio?style=flat-square)](https://pypistats.org/packages/esbonio) | | `lib/esbonio-extensions/` | A collection of Sphinx extensions
[![PyPI](https://img.shields.io/pypi/v/esbonio-extensions?style=flat-square)![PyPI - Downloads](https://img.shields.io/pypi/dm/esbonio-extensions?style=flat-square)](https://pypistats.org/packages/esbonio-extensions) | diff --git a/code/.bumpversion.cfg b/code/.bumpversion.cfg index df197fe7c..472c2d079 100644 --- a/code/.bumpversion.cfg +++ b/code/.bumpversion.cfg @@ -3,7 +3,7 @@ current_version = 0.6.2 commit = False tag = False parse = (?P\d+)\.(?P\d+)\.(?P\d+)(-dev(?P\d+))? -serialize = +serialize = {major}.{minor}.{patch}-dev{dev} {major}.{minor}.{patch} diff --git a/code/.vscodeignore b/code/.vscodeignore index b5a1b4143..e4c934178 100644 --- a/code/.vscodeignore +++ b/code/.vscodeignore @@ -2,8 +2,10 @@ changes node_modules src/** dist/**/*.map +dist/test syntaxes/tests .bumpversion.cfg +.pytest_cache CHANGES.rst pyproject.toml tsconfig.json diff --git a/code/CHANGES.rst b/code/CHANGES.rst index ecdb9c2dc..aaa2864da 100644 --- a/code/CHANGES.rst +++ b/code/CHANGES.rst @@ -24,7 +24,7 @@ Misc - Improvements to the development experience (`#170 `_) -v0.6.0 - 2021-05-07v0.6.0 - 2021-05-07 +v0.6.0 - 2021-05-07 ------------------- Features diff --git a/code/README.md b/code/README.md index 9d19681b4..8bff2aa97 100644 --- a/code/README.md +++ b/code/README.md @@ -5,6 +5,33 @@ Esbonio is an extension that provides a language server for working with [Sphinx](https://www.sphinx-doc.org/en/master/) documentation projects. +## Features + +### Preview + +The extension can show a HTML preview of the documentation + +![HTML Preview](../resources/images/vscode-preview-demo.gif) + +### Completions + +The language server can provide completion suggestions in various contexts + +![Completion Demo](../resources/images/completion-demo.gif) + +### Diagnostics + +Errors from a build are published to VSCode as diagnostics + +![Diagnostics](../resources/images/diagnostic-sphinx-errors-demo.png) + +### Syntax Highlighting + +This extension also offers a simple grammar definition to enable some basic +syntax highlighting + +![Syntax Highlighting](../resources/images/syntax-highlighting-demo.png) + ## Setup The language server works by wrapping an instance of Sphinx's application object, @@ -54,46 +81,6 @@ To also manage updates manually, be sure to look at the [documentation](https://swyddfa.github.io/esbonio/docs/lsp/editors/vscode.html#configuration) for options on how to disable automatic updates. -## Features - -### Completions - -The language server can provide completion suggestions in various contexts -#### Directives - -Completion suggestions are offered for the directives themselves, as well as any options -that they expose. - -![Directive Completions](../resources/images/complete-directive-demo.gif) - -#### Roles - -In the case of roles, completions can also be offered for the targets of certain -[supported](https://swyddfa.github.io/esbonio/docs/lsp/features.html#roles) role types - -![Role Completions](../resources/images/complete-role-demo.gif) - -#### Inter Sphinx - -The [intersphinx](https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html) -extension can be used to easily link to other Sphinx projects. If configured, the language -server will offer suggestions when appropriate - -![InterSphinx Completions](../resources/images/complete-intersphinx-demo.gif) - -### Diagnostics - -Errors from a build are published to VSCode as diagnostics - -![Diagnostics](../resources/images/diagnostic-sphinx-errors-demo.png) - -### Syntax Highlighting - -This extension also offers a simple grammar definition to enable some basic -syntax highlighting - -![Syntax Highlighting](../resources/images/syntax-highlighting-demo.png) - ## Alternatives This project was created to scratch an itch, if it happens to also scratch an itch diff --git a/code/changes/185.misc.rst b/code/changes/185.misc.rst new file mode 100644 index 000000000..f03f6e224 --- /dev/null +++ b/code/changes/185.misc.rst @@ -0,0 +1,4 @@ +The cli arguments ``--cache-dir``, ``--log-filter``, ``--log-level`` and +``--hide-sphinx-output`` have been replaced with the configuration +parameters ``esbonio.sphinx.buildDir``, ``esbonio.server.logFilter``, +``esbonio.logLevel`` and ``esbonio.server.hideSphinxOutput`` respectively diff --git a/code/changes/190.feature.rst b/code/changes/190.feature.rst new file mode 100644 index 000000000..3336559f4 --- /dev/null +++ b/code/changes/190.feature.rst @@ -0,0 +1 @@ +Add the ability to preview the output from the ``html`` builder.` diff --git a/code/changes/192.misc.rst b/code/changes/192.misc.rst new file mode 100644 index 000000000..a2b42d95d --- /dev/null +++ b/code/changes/192.misc.rst @@ -0,0 +1,3 @@ +The language server's startup sequence has been reworked. Language clients are now +required to provide configuration parameters under the ``initializationOptions`` field +in the ``initialize`` request. diff --git a/code/changes/194.feature.rst b/code/changes/194.feature.rst new file mode 100644 index 000000000..f6b322981 --- /dev/null +++ b/code/changes/194.feature.rst @@ -0,0 +1 @@ +Add a statusbar item that indicates the state of the language server. diff --git a/code/changes/203.fix.rst b/code/changes/203.fix.rst new file mode 100644 index 000000000..d001c4fa8 --- /dev/null +++ b/code/changes/203.fix.rst @@ -0,0 +1 @@ +Fix incorrect syntax highlighting of multiple links on a single line diff --git a/code/changes/204.fix.rst b/code/changes/204.fix.rst new file mode 100644 index 000000000..d6d659a1f --- /dev/null +++ b/code/changes/204.fix.rst @@ -0,0 +1,2 @@ +VSCode now treats ``*`` characters as quotes, meaning selecting some text and entering +a ``*`` will automatically surround that text rather than replacing it. diff --git a/code/changes/205.feature.rst b/code/changes/205.feature.rst new file mode 100644 index 000000000..2c6f64f31 --- /dev/null +++ b/code/changes/205.feature.rst @@ -0,0 +1 @@ +VSCode will now syntax highlight C, C++, Javascript and Typescript code blocks diff --git a/code/changes/github-template.html b/code/changes/github-template.html index 322eed7d6..aaff9a3bb 100644 --- a/code/changes/github-template.html +++ b/code/changes/github-template.html @@ -1,3 +1,3 @@ %(body_pre_docinfo)s %(docinfo)s -%(body)s \ No newline at end of file +%(body)s diff --git a/code/icon.png b/code/icon.png new file mode 100644 index 000000000..008694419 Binary files /dev/null and b/code/icon.png differ diff --git a/code/package-lock.json b/code/package-lock.json index dce481615..10e29cecf 100644 --- a/code/package-lock.json +++ b/code/package-lock.json @@ -1,6 +1,6 @@ { "name": "esbonio", - "version": "0.5.1", + "version": "0.6.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -52,6 +52,17 @@ "@types/node": "*" } }, + "@types/jsdom": { + "version": "16.2.10", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-16.2.10.tgz", + "integrity": "sha512-q3aIjp3ehhVSXSbvNyuireAfvU2umRiZ2aLumyeZewCnoNaokrRDdTu5IvaeE9pzNtWHXrUnM9lb22Vl3W08EA==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/parse5": "*", + "@types/tough-cookie": "*" + } + }, "@types/json-schema": { "version": "7.0.7", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", @@ -76,12 +87,24 @@ "integrity": "sha512-+gaugz6Oce6ZInfI/tK4Pq5wIIkJMEJUu92RB3Eu93mtj4wjjjz9EB5mLp5s1pSsLXdC/CPut/xF20ZzAQJbTA==", "dev": true }, + "@types/parse5": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.0.tgz", + "integrity": "sha512-oPwPSj4a1wu9rsXTEGIJz91ISU725t0BmSnUhb57sI+M8XEmvUop84lzuiYdq0Y5M6xLY8DBPg0C2xEQKLyvBA==", + "dev": true + }, "@types/semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.5.tgz", "integrity": "sha512-iotVxtCCsPLRAvxMFFgxL8HD2l4mAZ2Oin7/VJ2ooWO0VOK4EGOGmZWZn1uCq7RofR3I/1IOSjCHlFT71eVK0Q==", "dev": true }, + "@types/tough-cookie": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A==", + "dev": true + }, "@types/vscode": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.52.0.tgz", @@ -273,11 +296,36 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true }, + "abab": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", + "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==" + }, "acorn": { "version": "8.2.4", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.2.4.tgz", - "integrity": "sha512-Ibt84YwBDDA890eDiDCEqcbwvHlBvzzDkU2cGBBDDI1QWT12jTiXIOn2CIw5KK4i6N5Z2HUxwYjzriDyqaqqZg==", - "dev": true + "integrity": "sha512-Ibt84YwBDDA890eDiDCEqcbwvHlBvzzDkU2cGBBDDI1QWT12jTiXIOn2CIw5KK4i6N5Z2HUxwYjzriDyqaqqZg==" + }, + "acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "requires": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==" + } + } + }, + "acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==" }, "agent-base": { "version": "6.0.2", @@ -292,7 +340,6 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -343,6 +390,34 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" + }, "azure-devops-node-api": { "version": "10.2.2", "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-10.2.2.tgz", @@ -358,6 +433,14 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, "big-integer": { "version": "1.6.48", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz", @@ -416,6 +499,11 @@ "fill-range": "^7.0.1" } }, + "browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" + }, "browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", @@ -481,6 +569,11 @@ "integrity": "sha512-k/RYs6zc/fjbxTjaWZemeSmOjO0JJV+KguOBA3NwPup8uzxM1cMhR2BD9XmO86GuqaqTCO8CgkgH9Rz//vdDiA==", "dev": true }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, "chainsaw": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", @@ -628,6 +721,14 @@ "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", "dev": true }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, "commander": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", @@ -642,8 +743,7 @@ "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "cross-spawn": { "version": "7.0.3", @@ -675,6 +775,44 @@ "integrity": "sha512-qxyKHQvgKwzwDWC/rGbT821eJalfupxYW2qbSJSAtdSTimsr/MlaGONoNLllaUPZWf8QnbcKM/kPVYUQuEKAFA==", "dev": true }, + "cssom": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", + "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==" + }, + "cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "requires": { + "cssom": "~0.3.6" + }, + "dependencies": { + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" + } + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "data-urls": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", + "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "requires": { + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" + } + }, "debug": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", @@ -698,6 +836,21 @@ "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", "dev": true }, + "decimal.js": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.1.tgz", + "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==" + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, "denodeify": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", @@ -727,6 +880,21 @@ "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", "dev": true }, + "domexception": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", + "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", + "requires": { + "webidl-conversions": "^5.0.0" + }, + "dependencies": { + "webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==" + } + } + }, "domhandler": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.2.0.tgz", @@ -756,6 +924,15 @@ "readable-stream": "^2.0.2" } }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, "electron-to-chromium": { "version": "1.3.727", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.727.tgz", @@ -824,6 +1001,25 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, + "escodegen": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", + "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "requires": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==" + } + } + }, "eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -834,6 +1030,11 @@ "estraverse": "^4.1.1" } }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, "esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", @@ -857,6 +1058,11 @@ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + }, "events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -880,17 +1086,30 @@ "strip-final-newline": "^2.0.0" } }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, "fastest-levenshtein": { "version": "1.0.12", @@ -932,6 +1151,21 @@ "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", "dev": true }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -997,6 +1231,14 @@ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, "glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", @@ -1038,6 +1280,20 @@ "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + } + }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -1065,6 +1321,14 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, + "html-encoding-sniffer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", + "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "requires": { + "whatwg-encoding": "^1.0.5" + } + }, "htmlparser2": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", @@ -1088,6 +1352,16 @@ "debug": "4" } }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, "https-proxy-agent": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", @@ -1104,6 +1378,14 @@ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, "import-local": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", @@ -1196,12 +1478,22 @@ "isobject": "^3.0.1" } }, + "is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" + }, "is-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", "dev": true }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -1220,6 +1512,11 @@ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", "dev": true }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, "jest-worker": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", @@ -1257,17 +1554,64 @@ "argparse": "^2.0.1" } }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "jsdom": { + "version": "16.5.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.5.3.tgz", + "integrity": "sha512-Qj1H+PEvUsOtdPJ056ewXM4UJPCi4hhLA8wpiz9F2YvsRBhuFsXxtrIFAgGBDynQA9isAMGE91PfUYbdMPXuTA==", + "requires": { + "abab": "^2.0.5", + "acorn": "^8.1.0", + "acorn-globals": "^6.0.0", + "cssom": "^0.4.4", + "cssstyle": "^2.3.0", + "data-urls": "^2.0.0", + "decimal.js": "^10.2.1", + "domexception": "^2.0.1", + "escodegen": "^2.0.0", + "html-encoding-sniffer": "^2.0.1", + "is-potential-custom-element-name": "^1.0.0", + "nwsapi": "^2.2.0", + "parse5": "6.0.1", + "request": "^2.88.2", + "request-promise-native": "^1.0.9", + "saxes": "^5.0.1", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.0.0", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^2.0.0", + "webidl-conversions": "^6.1.0", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.5.0", + "ws": "^7.4.4", + "xml-name-validator": "^3.0.0" + } + }, "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", "dev": true }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "json5": { "version": "2.2.0", @@ -1278,6 +1622,17 @@ "minimist": "^1.2.5" } }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -1290,6 +1645,15 @@ "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "dev": true }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, "linkify-it": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", @@ -1334,8 +1698,7 @@ "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "log-symbols": { "version": "4.0.0", @@ -1476,14 +1839,12 @@ "mime-db": { "version": "1.47.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz", - "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==", - "dev": true + "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==" }, "mime-types": { "version": "2.1.30", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz", "integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==", - "dev": true, "requires": { "mime-db": "1.47.0" } @@ -1639,6 +2000,16 @@ "boolbase": "^1.0.0" } }, + "nwsapi": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", + "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==" + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, "object-inspect": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.2.tgz", @@ -1672,6 +2043,19 @@ "nan": "^2.14.0" } }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", @@ -1738,8 +2122,7 @@ "parse5": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" }, "parse5-htmlparser2-tree-adapter": { "version": "6.0.1", @@ -1780,6 +2163,11 @@ "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", "dev": true }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, "picomatch": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz", @@ -1834,6 +2222,11 @@ } } }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" + }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -1846,11 +2239,15 @@ "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", "dev": true }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "qs": { "version": "6.10.1", @@ -1920,6 +2317,78 @@ "resolve": "^1.9.0" } }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + } + } + }, + "request-promise-core": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", + "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", + "requires": { + "lodash": "^4.17.19" + } + }, + "request-promise-native": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", + "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", + "requires": { + "request-promise-core": "1.1.4", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + }, + "dependencies": { + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + } + } + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -1963,8 +2432,20 @@ "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "requires": { + "xmlchars": "^2.2.0" + } }, "schema-utils": { "version": "3.0.0", @@ -2050,8 +2531,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "source-map-support": { "version": "0.5.19", @@ -2069,6 +2549,27 @@ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" + }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -2126,6 +2627,11 @@ "has-flag": "^3.0.0" } }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" + }, "tapable": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", @@ -2189,6 +2695,24 @@ "is-number": "^7.0.0" } }, + "tough-cookie": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", + "requires": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.1.2" + } + }, + "tr46": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz", + "integrity": "sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==", + "requires": { + "punycode": "^2.1.1" + } + }, "traverse": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", @@ -2271,6 +2795,27 @@ "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", "dev": true }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "requires": { + "prelude-ls": "~1.1.2" + } + }, "typed-rest-client": { "version": "1.8.4", "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.4.tgz", @@ -2300,6 +2845,11 @@ "integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==", "dev": true }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, "unzipper": { "version": "0.10.11", "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.11.tgz", @@ -2322,7 +2872,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, "requires": { "punycode": "^2.1.0" } @@ -2339,12 +2888,27 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, "v8-compile-cache": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "dev": true }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, "vsce": { "version": "1.88.0", "resolved": "https://registry.npmjs.org/vsce/-/vsce-1.88.0.tgz", @@ -2462,6 +3026,22 @@ } } }, + "w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "requires": { + "browser-process-hrtime": "^1.0.0" + } + }, + "w3c-xmlserializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", + "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "requires": { + "xml-name-validator": "^3.0.0" + } + }, "watchpack": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.1.1.tgz", @@ -2472,6 +3052,11 @@ "graceful-fs": "^4.1.2" } }, + "webidl-conversions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==" + }, "webpack": { "version": "5.36.2", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.36.2.tgz", @@ -2570,6 +3155,29 @@ "source-map": "^0.6.1" } }, + "whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "requires": { + "iconv-lite": "0.4.24" + } + }, + "whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" + }, + "whatwg-url": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.5.0.tgz", + "integrity": "sha512-fy+R77xWv0AiqfLl4nuGUlQ3/6b5uNfQ4WAbGQVMYshCTCCPK9psC1nWh3XHuxGVCtlcDDQPQW1csmmIQo+fwg==", + "requires": { + "lodash": "^4.7.0", + "tr46": "^2.0.2", + "webidl-conversions": "^6.1.0" + } + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -2594,6 +3202,11 @@ "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", "dev": true }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" + }, "workerpool": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", @@ -2675,6 +3288,21 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, + "ws": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz", + "integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==" + }, + "xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" + }, + "xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" + }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/code/package.json b/code/package.json index c1937e454..6f49a6cca 100644 --- a/code/package.json +++ b/code/package.json @@ -1,5 +1,6 @@ { "name": "esbonio", + "displayName": "Esbonio", "description": "An extension for editing sphinx projects", "repository": { "url": "https://github.com/swyddfa/esbonio" @@ -14,7 +15,7 @@ "webpack": "webpack --mode development", "webpack-dev": "webpack --mode development --watch", "test": "npm run compile-test && node ./dist/test/runTests.js", - "test-grammar": "vscode-tmgrammar-test -c -s source.rst -g syntaxes/rst.tmLanguage.json -g syntaxes/tests/css.tmLanguage.json -g syntaxes/tests/html.tmLanguage.json -g syntaxes/tests/json.tmLanguage.json -g syntaxes/tests/python.tmLanguage.json -g syntaxes/tests/yaml.tmLanguage.json -t \"syntaxes/**/*.rst\"", + "test-grammar": "vscode-tmgrammar-test -c -s source.rst -g syntaxes/rst.tmLanguage.json -g syntaxes/tests/c.tmLanguage.json -g syntaxes/tests/cpp.tmLanguage.json -g syntaxes/tests/css.tmLanguage.json -g syntaxes/tests/html.tmLanguage.json -g syntaxes/tests/js.tmLanguage.json -g syntaxes/tests/json.tmLanguage.json -g syntaxes/tests/python.tmLanguage.json -g syntaxes/tests/ts.tmLanguage.json -g syntaxes/tests/yaml.tmLanguage.json -t \"syntaxes/**/*.rst\"", "clean": "rm -r dist", "deploy": "vsce publish --baseImagesUrl https://github.com/swyddfa/esbonio/raw/release/code/", "package": "vsce package --baseImagesUrl https://github.com/swyddfa/esbonio/raw/release/code/", @@ -22,11 +23,13 @@ }, "main": "dist/extension", "dependencies": { + "jsdom": "^16.5.3", "semver": "^7.3.5", "vscode-languageclient": "^7.0.0" }, "devDependencies": { "@types/glob": "^7.1.3", + "@types/jsdom": "^16.2.10", "@types/mocha": "^8.2.2", "@types/node": "^14.14.44", "@types/semver": "^7.3.5", @@ -43,6 +46,7 @@ "engines": { "vscode": "^1.52.0" }, + "icon": "icon.png", "activationEvents": [ "onLanguage:rst", "workspaceContains:**/conf.py" @@ -59,6 +63,18 @@ "title": "Insert Inline Link", "category": "Esbonio" }, + { + "command": "esbonio.preview.open", + "title": "Open Preview", + "icon": "$(preview)", + "category": "Esbonio" + }, + { + "command": "esbonio.preview.openSide", + "title": "Open Preview to the Side", + "icon": "$(open-preview)", + "category": "Esbonio" + }, { "command": "esbonio.server.install", "title": "Install Language Server", @@ -164,13 +180,13 @@ "esbonio.sphinx.confDir": { "scope": "window", "type": "string", - "default": "", + "default": null, "description": "The Language Server should be able to automatically find the folder containing your project's 'conf.py' file. However this setting can be used to force the Language Server to use a particular directory if required." }, "esbonio.sphinx.srcDir": { "scope": "window", "type": "string", - "default": "", + "default": null, "markdownDescription": "The directory containing your rst source files. By default the Language Server will assume this is the same as `#esbonio.sphinx.srcDir#` but this opton can override this if necessary." } } @@ -195,6 +211,16 @@ "command": "esbonio.insert.link", "key": "alt+shift+l", "when": "editorTextFocus && editorLangId == rst" + }, + { + "command": "esbonio.preview.open", + "key": "ctrl+shift+v", + "when": "editorTextFocus && editorLangId == rst" + }, + { + "command": "esbonio.preview.openSide", + "key": "ctrl+k v", + "when": "editorTextFocus && editorLangId == rst" } ], "languages": [ @@ -208,6 +234,16 @@ ], "configuration": "./rst-language-configuration.json" } - ] + ], + "menus": { + "editor/title": [ + { + "command": "esbonio.preview.openSide", + "alt": "esbonio.preview.open", + "group": "navigation", + "when": "resourceLangId == rst" + } + ] + } } -} \ No newline at end of file +} diff --git a/code/pyproject.toml b/code/pyproject.toml index e96ec5df9..9b759ab0c 100644 --- a/code/pyproject.toml +++ b/code/pyproject.toml @@ -33,4 +33,4 @@ underlines = ["-", "^", "\""] [[tool.towncrier.type]] directory = "misc" name = "Misc" - showcontent = true \ No newline at end of file + showcontent = true diff --git a/code/rst-language-configuration.json b/code/rst-language-configuration.json index d1c138a2f..b70d7b531 100644 --- a/code/rst-language-configuration.json +++ b/code/rst-language-configuration.json @@ -5,13 +5,21 @@ "autoClosingPairs": [ { "open": "`", - "close": "`" - } + "close": "`", + }, + { + "open": "*", + "close": "*" + }, ], "surroundingPairs": [ [ "`", "`" + ], + [ + "*", + "*" ] ] -} \ No newline at end of file +} diff --git a/code/src/constants.ts b/code/src/constants.ts index 141981591..ed9467a7c 100644 --- a/code/src/constants.ts +++ b/code/src/constants.ts @@ -7,6 +7,10 @@ export namespace Server { } export namespace Commands { + + export const OPEN_PREVIEW = "esbonio.preview.open" + export const OPEN_PREVIEW_TO_SIDE = "esbonio.preview.openSide" + export const INSTALL_SERVER = "esbonio.server.install" export const RESTART_SERVER = "esbonio.server.restart" export const UPDATE_SERVER = "esbonio.server.update" diff --git a/code/src/editor.ts b/code/src/editor.ts index d4e3415f3..ce15f0553 100644 --- a/code/src/editor.ts +++ b/code/src/editor.ts @@ -134,4 +134,4 @@ export class EditorCommands { return { label: label, url: url } } -} \ No newline at end of file +} diff --git a/code/src/extension.ts b/code/src/extension.ts index 7461ddb5b..ba819867f 100644 --- a/code/src/extension.ts +++ b/code/src/extension.ts @@ -2,13 +2,14 @@ import * as vscode from "vscode"; import { EditorCommands, VSCodeInput } from "./editor"; import { getOutputLogger } from "./log"; -import { ClientManager } from "./lsp/client"; +import { EsbonioClient } from "./lsp/client"; import { PythonManager } from "./lsp/python"; import { ServerManager } from "./lsp/server"; +import { PreviewManager } from "./preview/view"; export const RESTART_LANGUAGE_SERVER = 'esbonio.languageServer.restart' -let client: ClientManager +let esbonio: EsbonioClient export async function activate(context: vscode.ExtensionContext) { @@ -20,13 +21,16 @@ export async function activate(context: vscode.ExtensionContext) { let python = new PythonManager(logger) let server = new ServerManager(logger, python, context) - client = new ClientManager(logger, python, server, context) - await client.start() + esbonio = new EsbonioClient(logger, python, server, context) + + let preview = new PreviewManager(logger, context, esbonio) + + await esbonio.start() } export function deactivate(): Thenable | undefined { - if (!client) { + if (!esbonio) { return undefined } - return client.stop() + return esbonio.stop() } diff --git a/code/src/log.ts b/code/src/log.ts index cf21ef2bd..2f1163d5e 100644 --- a/code/src/log.ts +++ b/code/src/log.ts @@ -73,4 +73,4 @@ export function getOutputLogger() { } return channelLogger -} \ No newline at end of file +} diff --git a/code/src/lsp/client.ts b/code/src/lsp/client.ts index e45bb88ef..10558af6c 100644 --- a/code/src/lsp/client.ts +++ b/code/src/lsp/client.ts @@ -12,15 +12,94 @@ import { ServerManager } from "./server"; const DEBUG = process.env.VSCODE_LSP_DEBUG === "true" +/** + * Represents the current sphinx configuration / configuration options + * that should be passed to sphinx on creation. + */ +export interface SphinxConfig { + + /** + * Sphinx's version number. + */ + version?: string + + /** + * The directory containing the project's 'conf.py' file. + */ + confDir?: string + + /** + * The source dir containing the *.rst files for the project. + */ + srcDir?: string + + /** + * The directory where Sphinx's build output should be stored. + */ + buildDir?: string + + /** + * The name of the builder to use. + */ + builderName?: string + +} + +/** + * Represents configuration options that should be passed to the server. + */ +export interface ServerConfig { + + /** + * Used to set the logging level of the server. + */ + logLevel: string + + /** + * A list of logger names to suppress output from. + */ + logFilter?: string[] + + /** + * A flag to indicate if Sphinx build output should be omitted from the log. + */ + hideSphinxOutput: boolean +} + +/** + * The initialization options we pass to the server on startup. + */ +export interface InitOptions { + + /** + * Language server specific options + */ + server: ServerConfig + + /** + * Sphinx specific options + */ + sphinx: SphinxConfig +} + /** * While the ServerManager is responsible for installation and updates of the - * Python package containing the server. The ClientManager is responsible for + * Python package containing the server. The EsbonioClient is responsible for * creating the LanguageClient instance that utilmately starts the server * running. */ -export class ClientManager { +export class EsbonioClient { + + /** + * If present, this represents the current configuration of the Sphinx instance + * managed by the Language server. + */ + public sphinxConfig?: SphinxConfig private client: LanguageClient + private statusBar: vscode.StatusBarItem + + private buildCompleteCallback constructor( private logger: Logger, @@ -34,6 +113,9 @@ export class ClientManager { context.subscriptions.push( vscode.workspace.onDidChangeConfiguration(this.configChanged, this) ) + + this.statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left) + context.subscriptions.push(this.statusBar) } async stop() { @@ -47,7 +129,9 @@ export class ClientManager { /** * Start the language client. */ - async start() { + async start(): Promise { + this.statusBar.text = "$(sync~spin) Starting." + this.statusBar.show() if (DEBUG) { this.client = await this.getTcpClient() } else { @@ -62,15 +146,35 @@ export class ClientManager { getOutputLogger().show() } }) - + this.statusBar.text = "$(error) Failed." return } - this.logger.info("Starting Language Server") - this.client.start() - if (DEBUG) { - // Auto open the output window when debugging - this.client.outputChannel.show() + try { + this.logger.info("Starting Language Server") + this.client.start() + + if (DEBUG) { + // Auto open the output window when debugging + this.client.outputChannel.show() + } + + await this.client.onReady() + this.client.onNotification("esbonio/sphinxConfiguration", params => { + this.sphinxConfig = params + this.statusBar.text = `$(check) Sphinx v${this.sphinxConfig.version}` + }) + + this.client.onNotification("esbonio/buildComplete", params => { + this.logger.debug("Build complete") + if (this.buildCompleteCallback) { + this.buildCompleteCallback() + } + }) + + return + } catch (err) { + this.statusBar.text = "$(error) Failed." } } @@ -98,28 +202,11 @@ export class ClientManager { return undefined } - let config = vscode.workspace.getConfiguration('esbonio') let command = await this.python.getCmd() - - let cache = this.context.storageUri.path - command.push( "-m", "esbonio", - "--cache-dir", join(cache, 'sphinx'), - "--log-level", config.get('server.logLevel') ) - if (config.get('server.hideSphinxOutput')) { - command.push("--hide-sphinx-output") - } - - let logFilters = config.get('server.logFilter') - if (logFilters) { - logFilters.forEach(filterName => { - command.push("--log-filter", filterName) - }) - } - this.logger.debug(`Server start command: ${command.join(" ")}`) return new LanguageClient( @@ -159,12 +246,32 @@ export class ClientManager { * transport. */ private getLanguageClientOptions(): LanguageClientOptions { - return { + + let cache = this.context.storageUri.path + let config = vscode.workspace.getConfiguration("esbonio") + + let initOptions: InitOptions = { + sphinx: { + srcDir: config.get("sphinx.srcDir"), + confDir: config.get('sphinx.confDir'), + buildDir: join(cache, 'sphinx') + }, + server: { + logLevel: config.get('server.logLevel'), + logFilter: config.get('server.logFilter'), + hideSphinxOutput: config.get('server.hideSphinxOutput') + } + } + + let clientOptions: LanguageClientOptions = { documentSelector: [ { scheme: 'file', language: 'rst' }, { scheme: 'file', language: 'python' } - ] + ], + initializationOptions: initOptions } + this.logger.debug(`LanguageClientOptions: ${JSON.stringify(clientOptions)}`) + return clientOptions } /** @@ -185,4 +292,8 @@ export class ClientManager { await this.restartServer() } } -} \ No newline at end of file + + onBuildComplete(callback) { + this.buildCompleteCallback = callback + } +} diff --git a/code/src/lsp/python.ts b/code/src/lsp/python.ts index b759735a4..521ca3359 100644 --- a/code/src/lsp/python.ts +++ b/code/src/lsp/python.ts @@ -174,4 +174,4 @@ export class PythonManager { this.checkedExtension = true } -} \ No newline at end of file +} diff --git a/code/src/lsp/server.ts b/code/src/lsp/server.ts index 722d9b8db..a306f4d86 100644 --- a/code/src/lsp/server.ts +++ b/code/src/lsp/server.ts @@ -303,4 +303,4 @@ export function shouldUpdate(frequency: string, today: Date, lastUpdate: Date): ] return conds.some(i => i) -} \ No newline at end of file +} diff --git a/code/src/preview/view.ts b/code/src/preview/view.ts new file mode 100644 index 000000000..67c776332 --- /dev/null +++ b/code/src/preview/view.ts @@ -0,0 +1,181 @@ +import * as jsom from "jsdom"; +import * as path from 'path'; +import * as vscode from 'vscode' + +import { Commands } from "../constants"; +import { Logger } from "../log"; +import { EsbonioClient } from "../lsp/client"; + + +/** + * Class responsible for generating a preview view of the documentation. + */ +export class PreviewManager { + + private htmlPath: string + private panel: vscode.WebviewPanel + + constructor( + private logger: Logger, + context: vscode.ExtensionContext, + private esbonio: EsbonioClient + ) { + context.subscriptions.push( + vscode.commands.registerTextEditorCommand(Commands.OPEN_PREVIEW, this.openPreview, this) + ) + context.subscriptions.push( + vscode.commands.registerTextEditorCommand(Commands.OPEN_PREVIEW_TO_SIDE, this.openPreviewToSide, this) + ) + + vscode.window.onDidChangeActiveTextEditor(this.onDidChangeEditor, this) + + esbonio.onBuildComplete(async () => { + await this.reloadView() + }) + } + + async openPreview(editor: vscode.TextEditor) { + return await this.previewEditor(editor, vscode.ViewColumn.Active) + } + + async openPreviewToSide(editor: vscode.TextEditor) { + return await this.previewEditor(editor, vscode.ViewColumn.Beside) + } + + /** + * Called whenever the user changes their active text editor. + * Used to switch the preview to match the current source file + * the user is editing. + */ + private async onDidChangeEditor(editor: vscode.TextEditor) { + if (!editor) { + return + } + + let htmlPath = await this.getHtmlPath(editor) + if (!htmlPath) { + return + } + + await this.reloadView(htmlPath) + } + + private async reloadView(htmlPath?: string) { + if (!this.panel) { + return + } + + if (!htmlPath) { + htmlPath = this.htmlPath + } + + this.logger.debug(`Previewing ${htmlPath}`) + this.panel.webview.html = await this.getHtmlContent(htmlPath) + this.htmlPath = htmlPath + } + + private async previewEditor(editor: vscode.TextEditor, placement: vscode.ViewColumn) { + + let htmlPath = await this.getHtmlPath(editor) + if (!htmlPath) { + return + } + + // Currently we only support one open editor at a time. + if (!this.panel) { + let buildDir = this.esbonio.sphinxConfig.buildDir + this.panel = vscode.window.createWebviewPanel( + 'esbonioPreview', 'Preview', + placement, + { + enableScripts: true, + localResourceRoots: [vscode.Uri.file(buildDir)] + } + ) + } + + this.panel.onDidDispose(() => { + this.panel = undefined + }) + + await this.reloadView(htmlPath) + } + + /** + * Translate the source *.rst (or other) filepath into the *.html path in the + * build directory that we want to display. + * + * @param sourcePath the path to the source file we wish to preview + * @param sphinx the SphinxConfig object that tells us where the directories are. + * + * @returns the translated path or undefined if the file is not part of + * the srcDir. + */ + private async getHtmlPath(editor: vscode.TextEditor): Promise { + + let sourcePath = editor.document.fileName + let srcDir = this.esbonio.sphinxConfig.srcDir + let buildDir = this.esbonio.sphinxConfig.buildDir + + if (!sourcePath.startsWith(srcDir)) { + this.logger.debug(`Ingoring ${sourcePath}`) + return undefined + } + + let rstFile = sourcePath.replace(srcDir, '') + let htmlFile = rstFile.replace(new RegExp(`\\${path.extname(rstFile)}`), '.html') + + return path.join(buildDir, htmlFile) + } + + /** + * Given a path to some HTML content load it and prepare it for display + * within a webview. + * + * This includes translating all css, js, image, etc. urls to be webviewuris + * so that they get loaded correctly. + */ + private async getHtmlContent(htmlPath: string): Promise { + // Since all sphinx generated URLs are relative to the current file (e.g. ../../xxxx) + // we need to join the urls with the html file's parent dir NOT the sphinx build dir. + let baseDir = path.dirname(htmlPath) + + let dom = await jsom.JSDOM.fromFile(htmlPath) + let head = dom.window.document.head + let body = dom.window.document.body + + // Rewrite the stylesheet paths so that they pass the webview security policies + let styles = head.querySelectorAll('[rel="stylesheet"]') + styles.forEach(stylesheet => this.rewriteHrefUrl(stylesheet, baseDir)) + + // Rewrite script urls + let headScripts = head.querySelectorAll('script') + let bodyScripts = body.querySelectorAll('script') + headScripts.forEach(script => this.rewriteSrcUrl(script, baseDir)) + bodyScripts.forEach(script => this.rewriteSrcUrl(script, baseDir)) + + // Rewrite image urls + let images = body.querySelectorAll('img') + images.forEach(image => this.rewriteSrcUrl(image, baseDir)) + + return dom.serialize() + } + + private rewriteSrcUrl(element, baseDir: string) { + let src = element.getAttribute('src') + + let uri = vscode.Uri.file(path.join(baseDir, src)) + let newSrc = this.panel.webview.asWebviewUri(uri) + + element.setAttribute('src', newSrc) + } + + private rewriteHrefUrl(element, baseDir: string) { + let src = element.getAttribute('href') + + let uri = vscode.Uri.file(path.join(baseDir, src)) + let newSrc = this.panel.webview.asWebviewUri(uri) + + element.setAttribute('href', newSrc) + } +} diff --git a/code/src/test/runTests.ts b/code/src/test/runTests.ts index 0f924d8fc..a2e0b6d76 100644 --- a/code/src/test/runTests.ts +++ b/code/src/test/runTests.ts @@ -14,4 +14,4 @@ async function main() { } } -main() \ No newline at end of file +main() diff --git a/code/src/test/suite/index.ts b/code/src/test/suite/index.ts index 0e818e6e3..05aa7e78c 100644 --- a/code/src/test/suite/index.ts +++ b/code/src/test/suite/index.ts @@ -33,4 +33,4 @@ export function run(): Promise { } }) }) -} \ No newline at end of file +} diff --git a/code/src/test/suite/lsp/server.test.ts b/code/src/test/suite/lsp/server.test.ts index 93b489d43..6232cb0e2 100644 --- a/code/src/test/suite/lsp/server.test.ts +++ b/code/src/test/suite/lsp/server.test.ts @@ -52,4 +52,4 @@ suite("ServerManager", () => { }) }) -}) \ No newline at end of file +}) diff --git a/code/syntaxes/rst.tmLanguage.json b/code/syntaxes/rst.tmLanguage.json index 4d48291d3..eda9e81b0 100644 --- a/code/syntaxes/rst.tmLanguage.json +++ b/code/syntaxes/rst.tmLanguage.json @@ -81,18 +81,30 @@ "directive": { "name": "meta.directive.rst", "patterns": [ + { + "include": "#c-code-block" + }, + { + "include": "#cpp-code-block" + }, { "include": "#css-code-block" }, { "include": "#html-code-block" }, + { + "include": "#js-code-block" + }, { "include": "#json-code-block" }, { "include": "#python-code-block" }, + { + "include": "#ts-code-block" + }, { "include": "#yaml-code-block" }, @@ -119,6 +131,36 @@ } ] }, + "c-code-block": { + "begin": "(\\s*)\\.\\.\\s+(code-block)::\\s+c$", + "beginCaptures": { + "2": { + "name": "entity.name.function" + } + }, + "end": "^(?!\\1\\s+)(?!\\s*$)", + "name": "meta.directive.code-block.c.rst", + "patterns": [ + { + "include": "#c-code" + } + ] + }, + "cpp-code-block": { + "begin": "(\\s*)\\.\\.\\s+(code-block)::\\s+cpp$", + "beginCaptures": { + "2": { + "name": "entity.name.function" + } + }, + "end": "^(?!\\1\\s+)(?!\\s*$)", + "name": "meta.directive.code-block.cpp.rst", + "patterns": [ + { + "include": "#cpp-code" + } + ] + }, "css-code-block": { "begin": "(\\s*)\\.\\.\\s+(code-block)::\\s+css", "beginCaptures": { @@ -149,6 +191,21 @@ } ] }, + "js-code-block": { + "begin": "(\\s*)\\.\\.\\s+(code-block)::\\s+(js|javascript)$", + "beginCaptures": { + "2": { + "name": "entity.name.function" + } + }, + "end": "^(?!\\1\\s+)(?!\\s*$)", + "name": "meta.directive.code-block.js.rst", + "patterns": [ + { + "include": "#js-code" + } + ] + }, "json-code-block": { "begin": "(\\s*)\\.\\.\\s+(code-block)::\\s+json", "beginCaptures": { @@ -179,6 +236,21 @@ } ] }, + "ts-code-block": { + "begin": "(\\s*)\\.\\.\\s+(code-block)::\\s+(ts|typescript)$", + "beginCaptures": { + "2": { + "name": "entity.name.function" + } + }, + "end": "^(?!\\1\\s+)(?!\\s*$)", + "name": "meta.directive.code-block.ts.rst", + "patterns": [ + { + "include": "#ts-code" + } + ] + }, "yaml-code-block": { "begin": "(\\s*)\\.\\.\\s+(code-block)::\\s+yaml", "beginCaptures": { @@ -359,7 +431,7 @@ } }, "link-inline": { - "match": "(`.+?(<.*>)`)(_)", + "match": "(`.+?(<.*?>)`)(_)", "name": "meta.link.rst", "captures": { "1": { @@ -374,7 +446,7 @@ } }, "link-reference": { - "match": "(`.+`)(_)", + "match": "(`.+?`)(_)", "name": "meta.reference.link.rst", "captures": { "1": { @@ -385,6 +457,26 @@ } } }, + "c-code": { + "begin": "(\\s*)", + "end": "^(?!\\1\\s+)(?!\\s*$)", + "name": "source.c", + "patterns": [ + { + "include": "source.c" + } + ] + }, + "cpp-code": { + "begin": "(\\s*)", + "end": "^(?!\\1\\s+)(?!\\s*$)", + "name": "source.cpp", + "patterns": [ + { + "include": "source.cpp" + } + ] + }, "css-code": { "begin": "(\\s*)", "end": "^(?!\\1\\s+)(?!\\s*$)", @@ -405,6 +497,16 @@ } ] }, + "js-code": { + "begin": "(\\s*)", + "end": "^(?!\\1\\s+)(?!\\s*$)", + "name": "source.js", + "patterns": [ + { + "include": "source.js" + } + ] + }, "json-code": { "begin": "(\\s*)", "end": "^(?!\\1\\s+)(?!\\s*$)", @@ -425,6 +527,16 @@ } ] }, + "ts-code": { + "begin": "(\\s*)", + "end": "^(?!\\1\\s+)(?!\\s*$)", + "name": "source.ts", + "patterns": [ + { + "include": "source.ts" + } + ] + }, "yaml-code": { "begin": "(\\s*)", "end": "^(?!\\1\\s+)(?!\\s*$)", @@ -436,4 +548,4 @@ ] } } -} \ No newline at end of file +} diff --git a/code/syntaxes/tests/_code.rst b/code/syntaxes/tests/_code.rst index a601d1013..95f339b56 100644 --- a/code/syntaxes/tests/_code.rst +++ b/code/syntaxes/tests/_code.rst @@ -2,6 +2,16 @@ -- In this file we use '-' as the comment character as vscode-tmgrammar-test -- gets confused between comments and directives. +.. code-block:: c + + int main(void) {} +-- ^^^^^^^^^^^^^^^^^ source.c + +.. code-block:: cpp + + std::cout << "Hello, world" << std::endl; +-- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ source.cpp + .. code-block:: css p {width: 50%} @@ -12,6 +22,16 @@

html

-- ^^^^^^^^^^^^^^^^^^^^^^^^^^^ text.html.derivative +.. code-block:: javascript + + import { join } from 'path'; +-- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ source.js + +.. code-block:: js + + console.log("Hi there!") +-- ^^^^^^^^^^^^^^^^^^^^^^^^ source.js + .. code-block:: json {"example": "json"} @@ -22,6 +42,16 @@ print("Hi there!") -- ^^^^^^^^^^^^^^^^^^ source.python +.. code-block:: ts + + function test(a: number, b: string) {} +-- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ source.ts + +.. code-block:: typescript + + function test(a: number, b: string) {} +-- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ source.ts + .. code-block:: yaml example: yaml diff --git a/code/syntaxes/tests/_comments.rst b/code/syntaxes/tests/_comments.rst index 473eb5446..17b8ca8b0 100644 --- a/code/syntaxes/tests/_comments.rst +++ b/code/syntaxes/tests/_comments.rst @@ -9,4 +9,4 @@ -- ^^^^^^^^^^^^^^^^^^^^^^^^ comment.line However... lines that contain ellipses should NOT be a comment. --- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -comment.line \ No newline at end of file +-- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -comment.line diff --git a/code/syntaxes/tests/_directives.rst b/code/syntaxes/tests/_directives.rst index 7e5c55dd0..6232addbf 100644 --- a/code/syntaxes/tests/_directives.rst +++ b/code/syntaxes/tests/_directives.rst @@ -11,4 +11,4 @@ .. cpp:function:: x.y.z -- ^^^ storage.type.namespace --- ^^^^^^^^ entity.name.function \ No newline at end of file +-- ^^^^^^^^ entity.name.function diff --git a/code/syntaxes/tests/_links.rst b/code/syntaxes/tests/_links.rst index a6cc93637..2df21accc 100644 --- a/code/syntaxes/tests/_links.rst +++ b/code/syntaxes/tests/_links.rst @@ -6,13 +6,31 @@ This line has an `inline `_ link -- ^^^^^^^ ^ string -- ^^^^^^^^^^^^^^^^^^^^ constant.other.url +There are `one `_ or `two `_ ways to approach this +-- ^^^^^^^^^^^^^^^ meta.link.rst +-- ^ keyword.operator +-- ^^^^^ ^ string +-- ^^^^^^^^ constant.other.url +-- ^^^^ -meta.link.url +-- ^^^^^^^^^^^^^^^ meta.link.rst +-- ^ keyword.operator +-- ^^^^^ ^ string +-- ^^^^^^^^ constant.other.url + This is a `named`_ link -- ^^^^^^^^ meta.reference.link.rst -- ^^^^^^^ variable.other.label +Here is a `named`_ link, followed by `another`_ +-- ^^^^^^^^ meta.reference.link.rst +-- ^^^^^^^ variable.other.label +-- ^^^^^^^^^^ meta.reference.link.rst +-- ^^^^^^^^^ variable.other.label +-- ^^^^^^^^^^^^^^^^^^^ -meta.reference.link.rst +-- ^^^^^^^^^^^^^^^^^^^ -variable.other.label .. _named: https://example.com -- ^^^^^^^^^^^^^^^^^^^^^^^^^^^ meta.definition.link.rst -- <- keyword.operator -- ^^^^^ variable.other.label -- ^ ^ keyword.operator --- ^^^^^^^^^^^^^^^^^^^ constant.other.url \ No newline at end of file +-- ^^^^^^^^^^^^^^^^^^^ constant.other.url diff --git a/code/syntaxes/tests/_sections.rst b/code/syntaxes/tests/_sections.rst index e53367987..45195852d 100644 --- a/code/syntaxes/tests/_sections.rst +++ b/code/syntaxes/tests/_sections.rst @@ -30,4 +30,4 @@ Section Section +++++++ -// <------- keyword.control \ No newline at end of file +// <------- keyword.control diff --git a/code/syntaxes/tests/c.tmLanguage.json b/code/syntaxes/tests/c.tmLanguage.json new file mode 100644 index 000000000..ed4ace935 --- /dev/null +++ b/code/syntaxes/tests/c.tmLanguage.json @@ -0,0 +1,14 @@ +{ + "scopeName": "source.c", + "patterns": [ + { + "include": "#code" + } + ], + "repository": { + "code": { + "match": ".*", + "name": "invalid" + } + } +} diff --git a/code/syntaxes/tests/cpp.tmLanguage.json b/code/syntaxes/tests/cpp.tmLanguage.json new file mode 100644 index 000000000..e596e9dfc --- /dev/null +++ b/code/syntaxes/tests/cpp.tmLanguage.json @@ -0,0 +1,14 @@ +{ + "scopeName": "source.cpp", + "patterns": [ + { + "include": "#code" + } + ], + "repository": { + "code": { + "match": ".*", + "name": "invalid" + } + } +} diff --git a/code/syntaxes/tests/css.tmLanguage.json b/code/syntaxes/tests/css.tmLanguage.json index cc4dd8368..4e75fc389 100644 --- a/code/syntaxes/tests/css.tmLanguage.json +++ b/code/syntaxes/tests/css.tmLanguage.json @@ -11,4 +11,4 @@ "name": "invalid" } } -} \ No newline at end of file +} diff --git a/code/syntaxes/tests/html.tmLanguage.json b/code/syntaxes/tests/html.tmLanguage.json index 37086b452..996da91f4 100644 --- a/code/syntaxes/tests/html.tmLanguage.json +++ b/code/syntaxes/tests/html.tmLanguage.json @@ -11,4 +11,4 @@ "name": "invalid" } } -} \ No newline at end of file +} diff --git a/code/syntaxes/tests/js.tmLanguage.json b/code/syntaxes/tests/js.tmLanguage.json new file mode 100644 index 000000000..1a4c8002a --- /dev/null +++ b/code/syntaxes/tests/js.tmLanguage.json @@ -0,0 +1,14 @@ +{ + "scopeName": "source.js", + "patterns": [ + { + "include": "#code" + } + ], + "repository": { + "code": { + "match": ".*", + "name": "invalid" + } + } +} diff --git a/code/syntaxes/tests/json.tmLanguage.json b/code/syntaxes/tests/json.tmLanguage.json index 024079e7d..e253cf72b 100644 --- a/code/syntaxes/tests/json.tmLanguage.json +++ b/code/syntaxes/tests/json.tmLanguage.json @@ -11,4 +11,4 @@ "name": "invalid" } } -} \ No newline at end of file +} diff --git a/code/syntaxes/tests/python.tmLanguage.json b/code/syntaxes/tests/python.tmLanguage.json index 4b8d230ff..f63997200 100644 --- a/code/syntaxes/tests/python.tmLanguage.json +++ b/code/syntaxes/tests/python.tmLanguage.json @@ -11,4 +11,4 @@ "name": "invalid" } } -} \ No newline at end of file +} diff --git a/code/syntaxes/tests/ts.tmLanguage.json b/code/syntaxes/tests/ts.tmLanguage.json new file mode 100644 index 000000000..9f7c133f9 --- /dev/null +++ b/code/syntaxes/tests/ts.tmLanguage.json @@ -0,0 +1,14 @@ +{ + "scopeName": "source.ts", + "patterns": [ + { + "include": "#code" + } + ], + "repository": { + "code": { + "match": ".*", + "name": "invalid" + } + } +} diff --git a/code/syntaxes/tests/yaml.tmLanguage.json b/code/syntaxes/tests/yaml.tmLanguage.json index 228c6c25d..3a5468d97 100644 --- a/code/syntaxes/tests/yaml.tmLanguage.json +++ b/code/syntaxes/tests/yaml.tmLanguage.json @@ -11,4 +11,4 @@ "name": "invalid" } } -} \ No newline at end of file +} diff --git a/code/tsconfig.json b/code/tsconfig.json index b6b7ce083..bce4d0a73 100644 --- a/code/tsconfig.json +++ b/code/tsconfig.json @@ -4,7 +4,8 @@ "moduleResolution": "node", "target": "es2019", "lib": [ - "ES2019" + "ES2019", + "dom" ], "outDir": "dist", "rootDir": "src", @@ -17,4 +18,4 @@ "node_modules", ".vscode-test" ] -} \ No newline at end of file +} diff --git a/code/webpack.config.js b/code/webpack.config.js index 23abbbe1a..580202e97 100644 --- a/code/webpack.config.js +++ b/code/webpack.config.js @@ -17,6 +17,9 @@ const config = { }, devtool: 'source-map', externals: { + bufferutil: 'util', // See [2] + canvas: "util", // See [1] + 'utf-8-validate': 'util', // See [2] vscode: "commonjs vscode" }, resolve: { @@ -42,4 +45,18 @@ const config = { } } -module.exports = config \ No newline at end of file +module.exports = config + +/* + [1] https://github.com/jsdom/jsdom/issues/2508#issuecomment-777387562 + + We don't want or need canvas support, as we only use jsdom to rewrite some + urls so that external assets load in the webview preview. + + This gets webpack to rewrite the "import canvas" statements to "import util" + and apparently the library is able to degrade gracefully! + */ + +/* + [2]: Similar to above, we don't need websocket support. + */ diff --git a/docs/_static/css/custom.css b/docs/_static/css/custom.css new file mode 100644 index 000000000..89a31928b --- /dev/null +++ b/docs/_static/css/custom.css @@ -0,0 +1,24 @@ +@media (min-width: 1200px) { + body > .container-xl { + margin-left: 0; + } + + #site-navigation { + position: sticky; + } + + .topbar { + max-width: unset; + } + + #navbar-toggler { + display: none + } +} + +@media (min-width: 1400px) { + #main-content > div { + max-width: unset; + flex: 1; + } +} diff --git a/docs/_static/images/emacs-eglot-minimal.png b/docs/_static/images/emacs-eglot-minimal.png deleted file mode 100644 index 67f39ecb7..000000000 Binary files a/docs/_static/images/emacs-eglot-minimal.png and /dev/null differ diff --git a/docs/_static/images/emacs-lsp-mode-minimal.png b/docs/_static/images/emacs-lsp-mode-minimal.png deleted file mode 100644 index cac3fafc4..000000000 Binary files a/docs/_static/images/emacs-lsp-mode-minimal.png and /dev/null differ diff --git a/docs/_static/images/vscode-screenshot.png b/docs/_static/images/vscode-screenshot.png deleted file mode 100644 index 427bc0cbb..000000000 Binary files a/docs/_static/images/vscode-screenshot.png and /dev/null differ diff --git a/docs/_static/sample-configs/emacs/.gitignore b/docs/_static/sample-configs/emacs/.gitignore deleted file mode 100644 index c874fab9e..000000000 --- a/docs/_static/sample-configs/emacs/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -elpa -*~ -custom.el \ No newline at end of file diff --git a/docs/changelog.rst b/docs/changelog.rst index 923453725..a4089a6df 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,4 +1,4 @@ Changelog ========= -.. include:: ../lib/esbonio/CHANGES.rst \ No newline at end of file +.. include:: ../lib/esbonio/CHANGES.rst diff --git a/docs/conf.py b/docs/conf.py index 8a0db9064..2b752f943 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -3,9 +3,7 @@ # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html - # -- Path setup -------------------------------------------------------------- - # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. @@ -13,13 +11,15 @@ # import os # import sys # sys.path.insert(0, os.path.abspath('.')) +from sphinx.application import Sphinx + import esbonio.lsp # -- Project information ----------------------------------------------------- project = "Esbonio" copyright = "2021, Alex Carney" -author = "Alex Carney" +author = "" # The full version, including alpha/beta/rc tags release = esbonio.lsp.__version__ @@ -66,7 +66,9 @@ # a list of builtin themes. # html_theme = "sphinx_book_theme" - +html_logo = "../resources/io.github.swyddfa.Esbonio.svg" +html_favicon = "favicon.svg" +html_static_path = ["_static"] html_theme_options = { "repository_url": "https://github.com/swyddfa/esbonio", "use_repository_button": True, @@ -75,7 +77,7 @@ "repository_branch": "release", "path_to_docs": "docs/", } -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -# html_static_path = ["_static"] + + +def setup(app: Sphinx): + app.add_css_file("css/custom.css", priority=1000) diff --git a/docs/contributing/devenvs.rst b/docs/contributing/devenvs.rst index d21e2902e..c31754dbb 100644 --- a/docs/contributing/devenvs.rst +++ b/docs/contributing/devenvs.rst @@ -45,4 +45,4 @@ the main instance. .. _VSCode: https://code.visualstudio.com/ -.. _npm: https://www.npmjs.com/get-npm \ No newline at end of file +.. _npm: https://www.npmjs.com/get-npm diff --git a/docs/contributing/lsp.rst b/docs/contributing/lsp.rst index 8305c108c..39508347f 100644 --- a/docs/contributing/lsp.rst +++ b/docs/contributing/lsp.rst @@ -10,4 +10,4 @@ the codebase. :maxdepth: 2 :glob: - lsp/* \ No newline at end of file + lsp/* diff --git a/docs/contributing/lsp/filepaths.rst b/docs/contributing/lsp/filepaths.rst index a20e76b88..a7d6c418c 100644 --- a/docs/contributing/lsp/filepaths.rst +++ b/docs/contributing/lsp/filepaths.rst @@ -1,4 +1,4 @@ Filepaths ========= -.. automodule:: esbonio.lsp.filepaths \ No newline at end of file +.. automodule:: esbonio.lsp.filepaths diff --git a/docs/contributing/lsp/logger.rst b/docs/contributing/lsp/logger.rst index d795db212..756fc3ebf 100644 --- a/docs/contributing/lsp/logger.rst +++ b/docs/contributing/lsp/logger.rst @@ -1,4 +1,4 @@ Logger ====== -.. automodule:: esbonio.lsp.logger \ No newline at end of file +.. automodule:: esbonio.lsp.logger diff --git a/docs/favicon.svg b/docs/favicon.svg new file mode 100644 index 000000000..191af0e97 --- /dev/null +++ b/docs/favicon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/docs/_static/images/emacs-eglot-extended.png b/docs/images/emacs-eglot-extended.png similarity index 100% rename from docs/_static/images/emacs-eglot-extended.png rename to docs/images/emacs-eglot-extended.png diff --git a/docs/_static/images/emacs-lsp-mode-extended.png b/docs/images/emacs-lsp-mode-extended.png similarity index 100% rename from docs/_static/images/emacs-lsp-mode-extended.png rename to docs/images/emacs-lsp-mode-extended.png diff --git a/docs/images/kate-lsp-settings.png b/docs/images/kate-lsp-settings.png new file mode 100644 index 000000000..2f5c19e96 Binary files /dev/null and b/docs/images/kate-lsp-settings.png differ diff --git a/docs/images/kate-plugin-settings.png b/docs/images/kate-plugin-settings.png new file mode 100644 index 000000000..5118e7294 Binary files /dev/null and b/docs/images/kate-plugin-settings.png differ diff --git a/docs/images/kate-screenshot.png b/docs/images/kate-screenshot.png new file mode 100644 index 000000000..b4d81c8c0 Binary files /dev/null and b/docs/images/kate-screenshot.png differ diff --git a/docs/images/nvim-coc.png b/docs/images/nvim-coc.png new file mode 100644 index 000000000..17a9aa042 Binary files /dev/null and b/docs/images/nvim-coc.png differ diff --git a/docs/images/vscode-screenshot.png b/docs/images/vscode-screenshot.png new file mode 100644 index 000000000..2c2851366 Binary files /dev/null and b/docs/images/vscode-screenshot.png differ diff --git a/docs/lsp/editors/emacs.rst b/docs/lsp/editors/emacs.rst index 887ca80af..3644997c3 100644 --- a/docs/lsp/editors/emacs.rst +++ b/docs/lsp/editors/emacs.rst @@ -1,11 +1,6 @@ Emacs ===== -.. note:: - - While I like to play around now and again with Emacs, I'm hardly an expert! If you - know of a better way to set this up, feel free to open a pull request! - There are multiple LSP clients available in the Emacs ecosystem. - `eglot`_ a more minimal Language Client that integrates tightly with features built @@ -19,70 +14,68 @@ This page contains a number of sample configurations that you can use to get sta offering configs that work within popular frameworks like Spacemacs and Doom - would there be any noticable difference in the config code we write? -Eglot -- Minimal Config ------------------------ +Eglot +----- -.. figure:: /_static/images/emacs-eglot-minimal.png +.. figure:: /images/emacs-eglot-extended.png :align: center :width: 80% - Using Esbonio with Emacs and the ``eglot-minimal.el`` configuration. - -This barebones configuration should be just enough to get things up and running with -Emacs, Esbonio and Eglot, might be useful to help track down configuration issues. + Using Esbonio and Emacs with the ``eglot-extended.el`` configuration -The key to setting up ``eglot`` is to tell it about the language server, how to start -it and that we want to use it with ``*.rst`` files +Configuring ``eglot`` involves updating the ``eglot-server-programs`` list to tell it about +the language server and how to start it. As well as adding an ``rst-mode-hook`` that runs +``eglot`` in ``*.rst`` files. -.. literalinclude:: /_static/sample-configs/emacs/eglot-minimal.el +.. literalinclude:: emacs/eglot-minimal.el :language: elisp :start-after: ;; files. :end-before: ;; Setup some keybindings - -To try this config on your machine. +We provide a barebones configuration ``eglot-minimal.el`` that you can use either to experiment +with Eglot and Esbonio or as a basis for your own configuration. To try it out on your machine. 1. Make sure you've followed the :ref:`editor_integration_setup`. -2. Download :download:`eglot-minimal.el ` +2. Download :download:`eglot-minimal.el ` to a folder of your choosing. 3. Edit ``eglot-minimal.el`` to set the path to the Python executable to be the one in the virtual environment you just installed the language server into. 4. Run the following command to launch a separate instance of Emacs isolated from your - usual configuraiton:: + usual configuration:: emacs -Q -l eglot-minimal.el -Eglot -- Extended Config ------------------------- +Server Configuration +^^^^^^^^^^^^^^^^^^^^ -.. figure:: /_static/images/emacs-eglot-extended.png - :align: center - :width: 80% +The language server provides a number of :ref:`settings ` that +for example can be used to control the instance of Sphinx that the server manages. - Using Esbonio and Emacs with the ``eglot-extended.el`` configuration - -Here is a configuration with a few more bells and whistles that aims to showcase what -can be achieved with some additional configuration. - -.. note:: +To set these values via Eglot it's necessary to create a subclass of Eglot's +``eglot-lsp-server`` type and implement the ``eglot-initialization-options`` method to return +the settings you wish to set. - There seems to be a bug in this config where ``project.el`` is not being loaded - correctly preventing ``eglot`` from starting. However, this only appears to be an - issue on the first run so if you encounter this try restarting Emacs and it should - magically fix itself. +.. literalinclude:: emacs/eglot-extended.el + :dedent: 2 + :language: elisp + :start-at: (defclass eglot-esbonio (eglot-lsp-server) () + :end-before: (add-to-list 'eglot-server-programs -This time the configuration makes use of `use-package`_ to install (if necessary) and -configure packages with a single declaration +Then when it comes to adding the entry to ``eglot-server-programs`` the ``eglot-esbonio`` +class needs to be prepended to the list specifying the server start command. -.. literalinclude:: /_static/sample-configs/emacs/eglot-extended.el +.. literalinclude:: emacs/eglot-extended.el + :dedent: 2 :language: elisp - :start-after: ;; Most important, ensure the eglot is available and configured. - :end-before: ;; UI Tweaks + :start-at: (add-to-list 'eglot-server-programs + :end-before: (use-package rst -To try this config on your machine +We provide an extended configuration ``eglot-extended.el`` that sets a few of these settings +as well as including a few extras. Feel free to use it to experiment with Elgot and Esbonio +or use it as a starting point for your own configuration. 1. Make sure you've followed the :ref:`editor_integration_setup`. -2. Download :download:`eglot-extended.el ` +2. Download :download:`eglot-extended.el ` to a folder of your choosing. 3. Edit ``eglot-extended.el`` to set the path to the Python executable to be the one in the virtual environment you just installed the language server into. @@ -91,31 +84,37 @@ To try this config on your machine emacs -Q -l eglot-extended.el -LSP Mode -- Minimal Config --------------------------- +.. note:: + + There seems to be a bug in this config where ``project.el`` is not being loaded + correctly preventing ``eglot`` from starting. However, this only appears to be an + issue on the first run so if you encounter this try restarting Emacs and it should + magically fix itself. + +LSP Mode +-------- -.. figure:: /_static/images/emacs-lsp-mode-minimal.png +.. figure:: /images/emacs-lsp-mode-extended.png :align: center :width: 80% - Using Esbonio and Emacs with the ``lsp-mode-minimal.el`` configuration - -This should be just enough configuration to get Esbonio working with LSP Mode and Emacs, -might be useful when tracking down configuration issues. + Using Esbonio and Emacs with the ``lsp-mode-extended.el`` configuration. -Setting up LSP Mode is slightly more complicated than Eglot as there is more -infrastructure to navigate but it boils down to the same steps, tell LSP Mode how to -start the server and then tell it when it should be started. +Setting up LSP Mode is *slightly* more complicated than Eglot as there is more +infrastructure to navigate but it boils down to the same steps, telling LSP Mode how +and when to start the server. -.. literalinclude:: /_static/sample-configs/emacs/lsp-mode-minimal.el +.. literalinclude:: emacs/lsp-mode-minimal.el :language: elisp :start-after: ;; Register the Esbonio language server with lsp-mode :end-before: ;; Setup some keybindings -To try this config on your machine +We provide a barebones configuration ``lsp-mode-minimal.el`` that you can use to either +experiment with LSP Mode and Esbonios or as a basis for your own configuration. To try it out +on your machine. 1. Make sure that you've followed the :ref:`editor_integration_setup`. -2. Download :download:`lsp-mode-minimal.el ` +2. Download :download:`lsp-mode-minimal.el ` to a folder of your choosing. 3. Edit ``lsp-mode-minimal.el`` to set the path to the Python executable to be the one in the virtual environment you just installed the language server into. @@ -124,30 +123,30 @@ To try this config on your machine emacs -Q -l lsp-mode-minimal.el -LSP Mode -- Extended Config ---------------------------- +Server Configuration +^^^^^^^^^^^^^^^^^^^^ -.. figure:: /_static/images/emacs-lsp-mode-extended.png - :align: center - :width: 80% - - Using Esbonio and Emacs with the ``lsp-mode-extended.el`` configuration. +The language server provides a number of :ref:`settings ` that +for example can be used to control the instance of Sphinx that the server manages. -Here is a configuration with a few more bells and whistles that aims to showcase what -can be achieved with some additional configuration. +To set these values via LSP Mode you need to extend your call to ``make-lsp-client`` to +include an ``initialization-options`` field which is set to a function that returns a +list of lists representing the settings you wish to set. -.. literalinclude:: /_static/sample-configs/emacs/lsp-mode-extended.el +.. literalinclude:: emacs/lsp-mode-extended.el + :dedent: 3 :language: elisp - :start-after: ;; Most important, ensure that lsp-mode is available and configured. - :end-before: ;; UI Tweaks + :start-at: (make-lsp-client + :end-at: :server-id -This time the configuration makes use of `use-package`_ to install (if necessary) and -configure packages with a single declaration +We provide an extended configuration ``lsp-mode-extended.el`` that sets a few of these +settings as well as including a few extras. Feel free to use it to experiment with LSP Mode +and Esbonio or use it as a starting point for your own configuration To try this config on your machine 1. Make sure that you've followed the :ref:`editor_integration_setup`. -2. Download :download:`lsp-mode-extended.el ` +2. Download :download:`lsp-mode-extended.el ` to a folder of your choosing. 3. Edit ``lsp-mode-extended.el`` to set the path to the Python executable to be the one in the virtual environment you just installed the language server into. @@ -158,4 +157,3 @@ To try this config on your machine .. _eglot: https://github.com/joaotavora/eglot .. _lsp-mode: https://emacs-lsp.github.io/lsp-mode/ -.. _use-package: https://github.com/jwiegley/use-package diff --git a/docs/lsp/editors/emacs/.gitignore b/docs/lsp/editors/emacs/.gitignore new file mode 100644 index 000000000..60c717f34 --- /dev/null +++ b/docs/lsp/editors/emacs/.gitignore @@ -0,0 +1,3 @@ +elpa +*~ +custom.el diff --git a/docs/_static/sample-configs/emacs/eglot-extended.el b/docs/lsp/editors/emacs/eglot-extended.el similarity index 84% rename from docs/_static/sample-configs/emacs/eglot-extended.el rename to docs/lsp/editors/emacs/eglot-extended.el index 214983f59..2c2fe0611 100644 --- a/docs/_static/sample-configs/emacs/eglot-extended.el +++ b/docs/lsp/editors/emacs/eglot-extended.el @@ -47,8 +47,18 @@ (use-package eglot :ensure t :config + (defclass eglot-esbonio (eglot-lsp-server) () + :documentation "Esbonio Language Server.") + + (cl-defmethod eglot-initialization-options ((server eglot-esbonio)) + "Passes the initializationOptions required to run the server." + `(:sphinx (:confDir "${workspaceRoot}" + :srcDir "${confDir}" ) + :server (:logLevel "debug"))) + (add-to-list 'eglot-server-programs - `(rst-mode . ("/path/to/virtualenv/bin/python" + `(rst-mode . (eglot-esbonio + "/path/to/virtualenv/bin/python" "-m" "esbonio")))) (use-package rst diff --git a/docs/_static/sample-configs/emacs/eglot-minimal.el b/docs/lsp/editors/emacs/eglot-minimal.el similarity index 100% rename from docs/_static/sample-configs/emacs/eglot-minimal.el rename to docs/lsp/editors/emacs/eglot-minimal.el diff --git a/docs/_static/sample-configs/emacs/lsp-mode-extended.el b/docs/lsp/editors/emacs/lsp-mode-extended.el similarity index 91% rename from docs/_static/sample-configs/emacs/lsp-mode-extended.el rename to docs/lsp/editors/emacs/lsp-mode-extended.el index 5d486690f..7bc7289c3 100644 --- a/docs/_static/sample-configs/emacs/lsp-mode-extended.el +++ b/docs/lsp/editors/emacs/lsp-mode-extended.el @@ -53,6 +53,9 @@ (lsp-stdio-connection '("/path/to/virtualenv/bin/python" "-m" "esbonio")) :activation-fn (lsp-activate-on "rst") + :initialization-options (lambda () `((sphinx . ((confDir . "${workspaceRoot}") + (srcDir . "${confDir}"))) + (server . ((logLevel . "debug"))))) :server-id 'esbonio))) (use-package rst diff --git a/docs/_static/sample-configs/emacs/lsp-mode-minimal.el b/docs/lsp/editors/emacs/lsp-mode-minimal.el similarity index 100% rename from docs/_static/sample-configs/emacs/lsp-mode-minimal.el rename to docs/lsp/editors/emacs/lsp-mode-minimal.el diff --git a/docs/lsp/editors/index.rst b/docs/lsp/editors/index.rst index 3f123e27c..7d4ff0a1d 100644 --- a/docs/lsp/editors/index.rst +++ b/docs/lsp/editors/index.rst @@ -1,7 +1,6 @@ Editor Integrations =================== - .. toctree:: :glob: :maxdepth: 1 @@ -42,6 +41,7 @@ language server from your editor of choice. :columns: 3 - :doc:`/lsp/editors/emacs` + - :doc:`/lsp/editors/kate` - :doc:`/lsp/editors/vscode` .. _editor_integration_config: @@ -49,7 +49,7 @@ language server from your editor of choice. Common Configuration -------------------- -The following options are implemented directly by the language server and therefore +The following options are implemented directly by the language server and should be supported by any language client. ``esbonio.sphinx.confDir`` (string) @@ -67,6 +67,29 @@ supported by any language client. you can use this setting to tell the server where to look. Currently accepted values include: - - ``/path/to/src/`` - An absolute path - - ``${workspaceRoot}/docs/src`` - A path relative to the root of your workspace - - ``${confDir}/../src/`` - A path relative to your project's ``confDir`` + - ``/path/to/src/`` - An absolute path + - ``${workspaceRoot}/docs/src`` - A path relative to the root of your workspace + - ``${confDir}/../src/`` - A path relative to your project's ``confDir`` + +``esbonio.sphinx.buildDir`` (string) + By default the language server will choose an appropriate location to cache the build + output from Sphinx. This option can be used to force the language server to use a location + of your choosing. + +``esbonio.server.logLevel`` (string) + This can be used to set the level of log messages emitted by the server. This can be set + to one of the following values. + + - ``error`` (default) + - ``info`` + - ``debug`` + +``esbonio.server.logFilter`` (string[]) + The language server will typically include log output from all of its components. This + option can be used to restrict the log output to be only those named. + +``esbonio.server.hideSphinxOutput`` (boolean) + Normally any build output from Sphinx will be forwarded to the client as log messages. + If you prefer this flag can be used to exclude any Sphinx output from the log. + +.. _VSCode Extension: https://github.com/swyddfa/esbonio/blob/4ce1ba426b85aa397d51336d8c7eecccb7516b71/code/src/lsp/client.ts#L253 diff --git a/docs/lsp/editors/kate.rst b/docs/lsp/editors/kate.rst new file mode 100644 index 000000000..748a45377 --- /dev/null +++ b/docs/lsp/editors/kate.rst @@ -0,0 +1,84 @@ +Kate +==== + +.. figure:: /images/kate-screenshot.png + :align: center + + Editing this page with Kate and Esbonio + +`Kate`_ is a text editor from the KDE project and comes with LSP support. + +Setup +----- + +Be sure that you have followed the :ref:`editor_integration_setup` + +Kate's LSP client is provided via a plugin which needs to be enabled if you're using +it for the first time. + +1. Open Kate's settings through the :guilabel:`Settings -> Configure Kate...` menu, + or with the :kbd:`Ctrl+Shift+,` shortcut. + +2. Select the :guilabel:`Plugins` section on the left hand side find the + :guilabel:`LSP Client` plugin and ensure that it's checked. + + .. figure:: /images/kate-plugin-settings.png + :align: center + :width: 80% + + Kate's :guilabel:`Plugins` settings. + +3. Once checked a new :guilabel:`LSP Client` section should appear at the bottom of the + list. Open it and select the :guilabel:`User Server Settings` tab. + +4. This should open up a text box where you can enter some JSON to tell Kate how and + when to start the language server. + + .. code-block:: json + + { + "servers": { + "rst": { + "command": ["python", "-m", "esbonio"], + "initializationOptions": { + "sphinx": { + "srcDir": "", + "confDir": "" + }, + "server": {} + }, + "rootIndicationFileNames": ["conf.py"], + "highlightingModeRegex": "^reStructuredText$" + } + } + } + + For details on what can be passed as ``initializationOptions`` be sure to check out + the section on :ref:`editor_integration_config` and have a look at `Kate's LSP Client`_ + documentation for more details on general LSP configuration. + +5. Once you're happy with your configuration be sure to hit the :guilabel:`Apply` button for + it to take effect! + + .. figure:: /images/kate-lsp-settings.png + :align: center + :width: 80% + + Kate's :guilabel:`LSP Client` settings with an example Esbonio config. + +.. note:: + + **Python Environments** + + In order for the language server to function correctly it needs to be installed into and + run from the same Python environment as the one used to build your documentation. In order + for Kate to correctly determine the right Python environment to use, you can either + + - Modify the ``command`` array in your LSP Config to use the full path to the + correct Python, or + - Start Kate from the terminal with the correct Python environment activated:: + + (.env) $ kate + +.. _Kate: https://kate-editor.org/en-gb/ +.. _Kate's LSP Client: https://docs.kde.org/stable5/en/kate/kate/kate-application-plugin-lspclient.html diff --git a/docs/lsp/editors/nvim/.gitignore b/docs/lsp/editors/nvim/.gitignore new file mode 100644 index 000000000..95b0cf493 --- /dev/null +++ b/docs/lsp/editors/nvim/.gitignore @@ -0,0 +1,2 @@ +*.swp +plugins diff --git a/docs/lsp/editors/nvim/esbonio-coc.vim b/docs/lsp/editors/nvim/esbonio-coc.vim new file mode 100644 index 000000000..2d6d9de83 --- /dev/null +++ b/docs/lsp/editors/nvim/esbonio-coc.vim @@ -0,0 +1,37 @@ +" --------------- First time setup ------------------ +" There are a few steps you need to perform when setting this up for the +" first time. +" +" 1. Ensure you have vim-plug's `plug.vim` file installed in your autoload +" directory. See https://github.com/junegunn/vim-plug#installation for +" details. +" 2. Open a terminal in the directory containing this file and run the +" following command to load this config isolated from your existing +" configuration. +" +" (n)vim -u esbonio-coc.vim +" 3. Install the coc.nvim plugin. +" +" :PlugInstall +" +" 4. Install the coc-esbonio extension. +" +" :CocInstall coc-esbonio +" +" --------------- Subsequent use -------------------- +" +" 1. Open a terminal in the directory containing this file and run the +" following command to load it. +" +" (n)vim -u esbonio-coc.vim + +set expandtab +set tabstop=3 +set softtabstop=3 +set shiftwidth=3 + +call plug#begin('./plugins') + +Plug 'neoclide/coc.nvim', {'branch': 'release'} + +call plug#end() diff --git a/docs/lsp/editors/vim.rst b/docs/lsp/editors/vim.rst new file mode 100644 index 000000000..3ed2179ae --- /dev/null +++ b/docs/lsp/editors/vim.rst @@ -0,0 +1,75 @@ +Vim/Neovim +========== + +There are multiple ways to make use of a language server within the vim ecosystem. + +- `coc.nvim`_ a fully featured language client that aims to closely follow the + way VSCode works. Despite what the name implies it includes supports for both + vim8 and neovim +- `vim-lsp`_ an async LSP plugin for vim8 and neovim. +- `neovim`_ neovim v0.5+ comes with built-in support for the language server protocol. + +.. _coc.nvim: https://github.com/neoclide/coc.nvim +.. _vim-lsp: https://github.com/prabirshrestha/vim-lsp +.. _neovim: https://neovim.io/doc/user/lsp.html + + +This page contains a number of sample configurations that you can use to get started. + +Coc.nvim +--------- + +.. figure:: /images/nvim-coc.png + :align: center + :width: 80% + + Using Esbonio and Neovim with the ``esbonio-coc.vim`` config. + +Setup +^^^^^ + +.. tabbed:: vim-plug + + .. literalinclude:: nvim/esbonio-coc.vim + :language: vim + :start-at: set expandtab + + To try this configuration on your machine. + + 1. Make sure that you've folllowed the :ref:`editor_integration_setup`. + 2. Download :download:`esbonio-coc.vim ` to a folder + of your choosing. + 3. Ensure you have vim-plug's ``plug.vim`` file installed in your autoload + directory. See + `this guide `_ for + details. + 4. Open a terminal in the directory containing this file and run the + following command to load this config isolated from your existing + configuration:: + + (n)vim -u esbonio-coc.vim + + 5. Install the coc.nvim plugin:: + + :PlugInstall + + 6. Install the coc-esbonio extension:: + + :CocInstall coc-esbonio + +Configuration +^^^^^^^^^^^^^ + +The language server provides a number of :ref:`configuration ` +values these can be set in coc.nvim's ``coc-settings.json`` configuration file, for +example + +.. code-block:: json + + { + "esbonio.sphinx.confDir": "${workspaceRoot}/docs", + "esbonio.sphinx.srcDir": "${confDir}/../src" + } + +See coc.nvim's `documentation `_ +for more details. diff --git a/docs/lsp/editors/vscode.rst b/docs/lsp/editors/vscode.rst index 3133b6211..4531ae7e3 100644 --- a/docs/lsp/editors/vscode.rst +++ b/docs/lsp/editors/vscode.rst @@ -3,13 +3,11 @@ VSCode ====== -.. figure:: /_static/images/vscode-screenshot.png +.. figure:: /images/vscode-screenshot.png :align: center - :width: 80% The VSCode extension editing this page - Integration with the `VSCode`_ editor is provided via the `Esbonio`_ extension. @@ -96,6 +94,16 @@ Insert Link - ``esbonio.insert.link`` - :kbd:`Alt+Shift+l` If you select some text before triggering the command, the text you selected will be used as the link's label. +Open Preview - ``esbonio.preview.open`` + This will open a webview in the current editor window showing the latest html build of the + document being edited. + +Open Preview to the Side - ``esbonio.preview.openSide`` + Much like the *Open Preview* command, this will open a webview showing the latest build + for the current document but to the side of the editor window. Additionally, as you change + between source files the preview will automatically update to show the page you are + currently working on. + Install Language Server - ``esbonio.server.install`` This can be used to manually install the language server into the current environment diff --git a/docs/lsp/features.rst b/docs/lsp/features.rst index a392b395c..b7ed2e6cd 100644 --- a/docs/lsp/features.rst +++ b/docs/lsp/features.rst @@ -9,59 +9,8 @@ Completion The Language Server can offer auto complete suggestions in a variety of contexts -.. tabbed:: Directives - - Completion suggestions are offered for the directives themselves, as well as any - options that they expose. - - .. figure:: ../../resources/images/complete-directive-demo.gif - :align: center - - Completing directives - -.. tabbed:: Roles - - In the case of roles, completions can also be offered for the targets of certain - supported role types - - .. figure:: ../../resources/images/complete-role-demo.gif - :align: center - - Completing roles - - - Target completions are currently supported for the following roles - - .. hlist:: - :columns: 3 - - * :rst:role:`sphinx:doc` - * :rst:role:`sphinx:download` - * :rst:role:`sphinx:envvar` - * :rst:role:`sphinx:ref` - * :rst:role:`sphinx:option` - * :rst:role:`sphinx:py:attr` - * :rst:role:`sphinx:py:class` - * :rst:role:`sphinx:py:data` - * :rst:role:`sphinx:py:exc` - * :rst:role:`sphinx:py:func` - * :rst:role:`sphinx:py:meth` - * :rst:role:`sphinx:py:mod` - * :rst:role:`sphinx:py:obj` - * :rst:role:`sphinx:term` - * :rst:role:`sphinx:token` - -.. tabbed:: Intersphinx - - The :doc:`intersphinx ` extension that - comes bundled with Sphinx makes it easy to link to other Sphinx projects. If - configured for your project, the language server will offer autocomplete - suggestions when appropriate. - - .. figure:: ../../resources/images/complete-intersphinx-demo.gif - :align: center - - Completing references to the Python documentation. +.. figure:: ../../resources/images/completion-demo.gif + :align: center Diagnostics ----------- @@ -72,4 +21,4 @@ building and publish them as diagnostic messages .. figure:: ../../resources/images/diagnostic-sphinx-errors-demo.png :align: center - Example diagnostic messages from Sphinx \ No newline at end of file + Example diagnostic messages from Sphinx diff --git a/lib/esbonio-extensions/.bumpversion.cfg b/lib/esbonio-extensions/.bumpversion.cfg index e71cca648..b1f598fc4 100644 --- a/lib/esbonio-extensions/.bumpversion.cfg +++ b/lib/esbonio-extensions/.bumpversion.cfg @@ -3,7 +3,7 @@ current_version = 0.0.1 commit = False tag = False parse = (?P\d+)\.(?P\d+)\.(?P\d+)(.dev(?P\d+))? -serialize = +serialize = {major}.{minor}.{patch}.dev{dev} {major}.{minor}.{patch} diff --git a/lib/esbonio-extensions/README.md b/lib/esbonio-extensions/README.md index b6b26ea29..f7cdb13bc 100644 --- a/lib/esbonio-extensions/README.md +++ b/lib/esbonio-extensions/README.md @@ -2,4 +2,4 @@ **This package is in early development** -A collection of Sphinx extensions. \ No newline at end of file +A collection of Sphinx extensions. diff --git a/lib/esbonio-extensions/changes/206.misc.rst b/lib/esbonio-extensions/changes/206.misc.rst new file mode 100644 index 000000000..3e505053c --- /dev/null +++ b/lib/esbonio-extensions/changes/206.misc.rst @@ -0,0 +1 @@ +No significant changes diff --git a/lib/esbonio-extensions/changes/github-template.html b/lib/esbonio-extensions/changes/github-template.html index 322eed7d6..aaff9a3bb 100644 --- a/lib/esbonio-extensions/changes/github-template.html +++ b/lib/esbonio-extensions/changes/github-template.html @@ -1,3 +1,3 @@ %(body_pre_docinfo)s %(docinfo)s -%(body)s \ No newline at end of file +%(body)s diff --git a/lib/esbonio-extensions/pyproject.toml b/lib/esbonio-extensions/pyproject.toml index fd89107b7..8430b1de8 100644 --- a/lib/esbonio-extensions/pyproject.toml +++ b/lib/esbonio-extensions/pyproject.toml @@ -61,4 +61,4 @@ usedevelop = True commands = python setup.py clean --all python setup.py sdist bdist_wheel -""" \ No newline at end of file +""" diff --git a/lib/esbonio-extensions/setup.cfg b/lib/esbonio-extensions/setup.cfg index c68e33050..e86adb63b 100644 --- a/lib/esbonio-extensions/setup.cfg +++ b/lib/esbonio-extensions/setup.cfg @@ -35,4 +35,4 @@ dev = black ; flake8 ; pytest ; pytest-cov ; mock [flake8] max-line-length = 88 -ignore = E501 \ No newline at end of file +ignore = E501 diff --git a/lib/esbonio-extensions/tests/conftest.py b/lib/esbonio-extensions/tests/conftest.py index ad7d7c00e..b8cdf08f8 100644 --- a/lib/esbonio-extensions/tests/conftest.py +++ b/lib/esbonio-extensions/tests/conftest.py @@ -1,15 +1,15 @@ import pathlib from unittest import mock +import py.test from docutils.io import StringInput -from docutils.parsers.rst import Parser, directives +from docutils.parsers.rst import directives +from docutils.parsers.rst import Parser from docutils.readers.standalone import Reader - from sphinx.ext.doctest import DoctestDirective -import py.test - -from esbonio.tutorial import SolutionDirective, TutorialDirective +from esbonio.tutorial import SolutionDirective +from esbonio.tutorial import TutorialDirective @py.test.fixture(scope="session") @@ -63,6 +63,9 @@ def rst_mock_settings(): settings.tab_width = 2 + # The following setting is required for docutils >=0.17 + settings.line_length_limit = 10_000 + # Fake some additional settings on the (Sphinx?) application object settings.env.app.confdir = "/project/docs" diff --git a/lib/esbonio-extensions/tests/data/tutorial/bare_link.rst b/lib/esbonio-extensions/tests/data/tutorial/bare_link.rst index 861ed03eb..f89ac6f34 100644 --- a/lib/esbonio-extensions/tests/data/tutorial/bare_link.rst +++ b/lib/esbonio-extensions/tests/data/tutorial/bare_link.rst @@ -1 +1 @@ -Link here: ``_ \ No newline at end of file +Link here: ``_ diff --git a/lib/esbonio-extensions/tests/data/tutorial/bold.rst b/lib/esbonio-extensions/tests/data/tutorial/bold.rst index feefde89f..558e3898a 100644 --- a/lib/esbonio-extensions/tests/data/tutorial/bold.rst +++ b/lib/esbonio-extensions/tests/data/tutorial/bold.rst @@ -1 +1 @@ -Here is some **bold** text \ No newline at end of file +Here is some **bold** text diff --git a/lib/esbonio-extensions/tests/data/tutorial/bullet_list.rst b/lib/esbonio-extensions/tests/data/tutorial/bullet_list.rst index 56b7e69c1..a1e76b11a 100644 --- a/lib/esbonio-extensions/tests/data/tutorial/bullet_list.rst +++ b/lib/esbonio-extensions/tests/data/tutorial/bullet_list.rst @@ -2,4 +2,4 @@ - Item two, for reasons it has been decided to make this item much longer than the other items to ensure that we can cover the case where the items contiain longer content -- Item three \ No newline at end of file +- Item three diff --git a/lib/esbonio-extensions/tests/data/tutorial/comment.rst b/lib/esbonio-extensions/tests/data/tutorial/comment.rst index f16b07338..9e694d914 100644 --- a/lib/esbonio-extensions/tests/data/tutorial/comment.rst +++ b/lib/esbonio-extensions/tests/data/tutorial/comment.rst @@ -1,3 +1,3 @@ .. Here is a comment -And here is some normal text \ No newline at end of file +And here is some normal text diff --git a/lib/esbonio-extensions/tests/data/tutorial/doctest_no_output.rst b/lib/esbonio-extensions/tests/data/tutorial/doctest_no_output.rst index 9af3b70aa..9d31b8eff 100644 --- a/lib/esbonio-extensions/tests/data/tutorial/doctest_no_output.rst +++ b/lib/esbonio-extensions/tests/data/tutorial/doctest_no_output.rst @@ -1,4 +1,4 @@ .. doctest:: example-code >>> l = [1,2,3] - >>> l.append(4) \ No newline at end of file + >>> l.append(4) diff --git a/lib/esbonio-extensions/tests/data/tutorial/heading.rst b/lib/esbonio-extensions/tests/data/tutorial/heading.rst index 13d8219e0..04875e9af 100644 --- a/lib/esbonio-extensions/tests/data/tutorial/heading.rst +++ b/lib/esbonio-extensions/tests/data/tutorial/heading.rst @@ -11,4 +11,4 @@ Heading 4 """"""""" Heading 5 -''''''''' \ No newline at end of file +''''''''' diff --git a/lib/esbonio-extensions/tests/data/tutorial/inline_code.rst b/lib/esbonio-extensions/tests/data/tutorial/inline_code.rst index 369a40a80..09bfd917b 100644 --- a/lib/esbonio-extensions/tests/data/tutorial/inline_code.rst +++ b/lib/esbonio-extensions/tests/data/tutorial/inline_code.rst @@ -1 +1 @@ -Here is some :code:`inline code` \ No newline at end of file +Here is some :code:`inline code` diff --git a/lib/esbonio-extensions/tests/data/tutorial/inline_link.rst b/lib/esbonio-extensions/tests/data/tutorial/inline_link.rst index c35eb139e..242ba899d 100644 --- a/lib/esbonio-extensions/tests/data/tutorial/inline_link.rst +++ b/lib/esbonio-extensions/tests/data/tutorial/inline_link.rst @@ -1 +1 @@ -Check out the `documentation `_ \ No newline at end of file +Check out the `documentation `_ diff --git a/lib/esbonio-extensions/tests/data/tutorial/italic.rst b/lib/esbonio-extensions/tests/data/tutorial/italic.rst index 2e06293b6..352c5ce0b 100644 --- a/lib/esbonio-extensions/tests/data/tutorial/italic.rst +++ b/lib/esbonio-extensions/tests/data/tutorial/italic.rst @@ -1 +1 @@ -Here is some *italic* text \ No newline at end of file +Here is some *italic* text diff --git a/lib/esbonio-extensions/tests/data/tutorial/literal_block.rst b/lib/esbonio-extensions/tests/data/tutorial/literal_block.rst index 7023ba989..3aa1d04ff 100644 --- a/lib/esbonio-extensions/tests/data/tutorial/literal_block.rst +++ b/lib/esbonio-extensions/tests/data/tutorial/literal_block.rst @@ -1,4 +1,4 @@ :: squares = [n**2 for n in range(10)] - total = sum(squares) \ No newline at end of file + total = sum(squares) diff --git a/lib/esbonio-extensions/tests/data/tutorial/note.rst b/lib/esbonio-extensions/tests/data/tutorial/note.rst index 922faf86c..58d65c4e8 100644 --- a/lib/esbonio-extensions/tests/data/tutorial/note.rst +++ b/lib/esbonio-extensions/tests/data/tutorial/note.rst @@ -1,3 +1,3 @@ .. note:: - This is an *important* note \ No newline at end of file + This is an *important* note diff --git a/lib/esbonio-extensions/tests/data/tutorial/paragraphs.rst b/lib/esbonio-extensions/tests/data/tutorial/paragraphs.rst index 5130884f5..9f5e8b093 100644 --- a/lib/esbonio-extensions/tests/data/tutorial/paragraphs.rst +++ b/lib/esbonio-extensions/tests/data/tutorial/paragraphs.rst @@ -4,4 +4,4 @@ mutliple paragraphs - unlike the code blocks it does not split the paragraph into multiple cells. Instead this should be combined into a single cell with the appropriate spacing -between the lines so that they are formatted accordingly. \ No newline at end of file +between the lines so that they are formatted accordingly. diff --git a/lib/esbonio/.bumpversion.cfg b/lib/esbonio/.bumpversion.cfg index 402400d90..194c1612c 100644 --- a/lib/esbonio/.bumpversion.cfg +++ b/lib/esbonio/.bumpversion.cfg @@ -3,7 +3,7 @@ current_version = 0.6.1 commit = False tag = False parse = (?P\d+)\.(?P\d+)\.(?P\d+)(.dev(?P\d+))? -serialize = +serialize = {major}.{minor}.{patch}.dev{dev} {major}.{minor}.{patch} diff --git a/lib/esbonio/README.md b/lib/esbonio/README.md index 6cdfb10fd..cd4590497 100644 --- a/lib/esbonio/README.md +++ b/lib/esbonio/README.md @@ -1,3 +1,4 @@ +![Esbonio logo](https://github.com/swyddfa/esbonio/blob/release/resources/io.github.swyddfa.Esbonio.svg?raw=true) # Esbonio [![PyPI](https://img.shields.io/pypi/v/esbonio?style=flat-square)](https://pypi.org/project/esbonio) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/esbonio?style=flat-square)](https://pypi.org/project/esbonio) Esbonio - to explain. @@ -11,4 +12,4 @@ The Language Server can be installed via pip. ``` $ pip install esbonio -``` \ No newline at end of file +``` diff --git a/lib/esbonio/changes/184.fix.rst b/lib/esbonio/changes/184.fix.rst new file mode 100644 index 000000000..76a33db80 --- /dev/null +++ b/lib/esbonio/changes/184.fix.rst @@ -0,0 +1,2 @@ +The language server now correctly handles windows file URIs when determining Sphinx's +build directory. diff --git a/lib/esbonio/changes/185.misc.rst b/lib/esbonio/changes/185.misc.rst new file mode 100644 index 000000000..f03f6e224 --- /dev/null +++ b/lib/esbonio/changes/185.misc.rst @@ -0,0 +1,4 @@ +The cli arguments ``--cache-dir``, ``--log-filter``, ``--log-level`` and +``--hide-sphinx-output`` have been replaced with the configuration +parameters ``esbonio.sphinx.buildDir``, ``esbonio.server.logFilter``, +``esbonio.logLevel`` and ``esbonio.server.hideSphinxOutput`` respectively diff --git a/lib/esbonio/changes/191.fix.rst b/lib/esbonio/changes/191.fix.rst new file mode 100644 index 000000000..ba1972f8b --- /dev/null +++ b/lib/esbonio/changes/191.fix.rst @@ -0,0 +1,2 @@ +Role and role target completions are now correctly generated when the role +is being typed within parenthesis e.g. ``(:kbd:...`` diff --git a/lib/esbonio/changes/192.misc.rst b/lib/esbonio/changes/192.misc.rst new file mode 100644 index 000000000..a2b42d95d --- /dev/null +++ b/lib/esbonio/changes/192.misc.rst @@ -0,0 +1,3 @@ +The language server's startup sequence has been reworked. Language clients are now +required to provide configuration parameters under the ``initializationOptions`` field +in the ``initialize`` request. diff --git a/lib/esbonio/changes/193.misc.rst b/lib/esbonio/changes/193.misc.rst new file mode 100644 index 000000000..29356f552 --- /dev/null +++ b/lib/esbonio/changes/193.misc.rst @@ -0,0 +1,2 @@ +The language server will now send an `esbonio/buildComplete` notification to +clients when it has finished (re)building the docs. diff --git a/lib/esbonio/changes/195.misc.rst b/lib/esbonio/changes/195.misc.rst new file mode 100644 index 000000000..431e46229 --- /dev/null +++ b/lib/esbonio/changes/195.misc.rst @@ -0,0 +1,3 @@ +An entry for ``esbonio`` has been added to the ``console_scripts`` +entry point, so it's now possible to launch the language server by +calling ``esbonio`` directly diff --git a/lib/esbonio/changes/208.fix.rst b/lib/esbonio/changes/208.fix.rst new file mode 100644 index 000000000..a2206ded0 --- /dev/null +++ b/lib/esbonio/changes/208.fix.rst @@ -0,0 +1,2 @@ +Path variables like ``${confDir}`` and ``${workspaceRoot}`` are now properly expanded +even when there are no additional path elements. diff --git a/lib/esbonio/changes/github-template.html b/lib/esbonio/changes/github-template.html index 322eed7d6..aaff9a3bb 100644 --- a/lib/esbonio/changes/github-template.html +++ b/lib/esbonio/changes/github-template.html @@ -1,3 +1,3 @@ %(body_pre_docinfo)s %(docinfo)s -%(body)s \ No newline at end of file +%(body)s diff --git a/lib/esbonio/esbonio/__main__.py b/lib/esbonio/esbonio/__main__.py index 36cd99bd3..93cf654e6 100644 --- a/lib/esbonio/esbonio/__main__.py +++ b/lib/esbonio/esbonio/__main__.py @@ -1,115 +1,6 @@ -import argparse -import logging import sys -import esbonio.lsp as lsp +from esbonio.cli import main -from esbonio.lsp import RstLanguageServer, __version__ -from esbonio.lsp.logger import LspHandler - -LOG_LEVELS = { - "debug": logging.DEBUG, - "error": logging.ERROR, - "info": logging.INFO, -} - - -class LogFilter: - """A log filter that accepts message from any of the listed logger names.""" - - def __init__(self, names): - self.names = names - - def filter(self, record): - return any(record.name == name for name in self.names) - - -def configure_logging(args, server: RstLanguageServer): - - level = LOG_LEVELS[args.log_level] - - lsp_logger = logging.getLogger("esbonio.lsp") - lsp_logger.setLevel(level) - - lsp_handler = LspHandler(server) - lsp_handler.setLevel(level) - - if args.log_filter is not None: - lsp_handler.addFilter(LogFilter(args.log_filter)) - - formatter = logging.Formatter("[%(name)s] %(message)s") - lsp_handler.setFormatter(formatter) - lsp_logger.addHandler(lsp_handler) - - if not args.hide_sphinx_output: - sphinx_logger = logging.getLogger("esbonio.sphinx") - sphinx_logger.setLevel(logging.INFO) - - sphinx_handler = LspHandler(server) - sphinx_handler.setLevel(logging.INFO) - - formatter = logging.Formatter("%(message)s") - sphinx_handler.setFormatter(formatter) - sphinx_logger.addHandler(sphinx_handler) - - -def start_server(args): - """Start the language server.""" - - server = lsp.create_language_server(lsp.BUILTIN_MODULES, cache_dir=args.cache_dir) - configure_logging(args, server) - - if args.port: - server.start_tcp("localhost", args.port) - else: - server.start_io() - - -cli = argparse.ArgumentParser(prog="esbonio", description="The Esbonio language server") - -cli.add_argument( - "--cache-dir", - default=None, - type=str, - help="the directory where cached data should be stored, e.g. Sphinx build output ", -) - -cli.add_argument( - "--hide-sphinx-output", - action="store_true", - help="hide sphinx build output from the log", -) - -cli.add_argument( - "--log-filter", - action="append", - help="only include log messages from loggers with the given name," - + "can be set multiple times.", -) - -cli.add_argument( - "--log-level", - choices=["error", "info", "debug"], - default="error", - help="set the level of log message to show from the language server", -) - -cli.add_argument( - "-p", - "--port", - type=int, - default=None, - help="start a TCP instance of the language server listening on the given port ", -) - -cli.add_argument( - "--version", action="store_true", help="print the current version and exit" -) - -args = cli.parse_args() - -if args.version: - print("v{}".format(__version__)) - sys.exit(0) - -start_server(args) +if __name__ == "__main__": + sys.exit(main()) diff --git a/lib/esbonio/esbonio/cli.py b/lib/esbonio/esbonio/cli.py new file mode 100644 index 000000000..b938f5ddc --- /dev/null +++ b/lib/esbonio/esbonio/cli.py @@ -0,0 +1,41 @@ +import argparse +import sys + +import esbonio.lsp as lsp +from esbonio.lsp import __version__ + + +def start_server(args): + """Start the language server.""" + + server = lsp.create_language_server(lsp.BUILTIN_MODULES) + + if args.port: + server.start_tcp("localhost", args.port) + else: + server.start_io() + + +cli = argparse.ArgumentParser(prog="esbonio", description="The Esbonio language server") + +cli.add_argument( + "-p", + "--port", + type=int, + default=None, + help="start a TCP instance of the language server listening on the given port ", +) + +cli.add_argument( + "--version", action="store_true", help="print the current version and exit" +) + + +def main(): + args = cli.parse_args() + + if args.version: + print("v{}".format(__version__)) + sys.exit(0) + + start_server(args) diff --git a/lib/esbonio/esbonio/lsp/__init__.py b/lib/esbonio/esbonio/lsp/__init__.py index f4c5cfd13..5a1eb57b7 100644 --- a/lib/esbonio/esbonio/lsp/__init__.py +++ b/lib/esbonio/esbonio/lsp/__init__.py @@ -4,33 +4,34 @@ import logging import pathlib import textwrap - -from typing import List, Optional -from urllib.parse import urlparse, unquote - +from typing import List +from typing import Optional +from urllib.parse import unquote +from urllib.parse import urlparse + +from pydantic import BaseModel +from pydantic import Field +from pygls.lsp.methods import COMPLETION +from pygls.lsp.methods import INITIALIZE +from pygls.lsp.methods import INITIALIZED +from pygls.lsp.methods import TEXT_DOCUMENT_DID_OPEN +from pygls.lsp.methods import TEXT_DOCUMENT_DID_SAVE +from pygls.lsp.types import CompletionList +from pygls.lsp.types import CompletionOptions +from pygls.lsp.types import CompletionParams +from pygls.lsp.types import DidOpenTextDocumentParams +from pygls.lsp.types import DidSaveTextDocumentParams +from pygls.lsp.types import InitializedParams +from pygls.lsp.types import InitializeParams +from pygls.lsp.types import Position from pygls.server import LanguageServer -from pygls.lsp.methods import ( - COMPLETION, - INITIALIZE, - INITIALIZED, - TEXT_DOCUMENT_DID_OPEN, - TEXT_DOCUMENT_DID_SAVE, -) -from pygls.lsp.types import ( - CompletionList, - CompletionOptions, - CompletionParams, - ConfigurationItem, - ConfigurationParams, - DidOpenTextDocumentParams, - DidSaveTextDocumentParams, - InitializeParams, - InitializedParams, - Position, -) from pygls.workspace import Document from sphinx.application import Sphinx +from esbonio.lsp.logger import LOG_LEVELS +from esbonio.lsp.logger import LogFilter +from esbonio.lsp.logger import LspHandler + __version__ = "0.6.1" @@ -43,51 +44,69 @@ ] -class LanguageFeature: - """Base class for language features.""" +class SphinxConfig(BaseModel): + """Represents both the current Sphinx configuration and also the config options that + we should create Sphinx with.""" - def __init__(self, rst: "RstLanguageServer"): - self.rst = rst - self.logger = rst.logger.getChild(self.__class__.__name__) + version: Optional[str] + """Sphinx's version number.""" + + conf_dir: Optional[str] = Field(None, alias="confDir") + """Can be used to override the default conf.py discovery mechanism.""" + + src_dir: Optional[str] = Field(None, alias="srcDir") + """Can be used to override the default assumption on where the project's rst files are + located.""" + build_dir: Optional[str] = Field(None, alias="buildDir") + """Can be used to override the default location for storing build outputs.""" -class SphinxConfig: - """Represents the `esbonio.sphinx.*` configuration namespace.""" + builder_name: str = Field("html", alias="builderName") + """The currently used builder.""" - def __init__(self, conf_dir: Optional[str] = None, src_dir: Optional[str] = None): - self.conf_dir = conf_dir - """Used to override the default 'conf.py' discovery mechanism.""" - self.src_dir = src_dir - """Used to override the assumption that rst soruce files are - in the same folder as 'conf.py'""" +class ServerConfig(BaseModel): + """Configuration options for the server.""" - @classmethod - def default(cls): - return cls(conf_dir="", src_dir="") + log_level: Optional[str] = Field("error", alias="logLevel") + """The logging level for server messages""" - @classmethod - def from_dict(cls, config): - conf_dir = config.get("confDir", "") - src_dir = config.get("srcDir", "") + log_filter: Optional[List[str]] = Field(None, alias="logFilter") + """A list of logger names to restrict output to.""" + + hide_sphinx_output: Optional[bool] = Field(False, alias="hideSphinxOutput") + """A flag to indicate if Sphinx build output should be omitted from the log.""" + + +class InitializationOptions(BaseModel): + """The initialization options we can expect to receive from a client.""" + + sphinx: Optional[SphinxConfig] = Field(default_factory=SphinxConfig) + """The ``esbonio.sphinx.*`` namespace of options""" + + server: Optional[ServerConfig] = Field(default_factory=ServerConfig) + """The ``esbonio.server.*`` namespace of options""" + + +class LanguageFeature: + """Base class for language features.""" - return cls(conf_dir=conf_dir, src_dir=src_dir) + def __init__(self, rst: "RstLanguageServer"): + self.rst = rst + self.logger = rst.logger.getChild(self.__class__.__name__) class RstLanguageServer(LanguageServer): - def __init__(self, cache_dir=None, *args, **kwargs): + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.cache_dir = cache_dir - """The folder to store cached data in.""" - self.logger = logging.getLogger(__name__) """The logger that should be used for all Language Server log entries""" self.app: Optional[Sphinx] = None """Sphinx application instance configured for the current project.""" - self.on_init_hooks = [] + self.on_initialize_hooks = [] """A list of functions to run on initialization""" self.on_initialized_hooks = [] @@ -104,7 +123,7 @@ def add_feature(self, feature): """Add a new feature to the language server.""" if hasattr(feature, "initialize"): - self.on_init_hooks.append(feature.initialize) + self.on_initialize_hooks.append(feature.initialize) if hasattr(feature, "initialized"): self.on_initialized_hooks.append(feature.initialized) @@ -139,6 +158,33 @@ def run_hooks(self, kind: str, *args): self.logger.debug("Running '%s' hook %s", kind, hook) hook(*args) + def _configure_logging(self, config: ServerConfig): + level = LOG_LEVELS[config.log_level] + + lsp_logger = logging.getLogger("esbonio.lsp") + lsp_logger.setLevel(level) + + lsp_handler = LspHandler(self) + lsp_handler.setLevel(level) + + if config.log_filter is not None and len(config.log_filter) > 0: + lsp_handler.addFilter(LogFilter(config.log_filter)) + + formatter = logging.Formatter("[%(name)s] %(message)s") + lsp_handler.setFormatter(formatter) + lsp_logger.addHandler(lsp_handler) + + if not config.hide_sphinx_output: + sphinx_logger = logging.getLogger("esbonio.sphinx") + sphinx_logger.setLevel(logging.INFO) + + sphinx_handler = LspHandler(self) + sphinx_handler.setLevel(logging.INFO) + + formatter = logging.Formatter("%(message)s") + sphinx_handler.setFormatter(formatter) + sphinx_logger.addHandler(sphinx_handler) + def get_line_til_position(doc: Document, position: Position) -> str: """Return the line up until the position of the cursor.""" @@ -182,45 +228,34 @@ def default(o): return json.dumps(obj, default=default) -def create_language_server( - modules: List[str], cache_dir: Optional[str] = None -) -> RstLanguageServer: +def create_language_server(modules: List[str]) -> RstLanguageServer: """Create a new language server instance. Parameters ---------- modules: The list of modules that should be loaded. - cache_dir: - The folder to use for cached data. """ - server = RstLanguageServer(cache_dir) + server = RstLanguageServer() for mod in modules: server.load_module(mod) @server.feature(INITIALIZE) def on_initialize(rst: RstLanguageServer, params: InitializeParams): - rst.logger.debug("%s: %s", INITIALIZE, dump(params)) - rst.run_hooks("init") + options = InitializationOptions(**params.initialization_options) + # Let there be light... + rst._configure_logging(options.server) rst.logger.info("Language server started.") + rst.logger.debug("%s: %s", INITIALIZE, dump(params)) + rst.run_hooks("initialize", options) + @server.feature(INITIALIZED) - async def on_initialized(rst: RstLanguageServer, params: InitializedParams): + def on_initialized(rst: RstLanguageServer, params: InitializedParams): rst.logger.debug("%s: %s", INITIALIZED, dump(params)) - - config_params = ConfigurationParams( - items=[ConfigurationItem(section="esbonio.sphinx")] - ) - - config_items = await rst.get_configuration_async(config_params) - sphinx_config = SphinxConfig.from_dict(config_items[0] or dict()) - - rst.logger.debug("SphinxConfig: %s", dump(sphinx_config)) - rst.run_hooks("initialized", sphinx_config) - - rst.logger.info("LSP server initialized") + rst.run_hooks("initialized") @server.feature( COMPLETION, CompletionOptions(trigger_characters=[".", ":", "`", "<", "/"]) diff --git a/lib/esbonio/esbonio/lsp/directives.py b/lib/esbonio/esbonio/lsp/directives.py index 42eca7345..610885600 100644 --- a/lib/esbonio/esbonio/lsp/directives.py +++ b/lib/esbonio/esbonio/lsp/directives.py @@ -2,18 +2,18 @@ import importlib import inspect import re - -from typing import List, Union, Tuple - -from docutils.parsers.rst import directives, Directive -from pygls.lsp.types import ( - CompletionItem, - CompletionItemKind, - InsertTextFormat, - Position, - Range, - TextEdit, -) +from typing import List +from typing import Tuple +from typing import Union + +from docutils.parsers.rst import Directive +from docutils.parsers.rst import directives +from pygls.lsp.types import CompletionItem +from pygls.lsp.types import CompletionItemKind +from pygls.lsp.types import InsertTextFormat +from pygls.lsp.types import Position +from pygls.lsp.types import Range +from pygls.lsp.types import TextEdit from pygls.workspace import Document import esbonio.lsp as lsp @@ -67,7 +67,7 @@ class Directives(lsp.LanguageFeature): """Directive support for the language server.""" - def initialized(self, config: lsp.SphinxConfig): + def initialize(self, options: lsp.InitializationOptions): self.discover() def discover(self): diff --git a/lib/esbonio/esbonio/lsp/filepaths.py b/lib/esbonio/esbonio/lsp/filepaths.py index 5fcd93cf4..0fa5b8161 100644 --- a/lib/esbonio/esbonio/lsp/filepaths.py +++ b/lib/esbonio/esbonio/lsp/filepaths.py @@ -7,16 +7,21 @@ """ import pathlib import re +from typing import Dict +from typing import List -from typing import Dict, List - -from pygls.lsp.types import CompletionItem, CompletionItemKind, Position +from pygls.lsp.types import CompletionItem +from pygls.lsp.types import CompletionItemKind +from pygls.lsp.types import Position from pygls.workspace import Document import esbonio.lsp as lsp -from esbonio.lsp import RstLanguageServer, LanguageFeature +from esbonio.lsp import LanguageFeature +from esbonio.lsp import RstLanguageServer from esbonio.lsp.directives import DIRECTIVE -from esbonio.lsp.roles import PARTIAL_PLAIN_TARGET, PARTIAL_ALIASED_TARGET +from esbonio.lsp.roles import PARTIAL_ALIASED_TARGET +from esbonio.lsp.roles import PARTIAL_PLAIN_TARGET + # TODO: Would it be better to make the role and directive language features extensible # and have this and the intersphinx feature contribute suggestions when they know @@ -76,7 +81,9 @@ def path_to_completion_item(self, path: pathlib.Path) -> CompletionItem: kind = CompletionItemKind.Folder if path.is_dir() else CompletionItemKind.File return CompletionItem( - label=str(path.name), kind=kind, insert_text=f"{path.name}", + label=str(path.name), + kind=kind, + insert_text=f"{path.name}", ) diff --git a/lib/esbonio/esbonio/lsp/intersphinx.py b/lib/esbonio/esbonio/lsp/intersphinx.py index e3aa0af0c..bbdec442d 100644 --- a/lib/esbonio/esbonio/lsp/intersphinx.py +++ b/lib/esbonio/esbonio/lsp/intersphinx.py @@ -1,35 +1,32 @@ """Intersphinx support.""" import re +from typing import List +from typing import Optional -from typing import List, Optional -from pygls.lsp.types import ( - CompletionItem, - CompletionItemKind, - Position, -) +from pygls.lsp.types import CompletionItem +from pygls.lsp.types import CompletionItemKind +from pygls.lsp.types import Position from pygls.workspace import Document import esbonio.lsp as lsp -from esbonio.lsp.roles import ( - COMPLETION_TARGETS, - DEFAULT_TARGET, - PARTIAL_PLAIN_TARGET, - PARTIAL_ALIASED_TARGET, -) +from esbonio.lsp.roles import COMPLETION_TARGETS +from esbonio.lsp.roles import DEFAULT_TARGET +from esbonio.lsp.roles import PARTIAL_ALIASED_TARGET +from esbonio.lsp.roles import PARTIAL_PLAIN_TARGET from esbonio.lsp.sphinx import get_domains PARTIAL_INTER_PLAIN_TARGET = re.compile( r""" - (^|.*[ ]) # roles must be preceeded by a space, or start the line - (?P: # roles start with the ':' character - (?!:) # make sure the next character is not ':' - (?P[\w]+:)? # there may be a domain namespace - (?P[\w-]*) # followed by the role name - :) # the role name ends with a ':' - ` # the target begins with a '`' - (?P[^<:`]*) # match "plain link" targets - : # projects end with a ':' + (^|.*[^\w:]) # roles cannot be preceeded by certain chars + (?P: # roles start with the ':' character + (?!:) # make sure the next character is not ':' + (?P[\w]+:)? # there may be a domain namespace + (?P[\w-]*) # followed by the role name + :) # the role name ends with a ':' + ` # the target begins with a '`' + (?P[^<:`]*) # match "plain link" targets + : # projects end with a ':' $ """, re.MULTILINE | re.VERBOSE, @@ -45,16 +42,16 @@ PARTIAL_INTER_ALIASED_TARGET = re.compile( r""" - (^|.*[ ]) # roles must be preceeded by a space, or start the line - (?P: # roles start with the ':' character - (?!:) # make sure the next character is not ':' - (?P[\w]+:)? # there may be a domain namespace - (?P[\w-]*) # followed by the role name - :) # the role name ends with a ':' - ` # the target begins with a '`'` - .*< # the actual target name starts after a '<' - (?P[^`:]*) # match "aliased" targets - : # projects end with a ':' + (^|.*[^\w:]) # roles cannot be preceeded by alpha chars + (?P: # roles start with the ':' character + (?!:) # make sure the next character is not ':' + (?P[\w]+:)? # there may be a domain namespace + (?P[\w-]*) # followed by the role name + :) # the role name ends with a ':' + ` # the target begins with a '`'` + .*< # the actual target name starts after a '<' + (?P[^`:]*) # match "aliased" targets + : # projects end with a ':' $ """, re.MULTILINE | re.VERBOSE, @@ -72,7 +69,7 @@ class InterSphinx(lsp.LanguageFeature): """Intersphinx support for the language server.""" - def initialized(self, config: lsp.SphinxConfig): + def initialize(self, options: lsp.InitializationOptions): self.targets = {} self.target_types = {} diff --git a/lib/esbonio/esbonio/lsp/logger.py b/lib/esbonio/esbonio/lsp/logger.py index b00c523f1..c8bf28425 100644 --- a/lib/esbonio/esbonio/lsp/logger.py +++ b/lib/esbonio/esbonio/lsp/logger.py @@ -1,10 +1,25 @@ """This module defines a custom logging handler that publishes log messages to an LSP client.""" - import logging from pygls.server import LanguageServer +LOG_LEVELS = { + "debug": logging.DEBUG, + "error": logging.ERROR, + "info": logging.INFO, +} + + +class LogFilter: + """A log filter that accepts message from any of the listed logger names.""" + + def __init__(self, names): + self.names = names + + def filter(self, record): + return any(record.name == name for name in self.names) + class LspHandler(logging.Handler): """A logging handler that will send log records to an LSP client.""" diff --git a/lib/esbonio/esbonio/lsp/roles.py b/lib/esbonio/esbonio/lsp/roles.py index 3c981a627..abcd9ceea 100644 --- a/lib/esbonio/esbonio/lsp/roles.py +++ b/lib/esbonio/esbonio/lsp/roles.py @@ -1,18 +1,15 @@ """Role support.""" import collections import re - from typing import List from docutils.parsers.rst import roles -from pygls.lsp.types import ( - CompletionItem, - CompletionItemKind, - DidSaveTextDocumentParams, - Position, - Range, - TextEdit, -) +from pygls.lsp.types import CompletionItem +from pygls.lsp.types import CompletionItemKind +from pygls.lsp.types import DidSaveTextDocumentParams +from pygls.lsp.types import Position +from pygls.lsp.types import Range +from pygls.lsp.types import TextEdit from pygls.workspace import Document import esbonio.lsp as lsp @@ -22,12 +19,12 @@ PARTIAL_ROLE = re.compile( r""" - (^|.*[ ]) # roles must be preceeded by a space, or start the line - (?P: # roles start with the ':' character - (?!:) # make sure the next character is not ':' - (?P[\w]+:)? # there may be a domain namespace - (?P[\w-]*)) # match the role name - $ # ensure pattern only matches incomplete roles + (^|.*[^\w:]) # roles cannot be preceeded by certain characters + (?P: # roles start with the ':' character + (?!:) # make sure the next character is not ':' + (?P[\w]+:)? # there may be a domain namespace + (?P[\w-]*)) # match the role name + $ # ensure pattern only matches incomplete roles """, re.MULTILINE | re.VERBOSE, ) @@ -43,14 +40,14 @@ PARTIAL_PLAIN_TARGET = re.compile( r""" - (^|.*[ ]) # roles must be preceeded by a space, or start the line - (?P: # roles start with the ':' character - (?!:) # make sure the next character is not ':' - (?P[\w]+:)? # there may be a domain namespace - (?P[\w-]*) # followed by the role name - :) # the role name ends with a ':' - ` # the target begins with a '`' - (?P[^<:`]*) # match "plain link" targets + (^|.*[^\w:]) # roles cannot be preceeded by certain chars + (?P: # roles start with the ':' character + (?!:) # make sure the next character is not ':' + (?P[\w]+:)? # there may be a domain namespace + (?P[\w-]*) # followed by the role name + :) # the role name ends with a ':' + ` # the target begins with a '`' + (?P[^<:`]*) # match "plain link" targets $ """, re.MULTILINE | re.VERBOSE, @@ -66,15 +63,15 @@ PARTIAL_ALIASED_TARGET = re.compile( r""" - (^|.*[ ]) # roles must be preceeded by a space, or start the line - (?P: # roles start with the ':' character - (?!:) # make sure the next character is not ':' - (?P[\w]+:)? # there may be a domain namespace - (?P[\w-]*) # followed by the role name - :) # the role name ends with a ':' - ` # the target begins with a '`'` - .*< # the actual target name starts after a '<' - (?P[^`:]*) # match "aliased" targets + (^|.*[^\w:]) # roles cannot be preceeded by certain chars + (?P: # roles start with the ':' character + (?!:) # make sure the next character is not ':' + (?P[\w]+:)? # there may be a domain namespace + (?P[\w-]*) # followed by the role name + :) # the role name ends with a ':' + ` # the target begins with a '`'` + .*< # the actual target name starts after a '<' + (?P[^`:]*) # match "aliased" targets $ """, re.MULTILINE | re.VERBOSE, @@ -107,7 +104,7 @@ class Roles(lsp.LanguageFeature): """Role support for the language server.""" - def initialized(self, config: lsp.SphinxConfig): + def initialize(self, options: lsp.InitializationOptions): self.discover_roles() self.discover_targets() diff --git a/lib/esbonio/esbonio/lsp/sphinx.py b/lib/esbonio/esbonio/lsp/sphinx.py index 45bb5fbd8..c0208ac40 100644 --- a/lib/esbonio/esbonio/lsp/sphinx.py +++ b/lib/esbonio/esbonio/lsp/sphinx.py @@ -4,20 +4,20 @@ import logging import pathlib import re - -from typing import Iterator, Optional, Tuple +import traceback +from typing import Iterator +from typing import Optional +from typing import Tuple from urllib.parse import quote import appdirs - -from pygls.lsp.types import ( - Diagnostic, - DiagnosticSeverity, - DidSaveTextDocumentParams, - MessageType, - Position, - Range, -) +from pygls.lsp.types import Diagnostic +from pygls.lsp.types import DiagnosticSeverity +from pygls.lsp.types import DidSaveTextDocumentParams +from pygls.lsp.types import MessageType +from pygls.lsp.types import Position +from pygls.lsp.types import Range +from sphinx import __version__ as __sphinx_version__ from sphinx.application import Sphinx from sphinx.domains import Domain from sphinx.util import console @@ -25,6 +25,9 @@ import esbonio.lsp as lsp +PATH_VAR_PATTERN = re.compile(r"^\${(\w+)}/?.*") + + PROBLEM_PATTERN = re.compile( r""" (?P(.*:\\)?[^:]*): # Capture the path to the file containing the problem @@ -95,7 +98,7 @@ def expand_conf_dir(root_dir: str, conf_dir: str) -> str: The user provided path """ - match = re.match(r"^\${(\w+)}/.*", conf_dir) + match = PATH_VAR_PATTERN.match(conf_dir) if not match or match.group(1) != "workspaceRoot": return conf_dir @@ -134,7 +137,7 @@ def get_src_dir( src_dir = config.src_dir root_dir = lsp.filepath_from_uri(root_uri) - match = re.match(r"^\${(\w+)}/.*", src_dir) + match = PATH_VAR_PATTERN.match(src_dir) if match and match.group(1) == "workspaceRoot": src = pathlib.Path(src_dir).parts[1:] return pathlib.Path(root_dir, *src).resolve() @@ -146,6 +149,25 @@ def get_src_dir( return src_dir +def get_build_dir(conf_dir: pathlib.Path, config: lsp.SphinxConfig) -> pathlib.Path: + + if config.build_dir is None: + # Try to pick a sensible dir based on the project's location + cache = appdirs.user_cache_dir("esbonio", "swyddfa") + project = hashlib.md5(str(conf_dir).encode()).hexdigest() + + return pathlib.Path(cache) / project + + build_dir = pathlib.Path(config.build_dir) + + # Strangely for windows paths, there's an extra leading slash which we have to + # remove ourselves. + if isinstance(build_dir, pathlib.WindowsPath) and str(build_dir).startswith("\\"): + build_dir = pathlib.Path(str(build_dir)[1:]) + + return build_dir + + def find_conf_dir(root_uri: str, config: lsp.SphinxConfig) -> Optional[pathlib.Path]: """Attempt to find Sphinx's configuration file in the given workspace.""" @@ -226,13 +248,29 @@ def __init__(self, *args, **kwargs): self.sphinx_log = logging.getLogger("esbonio.sphinx") """The logger that should be used by a Sphinx application""" - def initialized(self, config: lsp.SphinxConfig): + def initialize(self, options: lsp.InitializationOptions): - self.config = config - self.logger.debug("%s", self.config) + self.config = options.sphinx + self.logger.debug("SphinxConfig %s", self.config.dict()) self.create_app(self.config) self.build_app() + def initialized(self): + + if not self.rst.app: + return + + app = self.rst.app + params = lsp.SphinxConfig( + version=__sphinx_version__, + confDir=app.confdir, + srcDir=app.srcdir, + buildDir=app.outdir, + builderName=app.builder.name, + ) + self.rst.logger.debug("Final Sphinx Config: %s", params.dict(by_alias=True)) + self.rst.send_notification("esbonio/sphinxConfiguration", params) + def save(self, params: DidSaveTextDocumentParams): filepath = lsp.filepath_from_uri(params.text_document.uri) @@ -269,21 +307,16 @@ def create_app(self, config: lsp.SphinxConfig): ) return - if self.rst.cache_dir is not None: - build_dir = self.rst.cache_dir - else: - # Try to pick a sensible dir based on the project's location - cache = appdirs.user_cache_dir("esbonio", "swyddfa") - project = hashlib.md5(str(conf_dir).encode()).hexdigest() - build_dir = pathlib.Path(cache) / project - + builder_name = config.builder_name src_dir = get_src_dir(self.rst.workspace.root_uri, conf_dir, config) + build_dir = get_build_dir(conf_dir, config) doctree_dir = pathlib.Path(build_dir) / "doctrees" + build_dir /= builder_name self.rst.logger.debug("Config dir %s", conf_dir) self.rst.logger.debug("Src dir %s", src_dir) self.rst.logger.debug("Build dir %s", build_dir) - self.rst.logger.debug("Doctree dir %s", str(doctree_dir)) + self.rst.logger.debug("Doctree dir %s", doctree_dir) # Disable color escape codes in Sphinx's log messages console.nocolor() @@ -298,13 +331,12 @@ def create_app(self, config: lsp.SphinxConfig): status=self, warning=self, ) - except Exception as exc: - message = "Unable to initialize Sphinx, see output window for details." + except Exception: self._conf_dir = conf_dir - self.sphinx_log.error(exc) + self.sphinx_log.error(traceback.format_exc()) self.rst.show_message( - message=message, + message="Unable to initialize Sphinx, see output window for details.", msg_type=MessageType.Error, ) @@ -315,15 +347,16 @@ def build_app(self): try: self.rst.app.build() - except Exception as exc: + except Exception: message = "Unable to build documentation, see output window for details." - self.sphinx_log.error(exc) + self.sphinx_log.error(traceback.format_exc()) self.rst.show_message( message=message, msg_type=MessageType.Error, ) + self.rst.send_notification("esbonio/buildComplete", {}) self.report_diagnostics() def report_diagnostics(self): diff --git a/lib/esbonio/esbonio/lsp/testing.py b/lib/esbonio/esbonio/lsp/testing.py index 6b6ddffea..c9c44e6c4 100644 --- a/lib/esbonio/esbonio/lsp/testing.py +++ b/lib/esbonio/esbonio/lsp/testing.py @@ -1,25 +1,48 @@ """Utility functions to help with testing Language Server features.""" import logging import pathlib - -from typing import List, Optional, Set +from typing import List +from typing import Optional +from typing import Set from pygls.lsp.types import Position from pygls.workspace import Document +from sphinx import __version__ as __sphinx_version__ from esbonio.lsp import LanguageFeature logger = logging.getLogger(__name__) +def sphinx_version(eq: Optional[int] = None) -> bool: + """Helper function for determining which version of Sphinx we are + testing with. + + Currently this only cares about the major version number. + + Parameters + ---------- + eq: + When set returns ``True`` if the Sphinx version exactly matches + what's given. + """ + + major, _, _ = [int(v) for v in __sphinx_version__.split(".")] + + if eq and major == eq: + return True + + return False + + def directive_argument_patterns(name: str, partial: str = "") -> List[str]: """Return a number of example directive argument patterns. These correspond to test cases where directive argument suggestions should be generated. - Paramters - --------- + Parameters + ---------- name: The name of the directive to generate suggestions for. partial: @@ -28,6 +51,31 @@ def directive_argument_patterns(name: str, partial: str = "") -> List[str]: return [s.format(name, partial) for s in [".. {}:: {}", " .. {}:: {}"]] +def role_patterns(partial: str = "") -> List[str]: + """Return a number of example role patterns. + + These correspond to when role suggestions should be generated. + + Parameters + ---------- + partial: + The partial role name that the user has already entered + """ + return [ + s.format(partial) + for s in [ + "{}", + "({}", + " {}", + " ({}", + "some text {}", + "some text ({}", + " some text {}", + " some text ({}", + ] + ] + + def role_target_patterns(name: str, partial: str = "") -> List[str]: """Return a number of example role target patterns. @@ -44,9 +92,13 @@ def role_target_patterns(name: str, partial: str = "") -> List[str]: s.format(name, partial) for s in [ ":{}:`{}", + "(:{}:`{}", ":{}:`More Info <{}", + "(:{}:`More Info <{}", " :{}:`{}", + " (:{}:`{}", " :{}:`Some Label <{}", + " (:{}:`Some Label <{}", ] ] @@ -67,9 +119,13 @@ def intersphinx_target_patterns(name: str, project: str) -> List[str]: s.format(name, project) for s in [ ":{}:`{}:", + "(:{}:`{}:", ":{}:`More Info <{}:", + "(:{}:`More Info <{}:", " :{}:`{}:", + " (:{}:`{}:", " :{}:`Some Label <{}:", + " (:{}:`Some Label <{}:", ] ] diff --git a/lib/esbonio/pyproject.toml b/lib/esbonio/pyproject.toml index 172869dab..96437a81f 100644 --- a/lib/esbonio/pyproject.toml +++ b/lib/esbonio/pyproject.toml @@ -48,15 +48,18 @@ underlines = ["-", "^", "\""] legacy_tox_ini = """ [tox] isolated_build = True +skip_missing_interpreters = true +envlist = py{36,37,38,39}-sphinx{2,3,4} [testenv] deps = - mock + sphinx2: sphinx>=2,<3 + sphinx3: sphinx>=3,<4 + sphinx4: sphinx>=4,<5 pytest pytest-cov -extras = lsp commands = - pytest --cov=esbonio.lsp {posargs} + pytest --tb=short --cov=esbonio.lsp {posargs} [testenv:pkg] deps = @@ -65,4 +68,4 @@ usedevelop = True commands = python setup.py clean --all python setup.py sdist bdist_wheel -""" \ No newline at end of file +""" diff --git a/lib/esbonio/setup.cfg b/lib/esbonio/setup.cfg index ed65078b4..34ea95291 100644 --- a/lib/esbonio/setup.cfg +++ b/lib/esbonio/setup.cfg @@ -36,10 +36,14 @@ install_requires = [options.packages.find] exclude = tests* +[options.entry_points] +console_scripts = + esbonio = esbonio.cli:main + [options.extras_require] -dev = black ; flake8 ; pytest ; pytest-cov ; mock +dev = black ; flake8 ; pre-commit ; pytest ; pytest-cov ; tox lsp = [flake8] max-line-length = 88 -ignore = E501 +ignore = E501,W503 diff --git a/lib/esbonio/tests/conftest.py b/lib/esbonio/tests/conftest.py index 113ffd6b6..006654969 100644 --- a/lib/esbonio/tests/conftest.py +++ b/lib/esbonio/tests/conftest.py @@ -1,7 +1,6 @@ import pathlib import py.test - from sphinx.application import Sphinx diff --git a/lib/esbonio/tests/data/sphinx-default/.vscode/settings.json b/lib/esbonio/tests/data/sphinx-default/.vscode/settings.json index 18bac8245..fec123f91 100644 --- a/lib/esbonio/tests/data/sphinx-default/.vscode/settings.json +++ b/lib/esbonio/tests/data/sphinx-default/.vscode/settings.json @@ -1,4 +1,4 @@ { "python.pythonPath": "${workspaceRoot}/../../../../../.env/bin/python", "python.formatting.provider": "black" -} \ No newline at end of file +} diff --git a/lib/esbonio/tests/data/sphinx-default/conf.py b/lib/esbonio/tests/data/sphinx-default/conf.py index bab801927..ad085cff3 100644 --- a/lib/esbonio/tests/data/sphinx-default/conf.py +++ b/lib/esbonio/tests/data/sphinx-default/conf.py @@ -3,9 +3,7 @@ # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html - # -- Path setup -------------------------------------------------------------- - # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. @@ -13,7 +11,6 @@ # import os # import sys # sys.path.insert(0, os.path.abspath('.')) - # -- Project information ----------------------------------------------------- project = "Defaults" diff --git a/lib/esbonio/tests/data/sphinx-default/glossary.rst b/lib/esbonio/tests/data/sphinx-default/glossary.rst index 796f635be..dc33b2aba 100644 --- a/lib/esbonio/tests/data/sphinx-default/glossary.rst +++ b/lib/esbonio/tests/data/sphinx-default/glossary.rst @@ -7,4 +7,4 @@ Glossary The longest side of a triangle right angle - An angle of 90 degrees \ No newline at end of file + An angle of 90 degrees diff --git a/lib/esbonio/tests/data/sphinx-default/theorems/index.rst b/lib/esbonio/tests/data/sphinx-default/theorems/index.rst index 14c0817b5..a49e8a5d2 100644 --- a/lib/esbonio/tests/data/sphinx-default/theorems/index.rst +++ b/lib/esbonio/tests/data/sphinx-default/theorems/index.rst @@ -5,4 +5,4 @@ There are many useful theorems, you will find some of them here. .. toctree:: - pythagoras \ No newline at end of file + pythagoras diff --git a/lib/esbonio/tests/data/sphinx-error/conf.py b/lib/esbonio/tests/data/sphinx-error/conf.py index 3333194cb..55d841c02 100644 --- a/lib/esbonio/tests/data/sphinx-error/conf.py +++ b/lib/esbonio/tests/data/sphinx-error/conf.py @@ -3,9 +3,7 @@ # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html - # -- Path setup -------------------------------------------------------------- - # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. @@ -13,7 +11,6 @@ # import os # import sys # sys.path.insert(0, os.path.abspath('.')) - # -- Project information ----------------------------------------------------- project = "Defaults" diff --git a/lib/esbonio/tests/data/sphinx-extensions/.vscode/settings.json b/lib/esbonio/tests/data/sphinx-extensions/.vscode/settings.json index 18bac8245..fec123f91 100644 --- a/lib/esbonio/tests/data/sphinx-extensions/.vscode/settings.json +++ b/lib/esbonio/tests/data/sphinx-extensions/.vscode/settings.json @@ -1,4 +1,4 @@ { "python.pythonPath": "${workspaceRoot}/../../../../../.env/bin/python", "python.formatting.provider": "black" -} \ No newline at end of file +} diff --git a/lib/esbonio/tests/data/sphinx-extensions/conf.py b/lib/esbonio/tests/data/sphinx-extensions/conf.py index 9d2bf2c3e..af917e08c 100644 --- a/lib/esbonio/tests/data/sphinx-extensions/conf.py +++ b/lib/esbonio/tests/data/sphinx-extensions/conf.py @@ -3,9 +3,7 @@ # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html - # -- Path setup -------------------------------------------------------------- - # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. @@ -13,7 +11,6 @@ # import os # import sys # sys.path.insert(0, os.path.abspath('.')) - # -- Project information ----------------------------------------------------- project = "Defaults" diff --git a/lib/esbonio/tests/data/sphinx-extensions/glossary.rst b/lib/esbonio/tests/data/sphinx-extensions/glossary.rst index 796f635be..dc33b2aba 100644 --- a/lib/esbonio/tests/data/sphinx-extensions/glossary.rst +++ b/lib/esbonio/tests/data/sphinx-extensions/glossary.rst @@ -7,4 +7,4 @@ Glossary The longest side of a triangle right angle - An angle of 90 degrees \ No newline at end of file + An angle of 90 degrees diff --git a/lib/esbonio/tests/data/sphinx-extensions/theorems/index.rst b/lib/esbonio/tests/data/sphinx-extensions/theorems/index.rst index 14c0817b5..a49e8a5d2 100644 --- a/lib/esbonio/tests/data/sphinx-extensions/theorems/index.rst +++ b/lib/esbonio/tests/data/sphinx-extensions/theorems/index.rst @@ -5,4 +5,4 @@ There are many useful theorems, you will find some of them here. .. toctree:: - pythagoras \ No newline at end of file + pythagoras diff --git a/lib/esbonio/tests/data/sphinx-srcdir/.vscode/settings.json b/lib/esbonio/tests/data/sphinx-srcdir/.vscode/settings.json index 6a9ce9641..42e7191c9 100644 --- a/lib/esbonio/tests/data/sphinx-srcdir/.vscode/settings.json +++ b/lib/esbonio/tests/data/sphinx-srcdir/.vscode/settings.json @@ -2,4 +2,4 @@ "python.pythonPath": "${workspaceRoot}/../../../../../.env/bin/python", "python.formatting.provider": "black", "esbonio.sphinx.srcDir": "${confDir}/../sphinx-default" -} \ No newline at end of file +} diff --git a/lib/esbonio/tests/data/sphinx-srcdir/conf.py b/lib/esbonio/tests/data/sphinx-srcdir/conf.py index bab801927..ad085cff3 100644 --- a/lib/esbonio/tests/data/sphinx-srcdir/conf.py +++ b/lib/esbonio/tests/data/sphinx-srcdir/conf.py @@ -3,9 +3,7 @@ # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html - # -- Path setup -------------------------------------------------------------- - # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. @@ -13,7 +11,6 @@ # import os # import sys # sys.path.insert(0, os.path.abspath('.')) - # -- Project information ----------------------------------------------------- project = "Defaults" diff --git a/lib/esbonio/tests/data/sphinx-srcdir/test.rst b/lib/esbonio/tests/data/sphinx-srcdir/test.rst index 7b965a586..0162acf3a 100644 --- a/lib/esbonio/tests/data/sphinx-srcdir/test.rst +++ b/lib/esbonio/tests/data/sphinx-srcdir/test.rst @@ -1,2 +1,2 @@ .. Try linking to something in the sphinx-default folder -.. e.g. :ref:`... \ No newline at end of file +.. e.g. :ref:`... diff --git a/lib/esbonio/tests/lsp/test_directives.py b/lib/esbonio/tests/lsp/test_directives.py index 901d89dfb..cc1f80eb7 100644 --- a/lib/esbonio/tests/lsp/test_directives.py +++ b/lib/esbonio/tests/lsp/test_directives.py @@ -5,6 +5,7 @@ from esbonio.lsp.directives import Directives from esbonio.lsp.testing import completion_test +from esbonio.lsp.testing import sphinx_version DEFAULT_EXPECTED = { @@ -126,7 +127,7 @@ def test_directive_completions(sphinx, project, text, expected, unexpected): rst.logger = logging.getLogger("rst") feature = Directives(rst) - feature.initialized(None) + feature.initialize(None) completion_test(feature, text, expected=expected, unexpected=unexpected) @@ -143,8 +144,8 @@ def test_directive_completions(sphinx, project, text, expected, unexpected): "special-members", } IMAGE_OPTS = {"align", "alt", "class", "height", "scale", "target", "width"} -PY_FUNC_OPTS = {"annotation", "async", "module", "noindex", "noindexentry"} -C_FUNC_OPTS = {"noindexentry"} +PY_FUNC_OPTS = {"annotation", "async", "module", "noindex"} +C_FUNC_OPTS = {"noindex"} if sphinx_version(eq=2) else {"noindexentry"} @py.test.mark.parametrize( @@ -217,6 +218,6 @@ def test_directive_option_completions(sphinx, project, text, expected, unexpecte rst.logger = logging.getLogger("rst") feature = Directives(rst) - feature.initialized(None) + feature.initialize(None) completion_test(feature, text, expected=expected, unexpected=unexpected) diff --git a/lib/esbonio/tests/lsp/test_filepaths.py b/lib/esbonio/tests/lsp/test_filepaths.py index f2733efac..fe76afac1 100644 --- a/lib/esbonio/tests/lsp/test_filepaths.py +++ b/lib/esbonio/tests/lsp/test_filepaths.py @@ -4,14 +4,10 @@ import py.test -from pygls.lsp.types import CompletionItemKind - from esbonio.lsp.filepaths import FilepathCompletions -from esbonio.lsp.testing import ( - completion_test, - directive_argument_patterns, - role_target_patterns, -) +from esbonio.lsp.testing import completion_test +from esbonio.lsp.testing import directive_argument_patterns +from esbonio.lsp.testing import role_target_patterns ROOT_FILES = { "_static", @@ -43,21 +39,46 @@ def trigger_cases(path=None): *itertools.product( [*trigger_cases("/"), *trigger_cases("/conf")], [ - ("sphinx-default", "index.rst", ROOT_FILES, None,), - ("sphinx-default", "theorems/pythagoras.rst", ROOT_FILES, None,), + ( + "sphinx-default", + "index.rst", + ROOT_FILES, + None, + ), + ( + "sphinx-default", + "theorems/pythagoras.rst", + ROOT_FILES, + None, + ), ], ), *itertools.product( trigger_cases(), [ - ("sphinx-default", "index.rst", ROOT_FILES, None,), - ("sphinx-default", "theorems/pythagoras.rst", THEOREM_FILES, None,), + ( + "sphinx-default", + "index.rst", + ROOT_FILES, + None, + ), + ( + "sphinx-default", + "theorems/pythagoras.rst", + THEOREM_FILES, + None, + ), ], ), *itertools.product( trigger_cases("../"), [ - ("sphinx-default", "theorems/pythagoras.rst", ROOT_FILES, None,), + ( + "sphinx-default", + "theorems/pythagoras.rst", + ROOT_FILES, + None, + ), ( "sphinx-default", "index.rst", @@ -69,8 +90,18 @@ def trigger_cases(path=None): *itertools.product( trigger_cases("/theorems/"), [ - ("sphinx-default", "index.rst", THEOREM_FILES, None,), - ("sphinx-default", "theorems/pythagoras.rst", THEOREM_FILES, None,), + ( + "sphinx-default", + "index.rst", + THEOREM_FILES, + None, + ), + ( + "sphinx-default", + "theorems/pythagoras.rst", + THEOREM_FILES, + None, + ), ], ), ], diff --git a/lib/esbonio/tests/lsp/test_intersphinx.py b/lib/esbonio/tests/lsp/test_intersphinx.py index 5a667f1b4..139290426 100644 --- a/lib/esbonio/tests/lsp/test_intersphinx.py +++ b/lib/esbonio/tests/lsp/test_intersphinx.py @@ -5,11 +5,9 @@ import py.test from esbonio.lsp.intersphinx import InterSphinx -from esbonio.lsp.testing import ( - completion_test, - role_target_patterns, - intersphinx_target_patterns, -) +from esbonio.lsp.testing import completion_test +from esbonio.lsp.testing import intersphinx_target_patterns +from esbonio.lsp.testing import role_target_patterns @py.test.fixture(scope="session") @@ -33,7 +31,7 @@ def cache(project): rst.logger = logging.getLogger("rst") feature = InterSphinx(rst) - feature.initialized(None) + feature.initialize(None) instances[project] = feature return feature diff --git a/lib/esbonio/tests/lsp/test_roles.py b/lib/esbonio/tests/lsp/test_roles.py index ae6621ea7..b9666b597 100644 --- a/lib/esbonio/tests/lsp/test_roles.py +++ b/lib/esbonio/tests/lsp/test_roles.py @@ -3,11 +3,12 @@ import unittest.mock as mock import py.test - from pygls.lsp.types import CompletionItemKind from esbonio.lsp.roles import Roles -from esbonio.lsp.testing import completion_test, role_target_patterns +from esbonio.lsp.testing import completion_test +from esbonio.lsp.testing import role_patterns +from esbonio.lsp.testing import role_target_patterns C_EXPECTED = {"c:func", "c:macro"} C_UNEXPECTED = {"ref", "doc", "py:func", "py:mod"} @@ -23,55 +24,46 @@ @py.test.mark.parametrize( - "project,text,expected,unexpected", + "text,setup", [ - ("sphinx-default", ":", DEFAULT_EXPECTED, DEFAULT_UNEXPECTED), - ("sphinx-default", ":r", DEFAULT_EXPECTED, DEFAULT_UNEXPECTED), - ("sphinx-default", ":ref:", None, None), - ("sphinx-default", ":py:", None, None), - ("sphinx-default", ":c:", C_EXPECTED, C_UNEXPECTED), - ("sphinx-default", "some text :", DEFAULT_EXPECTED, DEFAULT_UNEXPECTED), - ("sphinx-default", "some text :ref:", None, None), - ("sphinx-default", "some text :py:", None, None), - ("sphinx-default", "some text :c:", C_EXPECTED, C_UNEXPECTED), - ("sphinx-default", " :", DEFAULT_EXPECTED, DEFAULT_UNEXPECTED), - ("sphinx-default", " :r", DEFAULT_EXPECTED, DEFAULT_UNEXPECTED), - ("sphinx-default", " :ref:", None, None), - ("sphinx-default", " :py:", None, None), - ("sphinx-default", " :c:", C_EXPECTED, C_UNEXPECTED), - ("sphinx-default", " some text :", DEFAULT_EXPECTED, DEFAULT_UNEXPECTED), - ("sphinx-default", " some text :ref:", None, None), - ("sphinx-default", " some text :py:", None, None), - ("sphinx-default", " some text :c:", C_EXPECTED, C_UNEXPECTED), - ("sphinx-extensions", ":", EXT_EXPECTED, EXT_UNEXPECTED), - ("sphinx-extensions", ":r", EXT_EXPECTED, EXT_UNEXPECTED), - ("sphinx-extensions", ":ref:", None, None), - ("sphinx-extensions", ":py:", PY_EXPECTED, PY_UNEXPECTED), - ("sphinx-extensions", ":c:", None, None), - ("sphinx-extensions", "some text :", EXT_EXPECTED, EXT_UNEXPECTED), - ("sphinx-extensions", "some text :ref:", None, None), - ("sphinx-extensions", "some text :py:", PY_EXPECTED, PY_UNEXPECTED), - ("sphinx-extensions", "some text :c:", None, None), - ("sphinx-extensions", " :", EXT_EXPECTED, EXT_UNEXPECTED), - ("sphinx-extensions", " :r", EXT_EXPECTED, EXT_UNEXPECTED), - ("sphinx-extensions", " :ref:", None, None), - ("sphinx-extensions", " :py:", PY_EXPECTED, PY_UNEXPECTED), - ("sphinx-extensions", " :c:", None, None), - ("sphinx-extensions", " some text :", EXT_EXPECTED, EXT_UNEXPECTED), - ("sphinx-extensions", " some text :ref:", None, None), - ("sphinx-extensions", " some text :py:", PY_EXPECTED, PY_UNEXPECTED), - ("sphinx-extensions", " some text :c:", None, None), + *itertools.product( + role_patterns(":") + role_patterns(":r"), + [ + ("sphinx-default", DEFAULT_EXPECTED, DEFAULT_UNEXPECTED), + ("sphinx-extensions", EXT_EXPECTED, EXT_UNEXPECTED), + ], + ), + *itertools.product( + role_patterns(":ref:") + role_patterns("a:") + role_patterns("figure::"), + [("sphinx-default", None, None), ("sphinx-extensions", None, None)], + ), + *itertools.product( + role_patterns(":py:"), + [ + ("sphinx-default", None, None), + ("sphinx-extensions", PY_EXPECTED, PY_UNEXPECTED), + ], + ), + *itertools.product( + role_patterns(":c:"), + [ + ("sphinx-default", C_EXPECTED, C_UNEXPECTED), + ("sphinx-extensions", None, None), + ], + ), ], ) -def test_role_completions(sphinx, project, text, expected, unexpected): +def test_role_completions(sphinx, text, setup): """Ensure that we can offer correct role suggestions.""" + project, expected, unexpected = setup + rst = mock.Mock() rst.app = sphinx(project) rst.logger = logging.getLogger("rst") feature = Roles(rst) - feature.initialized(None) + feature.initialize(None) completion_test(feature, text, expected=expected, unexpected=unexpected) @@ -98,7 +90,6 @@ def test_role_completions(sphinx, project, text, expected, unexpected): { "genindex", "modindex", - "py-modindex", "pythagoras_theorem", "search", "welcome", @@ -252,7 +243,7 @@ def test_role_target_completions(sphinx, text, setup): rst.logger = logging.getLogger("rst") feature = Roles(rst) - feature.initialized(None) + feature.initialize(None) completion_test(feature, text, expected=expected, unexpected=unexpected) diff --git a/lib/esbonio/tests/lsp/test_sphinx.py b/lib/esbonio/tests/lsp/test_sphinx.py index 271434f8f..30df27f7f 100644 --- a/lib/esbonio/tests/lsp/test_sphinx.py +++ b/lib/esbonio/tests/lsp/test_sphinx.py @@ -4,18 +4,16 @@ import unittest.mock as mock import py.test - -from pygls.lsp.types import ( - Diagnostic, - DiagnosticSeverity, - DidSaveTextDocumentParams, - Position, - Range, - TextDocumentIdentifier, -) +from pygls.lsp.types import Diagnostic +from pygls.lsp.types import DiagnosticSeverity +from pygls.lsp.types import DidSaveTextDocumentParams +from pygls.lsp.types import Position +from pygls.lsp.types import Range +from pygls.lsp.types import TextDocumentIdentifier from esbonio.lsp import SphinxConfig -from esbonio.lsp.sphinx import DiagnosticList, SphinxManagement +from esbonio.lsp.sphinx import DiagnosticList +from esbonio.lsp.sphinx import SphinxManagement def line(linum: int) -> Range: @@ -221,13 +219,15 @@ def test_default_case(self, rst, testdata): rst.workspace.root_uri = f"file://{sphinx_default}" manager = SphinxManagement(rst) - manager.create_app(SphinxConfig.default()) + manager.create_app(SphinxConfig()) assert rst.app is not None assert rst.app.confdir == str(sphinx_default) assert rst.app.srcdir == str(sphinx_default) assert ".cache/esbonio" in rst.app.outdir - assert rst.app.doctreedir == os.path.join(rst.app.outdir, "doctrees") + assert rst.app.doctreedir == os.path.realpath( + os.path.join(rst.app.outdir, "..", "doctrees") + ) def test_missing_conf(self, rst): """Ensure that if we cannot find a project's conf.py we notify the user.""" @@ -236,7 +236,7 @@ def test_missing_conf(self, rst): rst.workspace.root_uri = f"file://{confdir}" manager = SphinxManagement(rst) - manager.create_app(SphinxConfig.default()) + manager.create_app(SphinxConfig()) assert rst.app is None @@ -250,7 +250,7 @@ def test_conf_dir_option(self, rst, testdata): data_dir = (sphinx_extensions / "..").resolve() rst.workspace.root_uri = f"file://{data_dir}" - config = SphinxConfig(conf_dir=str(sphinx_extensions)) + config = SphinxConfig(confDir=str(sphinx_extensions)) manager = SphinxManagement(rst) manager.create_app(config) @@ -265,7 +265,21 @@ def test_conf_dir_pattern(self, rst, testdata): data_dir = (sphinx_extensions / "..").resolve() rst.workspace.root_uri = f"file://{data_dir}" - config = SphinxConfig(conf_dir="${workspaceRoot}/sphinx-extensions") + config = SphinxConfig(confDir="${workspaceRoot}/sphinx-extensions") + + manager = SphinxManagement(rst) + manager.create_app(config) + + assert rst.app is not None + assert rst.app.confdir == str(sphinx_extensions) + + def test_conf_dir_is_workspace_root(self, rst, testdata): + """Ensure that we can override the conf dir to be the workspace root.""" + + sphinx_extensions = testdata("sphinx-extensions", path_only=True) + rst.workspace.root_uri = f"file://{sphinx_extensions}" + + config = SphinxConfig(confDir="${workspaceRoot}") manager = SphinxManagement(rst) manager.create_app(config) @@ -280,7 +294,7 @@ def test_src_dir_absolute_path(self, rst, testdata): rst.workspace.root_uri = f"file://{sphinx_srcdir}" srcdir = (sphinx_srcdir / "../sphinx-default").resolve() - config = SphinxConfig(src_dir=str(srcdir)) + config = SphinxConfig(srcDir=str(srcdir)) manager = SphinxManagement(rst) manager.create_app(config) @@ -298,7 +312,7 @@ def test_src_dir_workspace_root(self, rst, testdata): srcdir = "${workspaceRoot}/sphinx-default" confdir = "${workspaceRoot}/sphinx-srcdir" - config = SphinxConfig(src_dir=srcdir, conf_dir=confdir) + config = SphinxConfig(srcDir=srcdir, confDir=confdir) manager = SphinxManagement(rst) manager.create_app(config) @@ -307,6 +321,22 @@ def test_src_dir_workspace_root(self, rst, testdata): assert rst.app.confdir == str(datadir / "sphinx-srcdir") assert rst.app.srcdir == str(datadir / "sphinx-default") + def test_src_dir_is_conf_dir(self, rst, testdata): + """Ensure that we can override the src dir to be exactly the conf dir.""" + + sphinx_srcdir = testdata("sphinx-srcdir", path_only=True) + rst.workspace.root_uri = f"file://{sphinx_srcdir}" + + srcdir = "${confDir}" + config = SphinxConfig(srcDir=srcdir) + + manager = SphinxManagement(rst) + manager.create_app(config) + + assert rst.app is not None + assert rst.app.confdir == str(sphinx_srcdir) + assert rst.app.srcdir == str(sphinx_srcdir) + def test_src_dir_conf_dir(self, rst, testdata): """Ensure that we can override the src dir with a path relative to the conf dir.""" @@ -315,7 +345,7 @@ def test_src_dir_conf_dir(self, rst, testdata): rst.workspace.root_uri = f"file://{sphinx_srcdir}" srcdir = "${confDir}/../sphinx-default" - config = SphinxConfig(src_dir=srcdir) + config = SphinxConfig(srcDir=srcdir) manager = SphinxManagement(rst) manager.create_app(config) @@ -327,20 +357,19 @@ def test_src_dir_conf_dir(self, rst, testdata): def test_set_cache_dir(self, rst, testdata): """Ensure that we can override the cache dir if necessary""" - with tempfile.TemporaryDirectory() as cache_dir: + with tempfile.TemporaryDirectory() as build_dir: sphinx_default = testdata("sphinx-default", path_only=True) rst.workspace.root_uri = f"file://{sphinx_default}" - rst.cache_dir = cache_dir manager = SphinxManagement(rst) - manager.create_app(SphinxConfig.default()) + manager.create_app(SphinxConfig(buildDir=build_dir)) assert rst.app is not None assert rst.app.confdir == str(sphinx_default) assert rst.app.srcdir == str(sphinx_default) - assert rst.app.outdir == cache_dir - assert rst.app.doctreedir == os.path.join(rst.app.outdir, "doctrees") + assert rst.app.outdir == os.path.join(build_dir, "html") + assert rst.app.doctreedir == os.path.join(build_dir, "doctrees") def test_sphinx_exception(self, rst, testdata): """Ensure that we correctly handle the case where creating a Sphinx app throws @@ -350,7 +379,7 @@ def test_sphinx_exception(self, rst, testdata): rst.workspace.root_uri = f"file://{sphinx_error}" manager = SphinxManagement(rst) - manager.create_app(SphinxConfig.default()) + manager.create_app(SphinxConfig()) assert rst.app is None diff --git a/resources/images/complete-directive-demo.gif b/resources/images/complete-directive-demo.gif deleted file mode 100644 index 36f692b36..000000000 Binary files a/resources/images/complete-directive-demo.gif and /dev/null differ diff --git a/resources/images/complete-intersphinx-demo.gif b/resources/images/complete-intersphinx-demo.gif deleted file mode 100644 index 9698cbbf1..000000000 Binary files a/resources/images/complete-intersphinx-demo.gif and /dev/null differ diff --git a/resources/images/complete-role-demo.gif b/resources/images/complete-role-demo.gif deleted file mode 100644 index 93f121bbe..000000000 Binary files a/resources/images/complete-role-demo.gif and /dev/null differ diff --git a/resources/images/completion-demo.gif b/resources/images/completion-demo.gif new file mode 100644 index 000000000..27895f83d Binary files /dev/null and b/resources/images/completion-demo.gif differ diff --git a/resources/images/vscode-preview-demo.gif b/resources/images/vscode-preview-demo.gif new file mode 100644 index 000000000..59a18ef58 Binary files /dev/null and b/resources/images/vscode-preview-demo.gif differ diff --git a/resources/io.github.swyddfa.Esbonio.Source.svg b/resources/io.github.swyddfa.Esbonio.Source.svg new file mode 100644 index 000000000..9cbf2da50 --- /dev/null +++ b/resources/io.github.swyddfa.Esbonio.Source.svg @@ -0,0 +1,3521 @@ + + + Adwaita Icon Template + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + GNOME Design Team + + + + + Adwaita Icon Template + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Hicolor + Symbolic + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + battery is full and there is no a/c connected. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + E + 1 + + + completion-window + + + + + sbonioxplain + + + + + + + + + + + + + + + + + + + E + 1 + + + completion-window + + + + + sbonioxplain + + + + + + + + + + + + + + + + + + + E + 1 + + + completion-window + + + + + sbonioxplain + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/io.github.swyddfa.Esbonio.png b/resources/io.github.swyddfa.Esbonio.png new file mode 100644 index 000000000..008694419 Binary files /dev/null and b/resources/io.github.swyddfa.Esbonio.png differ diff --git a/resources/io.github.swyddfa.Esbonio.svg b/resources/io.github.swyddfa.Esbonio.svg new file mode 100644 index 000000000..99add5a2a --- /dev/null +++ b/resources/io.github.swyddfa.Esbonio.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +