diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 10a21355..485cf848 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,7 +19,7 @@ repos: args: [--config=lib/esbonio/setup.cfg] - repo: https://github.com/asottile/reorder_python_imports - rev: v3.0.1 + rev: v3.1.0 hooks: - id: reorder-python-imports args: [--application-directories=lib:esbonio] diff --git a/.vscode/settings.json b/.vscode/settings.json index 5b6d0261..eccda87c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,7 +7,6 @@ }, "files.associations": { "setup.cfg": "ini", - "pyproject.toml": "ini" }, "files.exclude": { "**/.git": true, diff --git a/docs/conf.py b/docs/conf.py index deee1247..68dfa384 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -129,8 +129,6 @@ def lsp_role(name, rawtext, text, lineno, inliner, options={}, content=[]): def setup(app: Sphinx): app.add_role("lsp", lsp_role) - # So that it's possible to use intersphinx to link to configuration options - # in sphinx. app.add_object_type( "confval", "confval", @@ -138,6 +136,20 @@ def setup(app: Sphinx): indextemplate="pair: %s; configuration value", ) + app.add_object_type( + "startmod", + "startmod", + objname="startup module", + indextemplate="pair: %s; startup module", + ) + + app.add_object_type( + "extmod", + "extmod", + objname="extension module", + indextemplate="pair: %s; startup module", + ) + # So that it's possible to use intersphinx to link to IPython magics app.add_object_type( "magic", diff --git a/docs/ext/cli_help.py b/docs/ext/cli_help.py index aff7d479..6846b8cf 100644 --- a/docs/ext/cli_help.py +++ b/docs/ext/cli_help.py @@ -1,3 +1,4 @@ +import argparse import importlib from docutils import nodes @@ -13,13 +14,13 @@ def run(self): name = self.arguments[0] mod = importlib.import_module(name) - if not hasattr(mod, "cli"): - return [] - - cli = mod.cli - if not hasattr(cli, "format_help"): + candidates = [ + v for v in mod.__dict__.values() if isinstance(v, argparse.ArgumentParser) + ] + if len(candidates) == 0: return [] + cli = candidates[0] return [nodes.literal_block("", cli.format_help(), language="none")] diff --git a/docs/images/vscode-screenshot.png b/docs/images/vscode-screenshot.png index 2c285136..819ec6d4 100644 Binary files a/docs/images/vscode-screenshot.png and b/docs/images/vscode-screenshot.png differ diff --git a/docs/index.rst b/docs/index.rst index b620d352..04cba903 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -74,6 +74,7 @@ Here is a quick summary of the features implemented by the language server. lsp/getting-started lsp/advanced-usage lsp/extending + lsp/how-to changelog Sphinx Extensions diff --git a/docs/lsp/advanced-usage.rst b/docs/lsp/advanced-usage.rst index a6a8ff7a..3cb9c4c4 100644 --- a/docs/lsp/advanced-usage.rst +++ b/docs/lsp/advanced-usage.rst @@ -16,46 +16,40 @@ the section on :doc:`/lsp/extending` if you want to know more) However, all that we need to know for the moment is the concept of startup modules. +.. _lsp-startup-mods: + Startup Modules --------------- A startup module is any python module (or script) that results in a running language server. The following startup modules are included with the ``esbonio`` python package. -.. relevant-to:: esbonio - :category: Startup Module +.. startmod:: esbonio The default startup module you are probably already familiar with. - It is in fact just an alias for the ``esbonio.lsp.sphinx`` startup module. + It is in fact just an alias for the :startmod:`esbonio.lsp.sphinx` startup module. - .. cli-help:: esbonio.__main__ + .. .. cli-help:: esbonio.__main__ -.. relevant-to:: esbonio.lsp.rst - :category: Startup Module +.. startmod:: esbonio.lsp.rst A "vanilla" reStructuedText language server for use with docutils projects. - .. cli-help:: esbonio.lsp.rst + .. .. cli-help:: esbonio.lsp.rst -.. relevant-to:: esbonio.lsp.sphinx - :category: Startup Module +.. startmod:: esbonio.lsp.sphinx A language server tailored for use with Sphinx projects. - .. cli-help:: esbonio.lsp.sphinx + .. .. cli-help:: esbonio.lsp.sphinx -Note that, the command line interfaces for these servers is identical and the only difference -between them is the module name passed to ``python -m``. -Modules -------- +Extension Modules +----------------- -Inspired by the way Sphinx extensions work, functionality is added to ``esbonio`` servers through -lists of python modules with each module contributing some features. The ``--include`` and -``--exclude`` arguments command line arguments allow you to add additional modules or remove any -that you don't need or find problematic. +Inspired by the way Sphinx extensions work, functionality is added to a language server through a list of python modules with each module contributing some features. -Below is the list of modules included by default for each of the provided startup modules. +Below is the list of modules loaded by default for each of the provided servers. .. relevant-to:: esbonio :category: Startup Module @@ -84,7 +78,10 @@ Below is the list of modules included by default for each of the provided startu In addition to the modules enabled by default, the following modules are provided and can be enabled if you wish. -``esbonio.lsp.spelling`` (Experimental) +.. extmod:: esbonio.lsp.spelling + + **Experimental** + Basic spell checking, with errors reported as diagnostics and corrections suggested as code actions. Currently only available for English and can be confused by reStructuredText syntax. diff --git a/docs/lsp/extending.rst b/docs/lsp/extending.rst index 32e9974b..6b97447a 100644 --- a/docs/lsp/extending.rst +++ b/docs/lsp/extending.rst @@ -32,7 +32,7 @@ Architecture Language Feature Language features are subclasses of :class:`~esbonio.lsp.rst.LanguageFeature`. - They are typically based on a single aspect of reStructuredText (e.g. :class:`~esbonio.lsp.roles.Roles`) or Sphinx (e.g. :class:`` responsible for providing + They are typically based on a single aspect of reStructuredText (e.g. :class:`~esbonio.lsp.roles.Roles`). Language Features (where it makes sense) should be server agnostic, that way the same features can be reused across different envrionments. @@ -52,7 +52,7 @@ Architecture There is nothing in Esbonio that would prevent you from writing your own if you so desired. - Module + Extension Module Ordinary Python modules are used to group related functionality together. Taking inspiration from how Sphinx is architected, language servers are assembled by passing the list of modules to load to the :func:`~esbonio.lsp.create_language_server`. This assembly process calls any functions with the name ``esbonio_setup`` allowing for ``LanguageFeatures`` to be configured and loaded into the server. diff --git a/docs/lsp/getting-started.rst b/docs/lsp/getting-started.rst index 1c5a8e33..70afb42c 100644 --- a/docs/lsp/getting-started.rst +++ b/docs/lsp/getting-started.rst @@ -6,15 +6,14 @@ Getting Started This section contains notes on how to use the Language Server with your text editor of choice. - -.. relevant-to:: VSCode +.. relevant-to:: VSCode (Esbonio) :category: Editor .. figure:: /images/vscode-screenshot.png :align: center :target: /_images/vscode-screenshot.png - The VSCode extension in action. + The Esbonio VSCode extension. .. relevant-to:: Emacs (eglot) :category: Editor @@ -101,7 +100,7 @@ building your documentation e.g. Otherwise the language server will fail to properly understand your project. -.. relevant-to:: VSCode +.. relevant-to:: VSCode (Esbonio) :category: Editor Integration with `VSCode`_ is provided by the `Esbonio`_ extension. @@ -139,7 +138,7 @@ Otherwise the language server will fail to properly understand your project. Configuration ------------- -.. relevant-to:: VSCode +.. relevant-to:: VSCode (Esbonio) :category: Editor .. include:: ./editors/vscode/_configuration.rst @@ -171,10 +170,8 @@ Configuration .. confval:: sphinx.buildDir (string) - By default the language server will choose a cache directory (as determined by - `appdirs `_) to put Sphinx's build output. - This option can be used to force the language server to use a location - of your choosing, currently accepted values include: + By default the language server will choose a cache directory (as determined by `appdirs `_) to put Sphinx's build output. + This option can be used to force the language server to use a location of your choosing, currently accepted values include: - ``/path/to/src/`` - An absolute path - ``${workspaceRoot}/docs/src`` - A path relative to the root of your workspace @@ -183,10 +180,9 @@ Configuration .. confval:: sphinx.confDir (string) - The language server attempts to automatically find the folder which contains your - project's ``conf.py``. If necessary this can be used to override the default discovery - mechanism and force the server to use a folder of your choosing. Currently accepted - values include: + The language server attempts to automatically find the folder which contains your project's ``conf.py``. + If necessary this can be used to override the default discovery mechanism and force the server to use a folder of your choosing. + Currently accepted values include: - ``/path/to/docs`` - An absolute path - ``${workspaceRoot}/docs`` - A path relative to the root of your workspace. @@ -194,10 +190,9 @@ Configuration .. confval:: sphinx.srcDir (string) - The language server assumes that your project's ``srcDir`` (the folder containing your - rst files) is the same as your projects's ``confDir``. If this assumption is not true, - you can use this setting to tell the server where to look. Currently accepted values - include: + The language server assumes that your project's ``srcDir`` (the folder containing your rst files) is the same as your projects's ``confDir``. + If this assumption is not true, 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 @@ -212,12 +207,13 @@ Configuration Controls the number of parallel jobs used during a Sphinx build. - The default value of ``"auto"`` will behave the same as passing ``-j auto`` to a ``sphinx-build`` command. Setting this value to ``1`` effectively disables parallel builds. + The default value of ``"auto"`` will behave the same as passing ``-j auto`` to a ``sphinx-build`` command. + Setting this value to ``1`` effectively disables parallel builds. .. confval:: 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. + 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`` @@ -225,8 +221,8 @@ Configuration .. confval:: 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. + 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. .. confval:: server.hideSphinxOutput (boolean) @@ -267,9 +263,7 @@ to help get you started. Debugging --------- -In the event that something does not work as expected, you can increase the -logging level of the server by setting the ``server.logLevel`` initialization option -to ``debug``. +In the event that something does not work as expected, you can increase the logging level of the server by setting the :confval:`server.logLevel (string)` option to ``debug``. .. relevant-to:: Neovim (lspconfig) :category: Editor @@ -289,7 +283,7 @@ to ``debug``. Additional Details ------------------ -.. relevant-to:: VSCode +.. relevant-to:: VSCode (Esbonio) :category: Editor diff --git a/docs/lsp/how-to.rst b/docs/lsp/how-to.rst new file mode 100644 index 00000000..426b2fb1 --- /dev/null +++ b/docs/lsp/how-to.rst @@ -0,0 +1,7 @@ +How To +====== + +.. toctree:: + :glob: + + how-to/* diff --git a/docs/lsp/how-to/get-debug-info.rst b/docs/lsp/how-to/get-debug-info.rst new file mode 100644 index 00000000..c9765079 --- /dev/null +++ b/docs/lsp/how-to/get-debug-info.rst @@ -0,0 +1,38 @@ +How To: Get Debug Information +============================= + +In its default configuration the language server doesn't give you much information, you get Sphinx's build output and not much else. +Depending on your needs you may find one of the following options useful. + +Enable Debug Logging +-------------------- + +The simplest way to get more information is to set the :confval:`server.logLevel (string)` option to ``debug``. +Additional messages from the language server will be sent to your language client as :lsp:`window/logMessage` messages. + +Capture All Messages +-------------------- + +If you are using one of the VSCode extensions you can set the ``esbonio.trace.server`` option to ``verbose``. +This will print all LSP message bodies sent to/from the client in the ``Output`` window. + +**Note:** This will generate a *lot* of output. + +Capture All Output +------------------ + +.. important:: + + This option requires the ``lsp-devtools`` package be installed in the same Python environment as the ``esbonio`` language server:: + + $ pip install lsp-devtools + +Alternatively you can capture **everything** sent to/from the language server in a text file ``lsp.log`` by using one of the following debug :ref:`lsp-startup-mods` + +.. startmod:: esbonio.lsp.rst._record + + Exactly the same as :startmod:`esbonio.lsp.rst`, but with output capture enabled. + +.. startmod:: esbonio.lsp.sphinx._record + + Exaclty the same as :startmod:`esbonio.lsp.sphinx` but with output capture enabled. diff --git a/lib/esbonio/CHANGES.rst b/lib/esbonio/CHANGES.rst index 44ebd691..405f1e59 100644 --- a/lib/esbonio/CHANGES.rst +++ b/lib/esbonio/CHANGES.rst @@ -19,6 +19,11 @@ Features The server also supports resolving links for directive arguments with initial support for ``.. image::``, ``.. figure::``, ``.. include::`` and ``.. literalinclude::`` directives. (`#294 `_) +Enhancements +^^^^^^^^^^^^ + +- Language clients can now control if the server forces a full build of a Sphinx project on startup by providing a ``sphinx.forceFullBuild`` initialization option, which defaults to ``true`` (`#358 `_) +- Language clients can now control the number of parallel jobs by providing a ``sphinx.numJobs`` initialization option, which defaults to ``auto``. Clients can disable parallel builds by setting this option to ``1`` (`#359 `_) Fixes ^^^^^ diff --git a/lib/esbonio/changes/380.enhancement.rst b/lib/esbonio/changes/380.enhancement.rst new file mode 100644 index 00000000..d721efc7 --- /dev/null +++ b/lib/esbonio/changes/380.enhancement.rst @@ -0,0 +1,2 @@ +Add ``esbonio.lsp.rst._record`` and ``esbonio.lsp.sphinx._record`` startup modules. +These can be used to record all LSP client-sever communication to a text file. diff --git a/lib/esbonio/changes/381.fix.rst b/lib/esbonio/changes/381.fix.rst new file mode 100644 index 00000000..f2619cc5 --- /dev/null +++ b/lib/esbonio/changes/381.fix.rst @@ -0,0 +1 @@ +The language server now detects functionality bundled with standard Sphinx extensions diff --git a/lib/esbonio/esbonio/lsp/rst/_record.py b/lib/esbonio/esbonio/lsp/rst/_record.py new file mode 100644 index 00000000..c68946de --- /dev/null +++ b/lib/esbonio/esbonio/lsp/rst/_record.py @@ -0,0 +1,17 @@ +"""Startup module that launches the real server in a sub process and dumps all messages +to a file.""" +import sys +from argparse import Namespace + +from lsp_devtools.cmds.record import record + + +def main(): + args = Namespace(file="lsp.log", format="%(message)s", raw=True) + cmd = [sys.executable, "-m", "esbonio.lsp.rst"] + sys.argv[1:] + + record(args, cmd) + + +if __name__ == "__main__": + main() diff --git a/lib/esbonio/esbonio/lsp/sphinx/__init__.py b/lib/esbonio/esbonio/lsp/sphinx/__init__.py index bcd79a6b..ff8c35b0 100644 --- a/lib/esbonio/esbonio/lsp/sphinx/__init__.py +++ b/lib/esbonio/esbonio/lsp/sphinx/__init__.py @@ -619,7 +619,7 @@ def _load_sphinx_extensions(self, app: Sphinx): if name in self._loaded_modules: self.logger.debug("Skipping previously loaded module '%s'", name) - if not hasattr(ext, "esbonio_setup"): + if not hasattr(mod, "esbonio_setup"): continue self.logger.debug("Loading sphinx module '%s'", name) @@ -1013,7 +1013,7 @@ def exception_to_diagnostic(exc: BaseException): tb = exc.__traceback__ frame = traceback.extract_tb(tb)[-1] path = pathlib.Path(frame.filename) - line = frame.lineno - 1 + line = (frame.lineno or 1) - 1 message = type(exc).__name__ if exc.args.count == 0 else exc.args[0] diff --git a/lib/esbonio/esbonio/lsp/sphinx/_record.py b/lib/esbonio/esbonio/lsp/sphinx/_record.py new file mode 100644 index 00000000..109ece08 --- /dev/null +++ b/lib/esbonio/esbonio/lsp/sphinx/_record.py @@ -0,0 +1,17 @@ +"""Startup module that launches the real server in a sub process and dumps all comms +to a file.""" +import sys +from argparse import Namespace + +from lsp_devtools.cmds.record import record + + +def main(): + args = Namespace(file="lsp.log", format="%(message)s", raw=True) + cmd = [sys.executable, "-m", "esbonio.lsp.sphinx"] + sys.argv[1:] + + record(args, cmd) + + +if __name__ == "__main__": + main() diff --git a/lib/esbonio/pyproject.toml b/lib/esbonio/pyproject.toml index ff329063..80b68602 100644 --- a/lib/esbonio/pyproject.toml +++ b/lib/esbonio/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" asyncio_mode = "auto" filterwarnings = [ "ignore:'contextfunction' is renamed to 'pass_context',*:DeprecationWarning", - "ignore:'environmentfilter' is renamed to 'pass_environment',*:DeprecationWarning" + "ignore:'environmentfilter' is renamed to 'pass_environment',*:DeprecationWarning", ] [tool.towncrier] @@ -17,34 +17,39 @@ issue_format = "`#{issue} `_" underlines = ["-", "^", "\""] [[tool.towncrier.type]] - directory = "feature" - name = "Features" - showcontent = true +directory = "feature" +name = "Features" +showcontent = true [[tool.towncrier.type]] - directory = "fix" - name = "Fixes" - showcontent = true +directory = "enhancement" +name = "Enhancements" +showcontent = true [[tool.towncrier.type]] - directory = "doc" - name = "Docs" - showcontent = true +directory = "fix" +name = "Fixes" +showcontent = true [[tool.towncrier.type]] - directory = "breaking" - name = "Breaking Changes" - showcontent = true +directory = "doc" +name = "Docs" +showcontent = true [[tool.towncrier.type]] - directory = "deprecated" - name = "Deprecated" - showcontent = true +directory = "breaking" +name = "Breaking Changes" +showcontent = true [[tool.towncrier.type]] - directory = "misc" - name = "Misc" - showcontent = true +directory = "deprecated" +name = "Deprecated" +showcontent = true + +[[tool.towncrier.type]] +directory = "misc" +name = "Misc" +showcontent = true [tool.tox] legacy_tox_ini = """ diff --git a/lib/esbonio/setup.cfg b/lib/esbonio/setup.cfg index 8e749032..e1f4acbf 100644 --- a/lib/esbonio/setup.cfg +++ b/lib/esbonio/setup.cfg @@ -46,7 +46,21 @@ console_scripts = esbonio = esbonio.__main__:main [options.extras_require] -dev = black ; mypy ; flake8 ; pre-commit ; pytest ; pytest-lsp ; pytest-cov ; pytest-timeout ; tox ; types-appdirs ; types-docutils ; types-pygments +debug = lsp-devtools +dev = + black + mypy + flake8 + lsp-devtools + pre-commit + pytest + pytest-lsp + pytest-cov + pytest-timeout + tox + types-appdirs + types-docutils + types-pygments [flake8] max-line-length = 88