diff --git a/sphinx/directives/code.py b/sphinx/directives/code.py index 543383dac..ff642ba70 100644 --- a/sphinx/directives/code.py +++ b/sphinx/directives/code.py @@ -13,6 +13,7 @@ from difflib import unified_diff from docutils import nodes from docutils.parsers.rst import Directive, directives +from docutils.statemachine import ViewList from six import string_types @@ -61,6 +62,17 @@ def dedent_lines(lines, dedent): return new_lines +def container_wrapper(directive, literal_node, caption): + caption_node = nodes.caption() + directive.state.nested_parse(ViewList([caption], source=''), + directive.content_offset, caption_node) + + container_node = nodes.container('', literal_block=True) + container_node += caption_node + container_node += literal_node + return container_node + + class CodeBlock(Directive): """ Directive for a code block with special highlighting or line numbering @@ -100,9 +112,6 @@ class CodeBlock(Directive): literal = nodes.literal_block(code, code) literal['language'] = self.arguments[0] - caption = self.options.get('caption') - if caption: - literal['caption'] = caption literal['linenos'] = 'linenos' in self.options or \ 'lineno-start' in self.options extra_args = literal['highlight_args'] = {} @@ -111,6 +120,11 @@ class CodeBlock(Directive): if 'lineno-start' in self.options: extra_args['linenostart'] = self.options['lineno-start'] set_source_info(self, literal) + + caption = self.options.get('caption') + if caption: + literal = container_wrapper(self, literal, caption) + return [literal] @@ -268,17 +282,20 @@ class LiteralInclude(Directive): retnode['language'] = self.options['language'] retnode['linenos'] = 'linenos' in self.options or \ 'lineno-start' in self.options - caption = self.options.get('caption') - if caption is not None: - if not caption: - caption = self.arguments[0] - retnode['caption'] = caption extra_args = retnode['highlight_args'] = {} if hl_lines is not None: extra_args['hl_lines'] = hl_lines if 'lineno-start' in self.options: extra_args['linenostart'] = self.options['lineno-start'] env.note_dependency(rel_filename) + + caption = self.options.get('caption') + if caption is not None: + if caption: + retnode = container_wrapper(self, retnode, caption) + else: + retnode = container_wrapper(self, retnode, self.arguments[0]) + return [retnode] diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index a636299ea..0338a6b57 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -562,9 +562,11 @@ class StandardDomain(Domain): break else: continue - elif node.tagname == 'literal_block': - if 'caption' in node: - sectname = node['caption'] + elif node.tagname == 'container' and node.get('literal_block'): + for n in node: + if n.tagname == 'caption': + sectname = clean_astext(n) + break else: continue else: diff --git a/sphinx/themes/basic/static/basic.css_t b/sphinx/themes/basic/static/basic.css_t index 17547d0fd..3616288c8 100644 --- a/sphinx/themes/basic/static/basic.css_t +++ b/sphinx/themes/basic/static/basic.css_t @@ -484,8 +484,7 @@ div.code-block-filename code { background-color: transparent; } -div.code-block-caption + pre, -div.code-block-caption + div.highlight > pre { +div.code-block-caption + div > div.highlight > pre { margin-top: 0; } diff --git a/sphinx/writers/html.py b/sphinx/writers/html.py index 14b11fcff..56657ee72 100644 --- a/sphinx/writers/html.py +++ b/sphinx/writers/html.py @@ -283,12 +283,21 @@ class HTMLTranslator(BaseTranslator): **highlight_args) starttag = self.starttag(node, 'div', suffix='', CLASS='highlight-%s' % lang) - if 'caption' in node: - starttag += '
%s
' % ( - node['caption'],) self.body.append(starttag + highlighted + '\n') raise nodes.SkipNode + def visit_caption(self, node): + if isinstance(node.parent, nodes.container) and node.parent.get('literal_block'): + self.body.append(self.starttag(node, 'div', '', CLASS='code-block-caption')) + else: + BaseTranslator.visit_caption(self, node) + + def depart_caption(self, node): + if isinstance(node.parent, nodes.container) and node.parent.get('literal_block'): + self.body.append('\n') + else: + BaseTranslator.depart_caption(self, node) + def visit_doctest_block(self, node): self.visit_literal_block(node) diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 59a174c1f..abc7ed75a 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -266,6 +266,7 @@ class LaTeXTranslator(nodes.NodeVisitor): self.next_section_ids = set() self.next_figure_ids = set() self.next_table_ids = set() + self.next_literal_ids = set() # flags self.in_title = 0 self.in_production_list = 0 @@ -1109,6 +1110,12 @@ class LaTeXTranslator(nodes.NodeVisitor): self.next_table_ids.add(node['refid']) self.next_table_ids.update(node['ids']) return + elif isinstance(next, nodes.container) and next.get('literal_block'): + # same for literal_block, but only if they have a caption + if node.get('refid'): + self.next_literal_ids.add(node['refid']) + self.next_literal_ids.update(node['ids']) + return except IndexError: pass if 'refuri' in node: @@ -1345,10 +1352,6 @@ class LaTeXTranslator(nodes.NodeVisitor): highlight_args['force'] = True if 'linenos' in node: linenos = node['linenos'] - caption = node.get('caption') - if caption: - self.body.append('\n\\begin{literal-block}\caption{%s}\n' % - (caption,)) def warner(msg): self.builder.warn(msg, (self.curfilestack[-1], node.line)) hlcode = self.highlighter.highlight_block(code, lang, warn=warner, @@ -1366,8 +1369,6 @@ class LaTeXTranslator(nodes.NodeVisitor): hlcode = hlcode.rstrip() + '\n' self.body.append('\n' + hlcode + '\\end{%sVerbatim}\n' % (self.table and 'Original' or '')) - if caption: - self.body.append('\n\\end{literal-block}\n') raise nodes.SkipNode def depart_literal_block(self, node): self.body.append('\n\\end{alltt}\n') @@ -1495,9 +1496,16 @@ class LaTeXTranslator(nodes.NodeVisitor): pass def visit_container(self, node): - pass + if node.get('literal_block'): + ids = '' + for id in self.next_literal_ids: + ids += self.hypertarget(id, anchor=False) + self.next_literal_ids.clear() + self.body.append('\n\\begin{literal-block}' + ids) + def depart_container(self, node): - pass + if node.get('literal_block'): + self.body.append('\\end{literal-block}\n') def visit_decoration(self, node): pass diff --git a/sphinx/writers/texinfo.py b/sphinx/writers/texinfo.py index a1051eb6a..61d2e62f2 100644 --- a/sphinx/writers/texinfo.py +++ b/sphinx/writers/texinfo.py @@ -1054,9 +1054,11 @@ class TexinfoTranslator(nodes.NodeVisitor): raise nodes.SkipNode def visit_container(self, node): - pass + if node.get('literal_block'): + self.body.append('\n\n@float LiteralBlock\n') def depart_container(self, node): - pass + if node.get('literal_block'): + self.body.append('\n@end float\n\n') def visit_decoration(self, node): pass @@ -1095,13 +1097,15 @@ class TexinfoTranslator(nodes.NodeVisitor): self.body.append('\n@end float\n\n') def visit_caption(self, node): - if not isinstance(node.parent, nodes.figure): + if (isinstance(node.parent, nodes.figure) or + (isinstance(node.parent, nodes.container) and node.parent.get('literal_block'))): + self.body.append('\n@caption{') + else: self.builder.warn('caption not inside a figure.', (self.curfilestack[-1], node.line)) - return - self.body.append('\n@caption{') def depart_caption(self, node): - if isinstance(node.parent, nodes.figure): + if (isinstance(node.parent, nodes.figure) or + (isinstance(node.parent, nodes.container) and node.parent.get('literal_block'))): self.body.append('}\n') def visit_image(self, node): diff --git a/tests/roots/test-directive-code/caption.rst b/tests/roots/test-directive-code/caption.rst index 274d0f19d..5a2fe4a1f 100644 --- a/tests/roots/test-directive-code/caption.rst +++ b/tests/roots/test-directive-code/caption.rst @@ -5,7 +5,7 @@ Code blocks ----------- .. code-block:: ruby - :caption: caption-test.rb + :caption: caption *test* rb def ruby? false @@ -17,5 +17,5 @@ Literal Include .. literalinclude:: literal.inc :language: python - :caption: caption-test.py + :caption: caption **test** py :lines: 10-11 diff --git a/tests/test_directive_code.py b/tests/test_directive_code.py index d29c9910d..0c2cead62 100644 --- a/tests/test_directive_code.py +++ b/tests/test_directive_code.py @@ -53,7 +53,7 @@ def test_code_block_dedent(app, status, warning): def test_code_block_caption_html(app, status, warning): app.builder.build(['caption']) html = (app.outdir / 'caption.html').text() - caption = '
caption-test.rb
' + caption = '
caption test rb
' assert caption in html @@ -61,7 +61,7 @@ def test_code_block_caption_html(app, status, warning): def test_code_block_caption_latex(app, status, warning): app.builder.build_all() latex = (app.outdir / 'Python.tex').text() - caption = '\\caption{caption-test.rb}' + caption = '\\caption{\ncaption \\emph{test} rb\n}' assert caption in latex @@ -99,7 +99,7 @@ def test_literal_include_dedent(app, status, warning): def test_literalinclude_caption_html(app, status, warning): app.builder.build('index') html = (app.outdir / 'caption.html').text() - caption = '
caption-test.py
' + caption = '
caption test py
' assert caption in html @@ -107,5 +107,5 @@ def test_literalinclude_caption_html(app, status, warning): def test_literalinclude_caption_latex(app, status, warning): app.builder.build('index') latex = (app.outdir / 'Python.tex').text() - caption = '\\caption{caption-test.py}' + caption = '\\caption{\ncaption \\textbf{test} py\n}' assert caption in latex