diff --git a/doc/usage/restructuredtext/directives.rst b/doc/usage/restructuredtext/directives.rst index 291a6ddc0..b5b0a2980 100644 --- a/doc/usage/restructuredtext/directives.rst +++ b/doc/usage/restructuredtext/directives.rst @@ -874,6 +874,19 @@ mainly contained in information units, such as the language reference. .. versionchanged:: 1.1 Added ``see`` and ``seealso`` types, as well as marking main entries. + .. rubric:: options + + .. rst:directive:option:: name: a label for hyperlink + :type: text + + Define implicit target name that can be referenced by using + :rst:role:`ref`. For example:: + + .. index:: Python + :name: py-index + + .. versionadded:: 3.0 + .. rst:role:: index While the :rst:dir:`index` directive is a block-level markup and links to the diff --git a/sphinx/domains/index.py b/sphinx/domains/index.py index d0bdbcfe0..18a256bac 100644 --- a/sphinx/domains/index.py +++ b/sphinx/domains/index.py @@ -12,6 +12,7 @@ from typing import Any, Dict, Iterable, List, Tuple from docutils import nodes from docutils.nodes import Node, system_message +from docutils.parsers.rst import directives from sphinx import addnodes from sphinx.domains import Domain @@ -68,19 +69,27 @@ class IndexDirective(SphinxDirective): required_arguments = 1 optional_arguments = 0 final_argument_whitespace = True - option_spec = {} # type: Dict + option_spec = { + 'name': directives.unchanged, + } def run(self) -> List[Node]: arguments = self.arguments[0].split('\n') - targetid = 'index-%s' % self.env.new_serialno('index') - targetnode = nodes.target('', '', ids=[targetid]) + + if 'name' in self.options: + targetname = self.options['name'] + targetnode = nodes.target('', '', names=[targetname]) + else: + targetid = 'index-%s' % self.env.new_serialno('index') + targetnode = nodes.target('', '', ids=[targetid]) + self.state.document.note_explicit_target(targetnode) indexnode = addnodes.index() indexnode['entries'] = [] indexnode['inline'] = False self.set_source_info(indexnode) for entry in arguments: - indexnode['entries'].extend(process_index_entry(entry, targetid)) + indexnode['entries'].extend(process_index_entry(entry, targetnode['ids'][0])) return [indexnode, targetnode] diff --git a/tests/test_environment_indexentries.py b/tests/test_environment_indexentries.py index ad7559ff2..685b36599 100644 --- a/tests/test_environment_indexentries.py +++ b/tests/test_environment_indexentries.py @@ -112,6 +112,28 @@ def test_create_seealso_index(app): assert index[2] == ('S', [('Sphinx', [[], [('see also documentation tool', [])], None])]) +@pytest.mark.sphinx('dummy', freshenv=True) +def test_create_index_with_name(app): + text = (".. index:: single: docutils\n" + " :name: ref1\n" + ".. index:: single: Python\n" + " :name: ref2\n" + ".. index:: Sphinx\n") + restructuredtext.parse(app, text) + index = IndexEntries(app.env).create_index(app.builder) + + # check index is created correctly + assert len(index) == 3 + assert index[0] == ('D', [('docutils', [[('', '#ref1')], [], None])]) + assert index[1] == ('P', [('Python', [[('', '#ref2')], [], None])]) + assert index[2] == ('S', [('Sphinx', [[('', '#index-0')], [], None])]) + + # check the reference labels are created correctly + std = app.env.get_domain('std') + assert std.anonlabels['ref1'] == ('index', 'ref1') + assert std.anonlabels['ref2'] == ('index', 'ref2') + + @pytest.mark.sphinx('dummy', freshenv=True) def test_create_index_by_key(app): # At present, only glossary directive is able to create index key