For any code literal blocks inside of a qmp-example directive, apply and enforce the QMP lexer/highlighter to those blocks.
This way, you won't need to write: ``` .. qmp-example:: :annotated: Blah blah .. code-block:: QMP -> { "lorem": "ipsum" } ``` But instead, simply: ``` .. qmp-example:: :annotated: Blah blah:: -> { "lorem": "ipsum" } ``` Once the directive block is exited, whatever the previous default highlight language was will be restored; localizing the forced QMP lexing to exclusively this directive. Signed-off-by: John Snow <js...@redhat.com> --- docs/sphinx/qapidoc.py | 53 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py index a2fa05fc491..c8c404ad1b0 100644 --- a/docs/sphinx/qapidoc.py +++ b/docs/sphinx/qapidoc.py @@ -26,6 +26,7 @@ import os import re +import sys import textwrap from typing import List @@ -37,6 +38,7 @@ from qapi.schema import QAPISchema import sphinx +from sphinx import addnodes from sphinx.directives.code import CodeBlock from sphinx.errors import ExtensionError from sphinx.util.docutils import SphinxDirective @@ -575,10 +577,10 @@ class QMPExample(CodeBlock, NestedDirective): Custom admonition for QMP code examples. When the :annotated: option is present, the body of this directive - is parsed as normal rST instead. Code blocks must be explicitly - written by the user, but this allows for intermingling explanatory - paragraphs with arbitrary rST syntax and code blocks for more - involved examples. + is parsed as normal rST, but with any '::' code blocks set to use + the QMP lexer. Code blocks must be explicitly written by the user, + but this allows for intermingling explanatory paragraphs with + arbitrary rST syntax and code blocks for more involved examples. When :annotated: is absent, the directive body is treated as a simple standalone QMP code block literal. @@ -592,6 +594,33 @@ class QMPExample(CodeBlock, NestedDirective): "title": directives.unchanged, } + def _highlightlang(self) -> addnodes.highlightlang: + """Return the current highlightlang setting for the document""" + node = None + doc = self.state.document + + if hasattr(doc, "findall"): + # docutils >= 0.18.1 + for node in doc.findall(addnodes.highlightlang): + pass + else: + for elem in doc.traverse(): + if isinstance(elem, addnodes.highlightlang): + node = elem + + if node: + return node + + # No explicit directive found, use defaults + node = addnodes.highlightlang( + lang=self.env.config.highlight_language, + force=False, + # Yes, Sphinx uses this value to effectively disable line + # numbers and not 0 or None or -1 or something. ¯\_(ツ)_/¯ + linenothreshold=sys.maxsize, + ) + return node + def admonition_wrap(self, *content) -> List[nodes.Node]: title = "Example:" if "title" in self.options: @@ -606,8 +635,24 @@ def admonition_wrap(self, *content) -> List[nodes.Node]: return [admon] def run_annotated(self) -> List[nodes.Node]: + lang_node = self._highlightlang() + content_node: nodes.Element = nodes.section() + + # Configure QMP highlighting for "::" blocks, if needed + if lang_node["lang"] != "QMP": + content_node += addnodes.highlightlang( + lang="QMP", + force=False, # "True" ignores lexing errors + linenothreshold=lang_node["linenothreshold"], + ) + self.do_parse(self.content, content_node) + + # Restore prior language highlighting, if needed + if lang_node["lang"] != "QMP": + content_node += addnodes.highlightlang(**lang_node.attributes) + return content_node.children def run(self) -> List[nodes.Node]: -- 2.45.0