docs/qapidoc: add QMP highlighting to annotated qmp-example blocks

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.

Note, if the default language is *already* QMP, this directive will not
generate and restore redundant highlight configuration nodes. We may
well decide that the default language ought to be QMP for any QAPI
reference pages, but this way the directive behaves consistently no
matter where it is used.

Signed-off-by: John Snow <jsnow@redhat.com>
Acked-by: Markus Armbruster <armbru@redhat.com>
Message-ID: <20240717021312.606116-5-jsnow@redhat.com>
Signed-off-by: Markus Armbruster <armbru@redhat.com>
This commit is contained in:
John Snow 2024-07-16 22:13:06 -04:00 committed by Markus Armbruster
parent 547864f9d4
commit 76e375fc3c

View File

@ -26,6 +26,7 @@ https://www.sphinx-doc.org/en/master/development/index.html
import os
import re
import sys
import textwrap
from typing import List
@ -36,6 +37,7 @@ from qapi.error import QAPIError, QAPISemError
from qapi.gen import QAPISchemaVisitor
from qapi.schema import QAPISchema
from sphinx import addnodes
from sphinx.directives.code import CodeBlock
from sphinx.errors import ExtensionError
from sphinx.util.docutils import switch_source_input
@ -545,10 +547,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.
@ -562,6 +564,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:
@ -576,8 +605,24 @@ class QMPExample(CodeBlock, NestedDirective):
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]: