diff --git a/mkdocs/contrib/search/__init__.py b/mkdocs/contrib/search/__init__.py index a6f26ab6dd..495d5a14b6 100644 --- a/mkdocs/contrib/search/__init__.py +++ b/mkdocs/contrib/search/__init__.py @@ -52,6 +52,7 @@ class _PluginConfig(base.Config): min_search_length = c.Type(int, default=3) prebuild_index = c.Choice((False, True, 'node', 'python'), default=False) indexing = c.Choice(('full', 'sections', 'titles'), default='full') + full_path_in_title = c.Choice((False, True), default=False) class SearchPlugin(BasePlugin[_PluginConfig]): diff --git a/mkdocs/contrib/search/search_index.py b/mkdocs/contrib/search/search_index.py index 9eb43e8d6e..728ee52c5f 100644 --- a/mkdocs/contrib/search/search_index.py +++ b/mkdocs/contrib/search/search_index.py @@ -59,6 +59,11 @@ def add_entry_from_context(self, page: Page) -> None: the page itself and then one for each of its' heading tags. """ + title_parts = [page.title] if page.title else [] + if self.config['full_path_in_title']: + for ancestor in page.ancestors: + title_parts.insert(0, ancestor.title) + page_title = ' / '.join(title_parts) # Create the content parser and feed in the HTML for the # full page. This handles all the parsing and prepares # us to iterate through it. @@ -73,14 +78,23 @@ def add_entry_from_context(self, page: Page) -> None: # Create an entry for the full page. text = parser.stripped_html.rstrip('\n') if self.config['indexing'] == 'full' else '' - self._add_entry(title=page.title, text=text, loc=url) + + self._add_entry(title=page_title, text=text, loc=url) if self.config['indexing'] in ['full', 'sections']: - for section in parser.data: - self.create_entry_for_section(section, page.toc, url) + for i, section in enumerate(parser.data): + if self.config['full_path_in_title'] and i == 0: + section_title = page_title + else: + section_title = None + self.create_entry_for_section(section, page.toc, url, section_title) def create_entry_for_section( - self, section: ContentSection, toc: TableOfContents, abs_url: str + self, + section: ContentSection, + toc: TableOfContents, + abs_url: str, + title: Optional[str] = None, ) -> None: """ Given a section on the page, the table of contents and @@ -91,7 +105,7 @@ def create_entry_for_section( text = ' '.join(section.text) if self.config['indexing'] == 'full' else '' if toc_item is not None: - self._add_entry(title=toc_item.title, text=text, loc=abs_url + toc_item.url) + self._add_entry(title=title or toc_item.title, text=text, loc=abs_url + toc_item.url) def generate_search_index(self) -> str: """python to json conversion""" diff --git a/mkdocs/tests/search_tests.py b/mkdocs/tests/search_tests.py index ada861cad5..5429d972a5 100644 --- a/mkdocs/tests/search_tests.py +++ b/mkdocs/tests/search_tests.py @@ -82,6 +82,7 @@ def test_plugin_config_defaults(self): 'min_search_length': 3, 'prebuild_index': False, 'indexing': 'full', + 'full_path_in_title': False, } plugin = search.SearchPlugin() errors, warnings = plugin.load_config({}) @@ -96,6 +97,7 @@ def test_plugin_config_lang(self): 'min_search_length': 3, 'prebuild_index': False, 'indexing': 'full', + 'full_path_in_title': False, } plugin = search.SearchPlugin() errors, warnings = plugin.load_config({'lang': 'es'}) @@ -110,6 +112,7 @@ def test_plugin_config_separator(self): 'min_search_length': 3, 'prebuild_index': False, 'indexing': 'full', + 'full_path_in_title': False, } plugin = search.SearchPlugin() errors, warnings = plugin.load_config({'separator': r'[\s\-\.]+'}) @@ -124,6 +127,7 @@ def test_plugin_config_min_search_length(self): 'min_search_length': 2, 'prebuild_index': False, 'indexing': 'full', + 'full_path_in_title': False, } plugin = search.SearchPlugin() errors, warnings = plugin.load_config({'min_search_length': 2}) @@ -138,6 +142,7 @@ def test_plugin_config_prebuild_index(self): 'min_search_length': 3, 'prebuild_index': True, 'indexing': 'full', + 'full_path_in_title': False, } plugin = search.SearchPlugin() errors, warnings = plugin.load_config({'prebuild_index': True}) @@ -152,6 +157,7 @@ def test_plugin_config_indexing(self): 'min_search_length': 3, 'prebuild_index': False, 'indexing': 'titles', + 'full_path_in_title': False, } plugin = search.SearchPlugin() errors, warnings = plugin.load_config({'indexing': 'titles'}) @@ -159,6 +165,21 @@ def test_plugin_config_indexing(self): self.assertEqual(errors, []) self.assertEqual(warnings, []) + def test_plugin_config_full_path_in_title(self): + expected = { + 'lang': None, + 'separator': r'[\s\-]+', + 'min_search_length': 3, + 'prebuild_index': False, + 'full_path_in_title': True, + 'indexing': 'full', + } + plugin = search.SearchPlugin() + errors, warnings = plugin.load_config({'full_path_in_title': True}) + self.assertEqual(plugin.config, expected) + self.assertEqual(errors, []) + self.assertEqual(warnings, []) + def test_event_on_config_defaults(self): plugin = search.SearchPlugin() plugin.load_config({})