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