Skip to content

Commit

Permalink
Merge branch 'master' into pr-11970-followup/fixup-cleanup-singlehtml…
Browse files Browse the repository at this point in the history
…-uri-resolution
  • Loading branch information
jayaddison authored Oct 25, 2024
2 parents f155b35 + 63a4175 commit 8df82c0
Show file tree
Hide file tree
Showing 14 changed files with 254 additions and 108 deletions.
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,8 @@ Features added
Bugs fixed
----------

* #13060: HTML Search: use ``Map`` to store per-file term scores.
Patch by James Addison

Testing
-------
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ docs = [
lint = [
"flake8>=6.0",
"ruff==0.7.0",
"mypy==1.12.1",
"mypy==1.13.0",
"sphinx-lint>=0.9",
"types-colorama==0.4.15.20240311",
"types-defusedxml==0.7.0.20240218",
Expand All @@ -91,7 +91,7 @@ lint = [
"types-Pygments==2.18.0.20240506",
"types-requests==2.32.0.20241016", # align with requests
"types-urllib3==1.26.25.14",
"pyright==1.1.385",
"pyright==1.1.386",
"pytest>=6.0",
]
test = [
Expand Down
21 changes: 13 additions & 8 deletions sphinx/builders/html/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -505,10 +505,15 @@ def prepare_writing(self, docnames: Set[str]) -> None:
rellinks: list[tuple[str, str, str, str]] = []
if self.use_index:
rellinks.append(('genindex', _('General Index'), 'I', _('index')))
for indexname, indexcls, _content, _collapse in self.domain_indices:
for index_name, index_cls, _content, _collapse in self.domain_indices:
# if it has a short name
if indexcls.shortname:
rellinks.append((indexname, indexcls.localname, '', indexcls.shortname))
if index_cls.shortname:
rellinks.append((
index_name,
index_cls.localname,
'',
index_cls.shortname,
))

# add assets registered after ``Builder.init()``.
for css_filename, attrs in self.app.registry.css_files:
Expand Down Expand Up @@ -755,14 +760,14 @@ def write_genindex(self) -> None:
self.handle_page('genindex', genindexcontext, 'genindex.html')

def write_domain_indices(self) -> None:
for indexname, indexcls, content, collapse in self.domain_indices:
indexcontext = {
'indextitle': indexcls.localname,
for index_name, index_cls, content, collapse in self.domain_indices:
index_context = {
'indextitle': index_cls.localname,
'content': content,
'collapse_index': collapse,
}
logger.info(indexname + ' ', nonl=True)
self.handle_page(indexname, indexcontext, 'domainindex.html')
logger.info('%s ', index_name, nonl=True)
self.handle_page(index_name, index_context, 'domainindex.html')

def copy_image_files(self) -> None:
if self.images:
Expand Down
10 changes: 0 additions & 10 deletions sphinx/builders/linkcheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
class _Status(StrEnum):
BROKEN = 'broken'
IGNORED = 'ignored'
LOCAL = 'local'
RATE_LIMITED = 'rate-limited'
REDIRECTED = 'redirected'
TIMEOUT = 'timeout'
Expand Down Expand Up @@ -124,15 +123,6 @@ def process_result(self, result: CheckResult) -> None:
else:
msg = result.uri
logger.info(darkgray('-ignored- ') + msg)
case _Status.LOCAL:
logger.info(darkgray('-local- ') + result.uri)
self.write_entry(
_Status.LOCAL,
result.docname,
filename,
result.lineno,
result.uri,
)
case _Status.WORKING:
logger.info(darkgreen('ok ') + f'{result.uri}{result.message}')
case _Status.TIMEOUT:
Expand Down
2 changes: 1 addition & 1 deletion sphinx/domains/javascript.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ def run(self) -> list[Node]:
domain = self.env.domains.javascript_domain

node_id = make_id(self.env, self.state.document, 'module', mod_name)
domain.note_module(mod_name, node_id)
domain.note_module(modname=mod_name, node_id=node_id)
# Make a duplicate entry in 'objects' to facilitate searching for
# the module in JavaScriptDomain.find_obj()
domain.note_object(mod_name, 'module', node_id,
Expand Down
112 changes: 76 additions & 36 deletions sphinx/domains/python/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,11 +471,13 @@ def run(self) -> list[Node]:
self.set_source_info(target)
self.state.document.note_explicit_target(target)

domain.note_module(modname,
node_id,
self.options.get('synopsis', ''),
self.options.get('platform', ''),
'deprecated' in self.options)
domain.note_module(
name=modname,
node_id=node_id,
synopsis=self.options.get('synopsis', ''),
platform=self.options.get('platform', ''),
deprecated='deprecated' in self.options,
)
domain.note_object(modname, 'module', node_id, location=target)

# the platform and synopsis aren't printed; in fact, they are only
Expand Down Expand Up @@ -556,26 +558,32 @@ class PythonModuleIndex(Index):
name = 'modindex'
localname = _('Python Module Index')
shortname = _('modules')
domain: PythonDomain

def generate(self, docnames: Iterable[str] | None = None,
) -> tuple[list[tuple[str, list[IndexEntry]]], bool]:
doc_names = frozenset(docnames) if docnames is not None else None

content: dict[str, list[IndexEntry]] = {}
# list of prefixes to ignore
ignores: list[str] = self.domain.env.config['modindex_common_prefix']
ignores = sorted(ignores, key=len, reverse=True)
ignores: list[str] = sorted(
self.domain.env.config['modindex_common_prefix'], key=len, reverse=True
)

# list of all modules, sorted by module name
modules = sorted(self.domain.data['modules'].items(),
key=lambda x: x[0].lower())
modules = sorted(self.domain.modules.items(), key=lambda t: t[0].lower())

# sort out collapsible modules
prev_modname = ''
num_toplevels = 0
for modname, (docname, node_id, synopsis, platforms, deprecated) in modules:
if docnames and docname not in docnames:

num_top_levels = 0
for modname, module in modules:
if doc_names and module.docname not in doc_names:
continue

for ignore in ignores:
if modname.startswith(ignore):
modname = modname[len(ignore):]
modname = modname.removeprefix(ignore)
stripped = ignore
break
else:
Expand All @@ -587,32 +595,55 @@ def generate(self, docnames: Iterable[str] | None = None,

entries = content.setdefault(modname[0].lower(), [])

package = modname.split('.')[0]
package = modname.split('.', maxsplit=1)[0]
if package != modname:
# it's a submodule
if prev_modname == package:
# first submodule - make parent a group head
if entries:
last = entries[-1]
entries[-1] = IndexEntry(last[0], 1, last[2], last[3],
last[4], last[5], last[6])
entries[-1] = IndexEntry(
name=last.name,
subtype=1,
docname=last.docname,
anchor=last.anchor,
extra=last.extra,
qualifier=last.qualifier,
descr=last.descr,
)
elif not prev_modname.startswith(package):
# submodule without parent in list, add dummy entry
entries.append(IndexEntry(stripped + package, 1, '', '', '', '', ''))
dummy_entry = IndexEntry(
name=stripped + package,
subtype=1,
docname='',
anchor='',
extra='',
qualifier='',
descr='',
)
entries.append(dummy_entry)
subtype = 2
else:
num_toplevels += 1
num_top_levels += 1
subtype = 0

qualifier = _('Deprecated') if deprecated else ''
entries.append(IndexEntry(stripped + modname, subtype, docname,
node_id, platforms, qualifier, synopsis))
entry = IndexEntry(
name=stripped + modname,
subtype=subtype,
docname=module.docname,
anchor=module.node_id,
extra=module.platform,
qualifier=_('Deprecated') if module.deprecated else '',
descr=module.synopsis,
)
entries.append(entry)
prev_modname = modname

# apply heuristics when to collapse modindex at page load:
# only collapse if number of toplevel modules is larger than
# number of submodules
collapse = len(modules) - num_toplevels < num_toplevels
collapse = len(modules) - num_top_levels < num_top_levels

# sort by first letter
sorted_content = sorted(content.items())
Expand Down Expand Up @@ -704,14 +735,20 @@ def note_object(self, name: str, objtype: str, node_id: str,
def modules(self) -> dict[str, ModuleEntry]:
return self.data.setdefault('modules', {}) # modname -> ModuleEntry

def note_module(self, name: str, node_id: str, synopsis: str,
platform: str, deprecated: bool) -> None:
def note_module(
self, name: str, node_id: str, synopsis: str, platform: str, deprecated: bool
) -> None:
"""Note a python module for cross reference.
.. versionadded:: 2.1
"""
self.modules[name] = ModuleEntry(self.env.docname, node_id,
synopsis, platform, deprecated)
self.modules[name] = ModuleEntry(
docname=self.env.docname,
node_id=node_id,
synopsis=synopsis,
platform=platform,
deprecated=deprecated,
)

def clear_doc(self, docname: str) -> None:
to_remove = [
Expand Down Expand Up @@ -854,9 +891,10 @@ def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Bui
continue

if obj[2] == 'module':
results.append(('py:mod',
self._make_module_refnode(builder, fromdocname,
name, contnode)))
results.append((
'py:mod',
self._make_module_refnode(builder, fromdocname, name, contnode)
))
else:
# determine the content of the reference by conditions
content = find_pending_xref_condition(node, 'resolved')
Expand All @@ -874,16 +912,18 @@ def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Bui
def _make_module_refnode(self, builder: Builder, fromdocname: str, name: str,
contnode: Node) -> Element:
# get additional info for modules
module = self.modules[name]
title = name
module: ModuleEntry = self.modules[name]
title_parts = [name]
if module.synopsis:
title += ': ' + module.synopsis
title_parts.append(f': {module.synopsis}')
if module.deprecated:
title += _(' (deprecated)')
title_parts.append(_(' (deprecated)'))
if module.platform:
title += ' (' + module.platform + ')'
return make_refnode(builder, fromdocname, module.docname, module.node_id,
contnode, title)
title_parts.append(f' ({module.platform})')
title = ''.join(title_parts)
return make_refnode(
builder, fromdocname, module.docname, module.node_id, contnode, title
)

def get_objects(self) -> Iterator[tuple[str, str, str, str, str, int]]:
for modname, mod in self.modules.items():
Expand Down
3 changes: 2 additions & 1 deletion sphinx/search/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,8 @@ class _JavaScriptIndex:
SUFFIX = ')'

def dumps(self, data: Any) -> str:
return self.PREFIX + json.dumps(data, sort_keys=True) + self.SUFFIX
data_json = json.dumps(data, separators=(',', ':'), sort_keys=True)
return self.PREFIX + data_json + self.SUFFIX

def loads(self, s: str) -> Any:
data = s[len(self.PREFIX) : -len(self.SUFFIX)]
Expand Down
7 changes: 4 additions & 3 deletions sphinx/themes/basic/static/searchtools.js
Original file line number Diff line number Diff line change
Expand Up @@ -547,8 +547,9 @@ const Search = {

// set score for the word in each file
recordFiles.forEach((file) => {
if (!scoreMap.has(file)) scoreMap.set(file, {});
scoreMap.get(file)[word] = record.score;
if (!scoreMap.has(file)) scoreMap.set(file, new Map());
const fileScores = scoreMap.get(file);
fileScores.set(word, record.score);
});
});

Expand Down Expand Up @@ -587,7 +588,7 @@ const Search = {
break;

// select one (max) score for the file.
const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w]));
const score = Math.max(...wordList.map((w) => scoreMap.get(file).get(w)));
// add result to the result list
results.push([
docNames[file],
Expand Down
2 changes: 1 addition & 1 deletion tests/js/fixtures/cpp/searchindex.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion tests/js/fixtures/multiterm/searchindex.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion tests/js/fixtures/partial/searchindex.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 8df82c0

Please sign in to comment.