Skip to content

Commit

Permalink
Merge pull request #760 from 2bndy5/fix-clode-block-highlighting
Browse files Browse the repository at this point in the history
Fix code block highlighting
  • Loading branch information
michaeljones authored Feb 9, 2022
2 parents 4f502ea + 9f20a1c commit 0d32ec9
Show file tree
Hide file tree
Showing 12 changed files with 242 additions and 13 deletions.
21 changes: 21 additions & 0 deletions breathe/filetypes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from typing import Optional
import os.path

import pygments # type: ignore


def get_pygments_alias(filename: str) -> Optional[str]:
"Find first pygments alias from filename"
try:
lexer_cls = pygments.lexers.get_lexer_for_filename(filename)
return lexer_cls.aliases[0]
except pygments.util.ClassNotFound:
return None


def get_extension(filename: str) -> str:
"Get extension from filename"
# If the filename is just '.ext' then we get ('.ext', '') so we fall back to first part if
# the second isn't there
(first, second) = os.path.splitext(filename)
return (second or first).lstrip(".")
8 changes: 4 additions & 4 deletions breathe/parser/compound.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,15 @@ def __init__(self, kind=None, prot=None, id=None, compoundname='', title='',
innerclass=None, innernamespace=None, innerpage=None, innergroup=None,
templateparamlist=None, sectiondef=None, briefdescription=None,
detaileddescription=None, inheritancegraph=None, collaborationgraph=None,
programlisting=None, location=None, listofallmembers=None):
programlisting=None, location=None, listofallmembers=None, language=None):

supermod.compounddefType.__init__(self, kind, prot, id, compoundname, title,
basecompoundref, derivedcompoundref, includes, includedby,
incdepgraph, invincdepgraph, innerdir, innerfile,
innerclass, innernamespace, innerpage, innergroup,
templateparamlist, sectiondef, briefdescription,
detaileddescription, inheritancegraph, collaborationgraph,
programlisting, location, listofallmembers)
programlisting, location, listofallmembers, language)


supermod.compounddefType.subclass = compounddefTypeSub
Expand Down Expand Up @@ -378,8 +378,8 @@ class listingTypeSub(supermod.listingType):

node_type = "listing"

def __init__(self, codeline=None):
supermod.listingType.__init__(self, codeline)
def __init__(self, codeline=None, domain=None):
supermod.listingType.__init__(self, codeline, domain)


supermod.listingType.subclass = listingTypeSub
Expand Down
21 changes: 15 additions & 6 deletions breathe/parser/compoundsuper.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
#

import sys
import os
import getopt
from xml.dom import minidom
from xml.dom import Node

from .. import filetypes
#
# User methods
#
Expand Down Expand Up @@ -193,10 +195,11 @@ def buildChildren(self, child_, nodeName_):
class compounddefType(GeneratedsSuper):
subclass = None
superclass = None
def __init__(self, kind=None, prot=None, id=None, compoundname=None, title=None, basecompoundref=None, derivedcompoundref=None, includes=None, includedby=None, incdepgraph=None, invincdepgraph=None, innerdir=None, innerfile=None, innerclass=None, innernamespace=None, innerpage=None, innergroup=None, templateparamlist=None, sectiondef=None, briefdescription=None, detaileddescription=None, inheritancegraph=None, collaborationgraph=None, programlisting=None, location=None, listofallmembers=None):
def __init__(self, kind=None, prot=None, id=None, compoundname=None, title=None, basecompoundref=None, derivedcompoundref=None, includes=None, includedby=None, incdepgraph=None, invincdepgraph=None, innerdir=None, innerfile=None, innerclass=None, innernamespace=None, innerpage=None, innergroup=None, templateparamlist=None, sectiondef=None, briefdescription=None, detaileddescription=None, inheritancegraph=None, collaborationgraph=None, programlisting=None, location=None, listofallmembers=None, language=None):
self.kind = kind
self.prot = prot
self.id = id
self.language = language
self.compoundname = compoundname
self.title = title
if basecompoundref is None:
Expand Down Expand Up @@ -376,6 +379,8 @@ def buildAttributes(self, attrs):
self.prot = attrs.get('prot').value
if attrs.get('id'):
self.id = attrs.get('id').value
if attrs.get('language'):
self.language = attrs.get('language').value.lower()
def buildChildren(self, child_, nodeName_):
if child_.nodeType == Node.ELEMENT_NODE and \
nodeName_ == 'compoundname':
Expand Down Expand Up @@ -482,7 +487,7 @@ def buildChildren(self, child_, nodeName_):
self.set_collaborationgraph(obj_)
elif child_.nodeType == Node.ELEMENT_NODE and \
nodeName_ == 'programlisting':
obj_ = listingType.factory()
obj_ = listingType.factory(domain=self.language)
obj_.build(child_)
self.set_programlisting(obj_)
elif child_.nodeType == Node.ELEMENT_NODE and \
Expand Down Expand Up @@ -2398,7 +2403,8 @@ def buildChildren(self, child_, nodeName_):
class listingType(GeneratedsSuper):
subclass = None
superclass = None
def __init__(self, codeline=None):
def __init__(self, codeline=None, domain: str=None):
self.domain = domain
if codeline is None:
self.codeline = []
else:
Expand Down Expand Up @@ -2436,14 +2442,17 @@ def hasContent_(self):
return True
else:
return False
def build(self, node_):
def build(self, node_: minidom.Element):
attrs = node_.attributes
self.buildAttributes(attrs)
for child_ in node_.childNodes:
nodeName_ = child_.nodeName.split(':')[-1]
self.buildChildren(child_, nodeName_)
def buildAttributes(self, attrs):
pass
def buildAttributes(self, attrs: minidom.NamedNodeMap):
if "filename" in attrs:
# extract the domain for this programlisting tag.
filename = attrs["filename"].value
self.domain = filetypes.get_pygments_alias(filename) or filetypes.get_extension(filename)
def buildChildren(self, child_, nodeName_):
if child_.nodeType == Node.ELEMENT_NODE and \
nodeName_ == 'codeline':
Expand Down
4 changes: 3 additions & 1 deletion breathe/renderer/sphinxrenderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -1604,14 +1604,16 @@ def visit_docformula(self, node) -> List[Node]:
def visit_listing(self, node) -> List[Node]:
nodelist: List[Node] = []
for i, item in enumerate(node.codeline):
# Put new lines between the lines. There must be a more pythonic way of doing this
# Put new lines between the lines
if i:
nodelist.append(nodes.Text("\n"))
nodelist.extend(self.render(item))

# Add blank string at the start otherwise for some reason it renders
# the pending_xref tags around the kind in plain text
block = nodes.literal_block("", "", *nodelist)
if node.domain:
block["language"] = node.domain
return [block]

def visit_codeline(self, node) -> List[Node]:
Expand Down
132 changes: 132 additions & 0 deletions documentation/source/codeblocks.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@

Code Blocks
===========

Breathe supports rendering code blocks with syntax highlighting provided by the
`Pygments <https://pygments.org/>`_ library. By default, Breathe will assume
that code blocks match the language of the source file, but you can also specify
the language of the code blocks using
`Doxygen's code command <https://www.doxygen.nl/manual/commands.html#cmdcode>`_
or `MarkDown's fenced code blocks <https://www.doxygen.nl/manual/markdown.html#md_fenced>`_.

.. note::
Any hyperlinked text found within the code blocks rendered with Doxygen's HTML output
will not be hyperlinked in any Sphinx output due to the use of the Pygments library.
As a benefit, a code-block's syntax highlighting can be any syntax supported by
Pygments (which is much more than only the languages supported by Doxygen's parsers).

The Doxygen syntax for code blocks supports specifying the language as follows:

.. code-block::
\code{.py}
class Python:
pass
\endcode
@code{.cpp}
class Cpp {};
@endcode
This technique can also be utilized from MarkDown syntax/files

.. code-block:: markdown
```py
class Python:
pass
```
```cpp
class Cpp {};
```
Breathe will pass the language specified to Pygments to get accurate
highlighting. If no language is explicitly provided (either from ``\code``
command or via Doxygen's XML output about the language documented), then
Pygments will try to guess what syntax the code block is using (based on
the code block's contents).

Examples
--------

The following should render with standard C/C++ highlighting. Notice, the
syntax is automatically highlighted as C++ because the documented function
exists in a C++ source file.

----

.. code-block:: cpp
/** A function with an unannotated code block with C/C++ code.
*
* @code
* char *buffer = new char[42];
* int charsAdded = sprintf(buffer, "Tabs are normally %d spaces\n", 8);
* @endcode
*/
void with_standard_code_block();
----

.. doxygenfunction:: with_standard_code_block
:path: ../../examples/specific/code_blocks/xml
:no-link:

----

The following should render with no detected highlighting.
Notice, there is no syntax highlighting because Pygments does not
recognize the code block's contained syntax as a C++ snippet.

----

.. code-block:: cpp
/** A function with an unannotated code block with non-C/C++ code.
*
* @code
* set(user_list A B C)
* foreach(element ${user_list})
* message(STATUS "Element is ${element}")
* endforeach()
* @endcode
*/
void with_unannotated_cmake_code_block();
----

.. doxygenfunction:: with_unannotated_cmake_code_block
:path: ../../examples/specific/code_blocks/xml
:no-link:

----

The following should render with specified CMake highlighting. Here, the syntax
highlighting is explicitly recognized as a CMake script snippet which overrides
the inherent C++ context.

----

.. code-block:: cpp
/** A function with an annotated cmake code block.
*
* @code{.cmake}
* set(user_list A B C)
* foreach(element ${user_list})
* message(STATUS "Element is ${element}")
* endforeach()
* @endcode
*/
void with_annotated_cmake_code_block();
----

.. doxygenfunction:: with_annotated_cmake_code_block
:path: ../../examples/specific/code_blocks/xml
:no-link:

.. warning::
Pygments will raise a warning in the Sphinx build logs if
the specified syntax does conform the specified syntax's convention(s).
2 changes: 1 addition & 1 deletion documentation/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@

# General information about the project.
project = "Breathe"
copyright = "2009-2014, Michael Jones"
copyright = "2009-2022, Michael Jones"


# The language for content autogenerated by Sphinx. Refer to documentation
Expand Down
21 changes: 21 additions & 0 deletions documentation/source/examples/codeblocks.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

Examples: Code Blocks
=====================

The following should render with standard C/C++ highlighting.

.. doxygenfunction:: with_standard_code_block
:path: ../../examples/specific/code_blocks/xml
:no-link:

The following should render with no detected highlighting.

.. doxygenfunction:: with_unannotated_cmake_code_block
:path: ../../examples/specific/code_blocks/xml
:no-link:

The following should render with specified cmake highlighting.

.. doxygenfunction:: with_annotated_cmake_code_block
:path: ../../examples/specific/code_blocks/xml
:no-link:
1 change: 1 addition & 0 deletions documentation/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ Features

markups
latexmath
codeblocks
domains
customcss
groups
Expand Down
1 change: 1 addition & 0 deletions documentation/source/testpages.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ Test Pages
embeddedrst
inline
members
examples/codeblocks

2 changes: 1 addition & 1 deletion examples/specific/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ projects = nutshell alias rst inline namespacefile array inheritance \
cpp_inherited_members cpp_trailing_return_type cpp_constexpr_hax \
cpp_function_lookup \
c_file c_struct c_enum c_typedef c_macro c_union membergroups \
simplesect
simplesect code_blocks

special = programlisting decl_impl multifilexml auto class typedef

Expand Down
11 changes: 11 additions & 0 deletions examples/specific/code_blocks.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
PROJECT_NAME = "Code Blocks"
OUTPUT_DIRECTORY = code_blocks
GENERATE_LATEX = NO
GENERATE_MAN = NO
GENERATE_RTF = NO
CASE_SENSE_NAMES = NO
INPUT = code_blocks.h
QUIET = YES
JAVADOC_AUTOBRIEF = YES
GENERATE_HTML = NO
GENERATE_XML = YES
31 changes: 31 additions & 0 deletions examples/specific/code_blocks.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

/** A function with an unannotated code block with C/C++ code.
*
* @code
* char* buffer = new char[42];
* int charsAdded = sprintf(buffer, "Tabs are normally %d spaces\n", 8);
* @endcode
*/
void with_standard_code_block();

/** A function with an unannotated code block with non-C/C++ code.
*
* @code
* set(user_list A B C)
* foreach(element ${user_list})
* message(STATUS "Element is ${element}")
* endforeach()
* @endcode
*/
void with_unannotated_cmake_code_block();

/** A function with an annotated cmake code block.
*
* @code{.cmake}
* set(user_list A B C)
* foreach(element ${user_list})
* message(STATUS "Element is ${element}")
* endforeach()
* @endcode
*/
void with_annotated_cmake_code_block();

0 comments on commit 0d32ec9

Please sign in to comment.