-- ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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 @@
+
+
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 @@
+
+