From 9a46457214bcc59a38eb8dada1d1bcba71c7405d Mon Sep 17 00:00:00 2001
From: tristanlatr <19967168+tristanlatr@users.noreply.github.com>
Date: Sun, 9 Nov 2025 05:46:43 +0000
Subject: [PATCH 01/14] Search UX part 1: Result selection and shortcuts.
---
README.rst | 5 ++
pydoctor/templatewriter/summary.py | 5 +-
pydoctor/themes/base/apidocs.css | 4 ++
pydoctor/themes/base/search.js | 93 +++++++++++++++++++++++++++++-
4 files changed, 105 insertions(+), 2 deletions(-)
diff --git a/README.rst b/README.rst
index d489be1e5..af7d818d7 100644
--- a/README.rst
+++ b/README.rst
@@ -73,6 +73,11 @@ What's New?
in development
^^^^^^^^^^^^^^
+* Improve the search box UX: There is now a keyboard shortcut ('Ctrl+K' or 'Cmd+K' on Mac or '/',
+ but the later is overriden by ReadTheDocs) to focus the search box.
+ When the search box is focused, you can use the up and down arrow keys to navigate the results,
+ and press enter to open the selected result.
+
pydoctor 25.10.1
^^^^^^^^^^^^^^^^
diff --git a/pydoctor/templatewriter/summary.py b/pydoctor/templatewriter/summary.py
index d24992905..8f81dbaf6 100644
--- a/pydoctor/templatewriter/summary.py
+++ b/pydoctor/templatewriter/summary.py
@@ -411,10 +411,13 @@ class HelpPage(Page):
Search
------
- You can search for definitions of modules, packages, classes, functions, methods and attributes.
+ You can search for definitions of modules, packages, classes, functions, methods and attributes. The shorcut Ctrl+K (or Cmd+K on Mac) focuses the search box.
These items can be searched using part or all of the name and/or from their docstrings if "search in docstrings" is enabled.
Multiple search terms can be provided separated by whitespace.
+
+ When the search box is focused, you can use the up and down arrow keys to navigate the results,
+ and press enter to open the selected result.
The search is powered by `lunrjs `_.
diff --git a/pydoctor/themes/base/apidocs.css b/pydoctor/themes/base/apidocs.css
index bdb548cd4..efb1b11a1 100644
--- a/pydoctor/themes/base/apidocs.css
+++ b/pydoctor/themes/base/apidocs.css
@@ -1151,6 +1151,10 @@ input[type="search"]::-webkit-search-results-decoration { display: none; }
cursor: pointer;
}
+.search-result-selected {
+ box-shadow: inset -5px 0px 0px 1px rgb(109 161 219 / 70%);
+}
+
/* Constant values repr */
pre.constant-value { padding: .5em; }
.rst-variable-linewrap { color: #604000; font-weight: bold; }
diff --git a/pydoctor/themes/base/search.js b/pydoctor/themes/base/search.js
index f76b5dd86..069a9d892 100644
--- a/pydoctor/themes/base/search.js
+++ b/pydoctor/themes/base/search.js
@@ -390,7 +390,7 @@ input.oninput = (event) => {
searchAsYouType();
}, 0);
};
-input.onkeyup = (event) => {
+input.onkedown = (event) => {
if (event.key === 'Enter') {
launchSearch(true);
}
@@ -436,6 +436,7 @@ window.addEventListener("click", (event) => {
// 2. Show the dropdown if the user clicks inside the search box
if (event.target.closest('#search-box')){
if (input.value.length>0){
+ _resetSelectedSearchResult();
showResultContainer();
return;
}
@@ -463,3 +464,93 @@ window.addEventListener("click", (event) => {
}
}
});
+
+// Focus on the search bar when the user hit '/' or 'ctrl+k' key, the '/' shortcut is replaced by the default readthedocs search
+// box which is not going to include any of the API documentation in its index at the moment (see
+// issue #356), this is why we provide another shortcut which is commonly associated to searching.
+window.addEventListener('keydown', (event) => {
+ if((event.key === 'k' && (event.ctrlKey || event.metaKey) && !event.altKey) || (
+ event.key === '/' && !event.ctrlKey && !event.metaKey && !event.altKey)){
+
+ event.preventDefault();
+ input.focus();
+ if (input.value.length>0){
+ _resetSelectedSearchResult();
+ showResultContainer();
+ return;
+ }
+ }
+});
+
+function _getSelectedSearchResult(){
+ const results = results_list.getElementsByTagName('tr');
+ let selectedIndex = -1;
+ for (let i=0; i= 0){
+ const results = results_list.getElementsByTagName('tr');
+ results[selectedIndex].classList.remove('search-result-selected');
+ }
+ }
+
+// When the search box is focused, we can use top and down arrows to navigate the search results
+// and use the enter key to open the selected result.
+input.addEventListener('keydown', (event) => {
+ const results = results_list.getElementsByTagName('tr');
+ if (results.length === 0){
+ return;
+ }
+ let selectedIndex = _getSelectedSearchResult();
+ if (event.key === 'ArrowDown'){
+ // Move selection down
+ if (selectedIndex >= 0){
+ results[selectedIndex].classList.remove('search-result-selected');
+ }
+
+ selectedIndex = (selectedIndex + 1) % results.length;
+ // Ignore private items when the toogle is off
+ while (document.body.classList.contains('private-hidden') && results[selectedIndex].classList.contains('private')){
+ selectedIndex = (selectedIndex + 1) % results.length;
+ }
+
+ results[selectedIndex].classList.add('search-result-selected');
+ results[selectedIndex].scrollIntoView({block: "nearest"});
+ event.preventDefault();
+ }
+ else if (event.key === 'ArrowUp'){
+ // Move selection up
+ if (selectedIndex >= 0){
+ results[selectedIndex].classList.remove('search-result-selected');
+ }
+
+ selectedIndex = (selectedIndex - 1 + results.length) % results.length;
+ // Ignore private items when the toogle is off
+ while (document.body.classList.contains('private-hidden') && results[selectedIndex].classList.contains('private')){
+ selectedIndex = (selectedIndex - 1 + results.length) % results.length;
+ }
+
+ results[selectedIndex].classList.add('search-result-selected');
+ results[selectedIndex].scrollIntoView({block: "nearest"});
+ event.preventDefault();
+ }
+ else if (event.key === 'Enter'){
+ // Open selected result
+ if (selectedIndex >= 0){
+ const link = results[selectedIndex].getElementsByTagName('a')[0];
+ if (link){
+ window.location.href = link.href;
+ event.preventDefault();
+ }
+ }
+ }
+});
From d80d0158e6a0908ce00ae01c8d81b55a78d5011f Mon Sep 17 00:00:00 2001
From: tristanlatr <19967168+tristanlatr@users.noreply.github.com>
Date: Sun, 9 Nov 2025 00:18:37 +0000
Subject: [PATCH 02/14] Search UX part 2: If the search box contains
"docstring:" enable search in docstrings automatically.
---
README.rst | 10 ++++++----
pydoctor/themes/base/search.js | 10 +++++++++-
2 files changed, 15 insertions(+), 5 deletions(-)
diff --git a/README.rst b/README.rst
index af7d818d7..de0ef0b39 100644
--- a/README.rst
+++ b/README.rst
@@ -73,10 +73,12 @@ What's New?
in development
^^^^^^^^^^^^^^
-* Improve the search box UX: There is now a keyboard shortcut ('Ctrl+K' or 'Cmd+K' on Mac or '/',
- but the later is overriden by ReadTheDocs) to focus the search box.
- When the search box is focused, you can use the up and down arrow keys to navigate the results,
- and press enter to open the selected result.
+* Improve the search box UX:
+ - There is now a keyboard shortcut ('Ctrl+K' or 'Cmd+K' on Mac or '/',
+ but the later is overriden by ReadTheDocs) to focus the search box.
+ When the search box is focused, you can use the up and down arrow keys to navigate the results,
+ and press enter to open the selected result.
+ - If the search box contains "docstring:", the search will be performed in docstrings automatically.
pydoctor 25.10.1
^^^^^^^^^^^^^^^^
diff --git a/pydoctor/themes/base/search.js b/pydoctor/themes/base/search.js
index 069a9d892..6d81854d6 100644
--- a/pydoctor/themes/base/search.js
+++ b/pydoctor/themes/base/search.js
@@ -364,7 +364,15 @@ function displaySearchResults(_query, documentResults, lunrResults){
}
function _isSearchInDocstringsEnabled() {
- return searchInDocstringsCheckbox.checked;
+ if (searchInDocstringsCheckbox.checked){
+ return true;
+ }
+ if (input.value.startsWith("docstring:") || input.value.indexOf(" docstring:") !== -1){
+ searchInDocstringsCheckbox.checked = true;
+ toggleSearchInDocstrings()
+ return true;
+ }
+ return false;
}
function toggleSearchInDocstrings() {
From 7001716e819d3b75def01f552f4cd49e3c3ca57a Mon Sep 17 00:00:00 2001
From: tristanlatr <19967168+tristanlatr@users.noreply.github.com>
Date: Sun, 9 Nov 2025 05:04:17 +0000
Subject: [PATCH 03/14] Search UX part 3: Use a logical "and" in between search
terms by default. Use leading '?' to make a term optional.
---
README.rst | 2 +
pydoctor/templatewriter/summary.py | 89 ++++++++++++++++--------------
pydoctor/themes/base/searchlib.js | 46 +++++++--------
3 files changed, 68 insertions(+), 69 deletions(-)
diff --git a/README.rst b/README.rst
index de0ef0b39..cb683767b 100644
--- a/README.rst
+++ b/README.rst
@@ -79,6 +79,8 @@ in development
When the search box is focused, you can use the up and down arrow keys to navigate the results,
and press enter to open the selected result.
- If the search box contains "docstring:", the search will be performed in docstrings automatically.
+ - Use a logical "and" in between search terms by default. Use leading '?' to make a term optional.
+ (More on this in the embedded help page of the generated documentation.)
pydoctor 25.10.1
^^^^^^^^^^^^^^^^
diff --git a/pydoctor/templatewriter/summary.py b/pydoctor/templatewriter/summary.py
index 8f81dbaf6..437c3a268 100644
--- a/pydoctor/templatewriter/summary.py
+++ b/pydoctor/templatewriter/summary.py
@@ -372,16 +372,14 @@ class HelpPage(Page):
There is one page per class, module and package.
Each page present summary table(s) which feature the members of the object.
- Package or Module page
- ~~~~~~~~~~~~~~~~~~~~~~~
+ **Package or Module page**
Each of these pages has two main sections consisting of:
- summary tables submodules and subpackages and the members of the module or in the ``__init__.py`` file.
- detailed descriptions of function and attribute members.
- Class page
- ~~~~~~~~~~
+ **Class page**
Each class has its own separate page.
Each of these pages has three main sections consisting of:
@@ -392,18 +390,15 @@ class HelpPage(Page):
Entries in each of these sections are omitted if they are empty or not applicable.
- Module Index
- ~~~~~~~~~~~~
+ **Module Index**
Provides a high level overview of the packages and modules structure.
- Class Hierarchy
- ~~~~~~~~~~~~~~~
+ **Class Hierarchy**
Provides a list of classes organized by inheritance structure. Note that ``object`` is ommited.
- Index Of Names
- ~~~~~~~~~~~~~~
+ **Index Of Names**
The Index contains an alphabetic index of all objects in the documentation.
@@ -421,15 +416,14 @@ class HelpPage(Page):
The search is powered by `lunrjs `_.
- Indexing
- ~~~~~~~~
+ **Indexing**
By default the search only matches on the name of the object.
Enable the full text search in the docstrings with the checkbox option.
You can instruct the search to look only in specific fields by passing the field name in the search like ``docstring:term``.
- **Possible fields are**:
+ Possible fields are:
- ``name``, the name of the object (example: "MyClassAdapter" or "my_fmin_opti").
- ``qname``, the fully qualified name of the object (example: "lib.classses.MyClassAdapter").
@@ -439,38 +433,49 @@ class HelpPage(Page):
Last two fields are only applicable if "search in docstrings" is enabled.
- Other search features
- ~~~~~~~~~~~~~~~~~~~~~
-
- Term presence.
- The default behaviour is to give a better ranking to object matching multiple terms of your query,
- but still show entries that matches only one of the two terms.
- To change this behavour, you can use the sign ``+``.
-
- - To indicate a term must exactly match use the plus sing: ``+``.
- - To indicate a term must not match use the minus sing: ``-``.
+ **Term presence**
+
+ By default, multiple terms in the query are combined with logical AND:
+ all (non-optional) terms must match for a result to be returned.
+
+ You can change how an individual term participates in the query by
+ prefixing it with one of three modifiers:
+
+ - ``+term``: The '+' prefix indicates an exact/required presence for that term.
+ When '+' is used the automatic trailing wildcard is suppressed and the
+ search treats the term as an exact token match (rather than a
+ prefix/wildcard search). The term must be present for a result to
+ match.
+ - ``-term``: The '-' prefix marks the term as an exclusion. Matches that contain
+ that term are filtered out. Like '+', the '-' prefix suppresses the
+ automatic trailing wildcard and treats the term as an exact token to
+ be excluded.
+ - ``?term``: The '?' prefix marks the term as optional. Optional terms are not
+ required for a result to match; they are used to increase relevance
+ if present but do not enforce inclusion.
- Wildcards
- A trailling wildcard is automatically added to each term of your query if they don't contain an explicit term presence (``+`` or ``-``).
- Searching for ``foo`` is the same as searching for ``foo*``.
+ **Wildcards**
+
+ - By default each plain term (without a presence modifier) gets an
+ automatic trailing wildcard, so "foo" is treated like "foo*".
+ In addition to this automatic feature, you can manually add a wildcard
+ anywhere else in the query.
+ - If a term is prefixed with '+' or '-' the automatic trailing wildcard
+ is not added, turning the term into an exact token match/exclusion.
+ - If a term contains a dot ('.'), a leading wildcard is also added to
+ enable matching across dotted module/class boundaries. For example,
+ "model." behaves like "*model.*".
- If the query include a dot (``.``), a leading wildcard will to also added,
- searching for ``model.`` is the same as ``*model.*`` and ``.model`` is the same as ``*.model*``.
-
- In addition to this automatic feature, you can manually add a wildcard anywhere else in the query.
-
-
- Query examples
- ~~~~~~~~~~~~~~
-
- - "doc" matches "pydoctor.model.Documentable" and "pydoctor.model.DocLocation".
- - "+doc" matches "pydoctor.model.DocLocation" but won't match "pydoctor.model.Documentable".
- - "ensure doc" matches "pydoctor.epydoc2stan.ensure_parsed_docstring" and other object whose matches either "doc" or "ensure".
- - "inp str" matches "java.io.InputStream" and other object whose matches either "in" or "str".
- - "model." matches everything in the pydoctor.model module.
- - ".web.*tag" matches "twisted.web.teplate.Tag" and related.
- - "docstring:ansi" matches object whose docstring matches "ansi".
+ **Examples**
+
+ - ``doc`` -> matches names containing tokens that start with "doc" (equivalent to "doc*").
+ - ``ensure doc`` -> matches object whose matches "doc*" and "ensure*".
+ - ``docstring:ansi`` -> matches object whose docstring matches "ansi*".
+ - ``+doc`` -> matches only where a token equals "doc" exactly.
+ - ``-test`` -> excludes any result containing a token equal to "test".
+ - ``?input ?str`` -> matches results that contain either "input" or "str" but neither is required.
+ - ``+doc -deprecated ?helper`` -> requires an exact "doc" token, excludes "deprecated", and treats "helper" as optional.
''')
def title(self) -> str:
diff --git a/pydoctor/themes/base/searchlib.js b/pydoctor/themes/base/searchlib.js
index c1a3995f0..d56bfd846 100644
--- a/pydoctor/themes/base/searchlib.js
+++ b/pydoctor/themes/base/searchlib.js
@@ -49,46 +49,38 @@ onmessage = (message) => {
});
// Auto wilcard feature, see issue https://github.com/twisted/pydoctor/issues/648
- var new_clauses = [];
-
_query.clauses.forEach(clause => {
- if (clause.presence === 1) { // ignore clauses that have explicit presence (+/-)
- // Setting clause.wildcard is useless, and clause.wildcard is actually always NONE
+ let excplicitlyOptional = clause.term.slice(0,1) == '?' && clause.term.length > 1;
+ if (excplicitlyOptional){
+ // Remove leading '?' from term
+ clause.term = clause.term.slice(1);
+ }
+
+ if (clause.presence === lunr.Query.presence.OPTIONAL) { // ignore clauses that have explicit presence (+/-)
+ if (!excplicitlyOptional){
+ clause.presence = lunr.Query.presence.REQUIRED
+ }
+ // Setting clause.wildcard is useless (but we do it anyway for clarty),
// due to https://github.com/olivernn/lunr.js/issues/495
- // But this works...
+ // But appending to .term works...
if (clause.term.slice(-1) != '*'){
- let new_clause = {...clause}
- new_clause.term = new_clause.term + '*'
- clause.boost = 2
- new_clause.boost = 1
- new_clauses.push(new_clause)
+ // Adding a trailing wildcard
+ clause.wildcard = lunr.Query.wildcard.TRAILING
+ clause.term = clause.term + '*'
}
- // Adding a leading wildcard if the dot is included as well.
- // This should only apply to terms that are applicable to name-like fields.
- // so we refer to the default fields
if (clause.term.indexOf('.') != -1) {
if (clause.term.slice(0,1) != '*'){
- let second_new_clause = {...clause}
- second_new_clause.boost = 1
- if (clause.term.slice(0,1) != '.'){
- second_new_clause.term = '.' + second_new_clause.term
- }
- second_new_clause.term = '*' + second_new_clause.term
- if (clause.term.slice(-1) != '*'){
- second_new_clause.term = second_new_clause.term + '*'
- }
- new_clauses.push(second_new_clause)
+ // Adding a leading wildcard if the dot is included as well.
+ clause.wildcard = lunr.Query.wildcard.LEADING | lunr.Query.wildcard.TRAILING
+ clause.term = '*' + clause.term
}
}
}
});
- new_clauses.forEach(clause => {
- _query.clauses.push(clause)
- });
console.log('Parsed query:')
- console.dir(_query.clauses)
+ console.dir(_query)
}
// Launch the search
From 6d3b3c8cc1f37df463317b5536b57011c4c0fd3d Mon Sep 17 00:00:00 2001
From: tristanlatr <19967168+tristanlatr@users.noreply.github.com>
Date: Sun, 9 Nov 2025 06:26:23 +0000
Subject: [PATCH 04/14] Search UX part 4: Include the field "kind" in the
default index and adjust the treshold for search as you type to be enabled.
---
pydoctor/templatewriter/search.py | 6 +++---
pydoctor/templatewriter/summary.py | 6 +++---
pydoctor/themes/base/search.js | 4 ++--
3 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/pydoctor/templatewriter/search.py b/pydoctor/templatewriter/search.py
index fe48a00ee..f2a7c3793 100644
--- a/pydoctor/templatewriter/search.py
+++ b/pydoctor/templatewriter/search.py
@@ -63,7 +63,7 @@ class LunrIndexWriter:
fields: List[str]
_BOOSTS = {
- 'name':6,
+ 'name':3,
'names': 1,
'qname':2,
'docstring':1,
@@ -160,12 +160,12 @@ def write_lunr_index(output_dir: Path, system: model.System) -> None:
"""
LunrIndexWriter(output_dir / "searchindex.json",
system=system,
- fields=["name", "names", "qname"]
+ fields=["name", "names", "qname", "kind"]
).write()
LunrIndexWriter(output_dir / "fullsearchindex.json",
system=system,
- fields=["name", "names", "qname", "docstring", "kind"]
+ fields=["name", "names", "qname", "kind", "docstring",]
).write()
diff --git a/pydoctor/templatewriter/summary.py b/pydoctor/templatewriter/summary.py
index 437c3a268..2466dbb01 100644
--- a/pydoctor/templatewriter/summary.py
+++ b/pydoctor/templatewriter/summary.py
@@ -426,12 +426,12 @@ class HelpPage(Page):
Possible fields are:
- ``name``, the name of the object (example: "MyClassAdapter" or "my_fmin_opti").
- - ``qname``, the fully qualified name of the object (example: "lib.classses.MyClassAdapter").
- - ``names``, the name splitted on camel case or snake case (example: "My Class Adapter" or "my fmin opti")
- ``docstring``, the docstring of the object (example: "This is an adapter for HTTP json requests that logs into a file...")
- ``kind``, can be one of: $kind_names
+ - ``qname``, the fully qualified name of the object (example: "lib.classses.MyClassAdapter").
+ - ``names``, the name splitted on camel case or snake case (example: "My Class Adapter" or "my fmin opti")
- Last two fields are only applicable if "search in docstrings" is enabled.
+ Field "docstring" is only applicable if "search in docstrings" is enabled.
**Term presence**
diff --git a/pydoctor/themes/base/search.js b/pydoctor/themes/base/search.js
index 6d81854d6..88ade68c2 100644
--- a/pydoctor/themes/base/search.js
+++ b/pydoctor/themes/base/search.js
@@ -160,8 +160,8 @@ function _stopSearchingProcess(){
// Values configuring the search-as-you-type feature.
var SEARCH_DEFAULT_DELAY = 150; // in miliseconds
var SEARCH_INCREASED_DELAY = 300; // in miliseconds
-var SEARCH_INDEX_SIZE_TRESH_INCREASE_DELAY = 15; // in MB
-var SEARCH_INDEX_SIZE_TRESH_DISABLE_SEARCH_AS_YOU_TYPE = 25; // in MB
+var SEARCH_INDEX_SIZE_TRESH_INCREASE_DELAY = 20; // in MB
+var SEARCH_INDEX_SIZE_TRESH_DISABLE_SEARCH_AS_YOU_TYPE = 35; // in MB
// Search delay depends on index size in MB
function _getIndexSizePromise(indexURL){
From e0a36c7a8a610564a4a85cb2bb2180704c67cd89 Mon Sep 17 00:00:00 2001
From: tristanlatr <19967168+tristanlatr@users.noreply.github.com>
Date: Mon, 10 Nov 2025 18:20:58 +0000
Subject: [PATCH 05/14] Don't use document's boosts. It's rather confusing to
have a search result further away just because it's a function not a class
---
pydoctor/templatewriter/search.py | 19 +++----------------
1 file changed, 3 insertions(+), 16 deletions(-)
diff --git a/pydoctor/templatewriter/search.py b/pydoctor/templatewriter/search.py
index f2a7c3793..c952ad3ce 100644
--- a/pydoctor/templatewriter/search.py
+++ b/pydoctor/templatewriter/search.py
@@ -75,14 +75,6 @@ class LunrIndexWriter:
_SKIP_PIPELINES = list(_BOOSTS)
_SKIP_PIPELINES.remove('docstring')
- @staticmethod
- def get_ob_boost(ob: model.Documentable) -> int:
- # Advantage container types because they hold more informations.
- if isinstance(ob, (model.Class, model.Module)):
- return 2
- else:
- return 1
-
def format(self, ob: model.Documentable, field:str) -> Optional[str]:
try:
return getattr(self, f'format_{field}')(ob) #type:ignore[no-any-return]
@@ -112,14 +104,9 @@ def format_kind(self, ob:model.Documentable) -> str:
def get_corpus(self) -> List[Tuple[Dict[str, Optional[str]], Dict[str, int]]]:
return [
- (
- {
- f:self.format(ob, f) for f in self.fields
- },
- {
- "boost": self.get_ob_boost(ob)
- }
- )
+ {
+ f:self.format(ob, f) for f in self.fields
+ }
for ob in (o for o in self.system.allobjects.values() if o.isVisible)
]
From 8624eaec98d24ef33089d6e270c9fcaa958b7459 Mon Sep 17 00:00:00 2001
From: tristanlatr <19967168+tristanlatr@users.noreply.github.com>
Date: Mon, 10 Nov 2025 21:01:01 +0000
Subject: [PATCH 06/14] Fix annotation
---
pydoctor/templatewriter/search.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/pydoctor/templatewriter/search.py b/pydoctor/templatewriter/search.py
index c952ad3ce..e16fcf784 100644
--- a/pydoctor/templatewriter/search.py
+++ b/pydoctor/templatewriter/search.py
@@ -4,7 +4,7 @@
from __future__ import annotations
from pathlib import Path
-from typing import Iterator, List, Optional, Tuple, Type, Dict, TYPE_CHECKING
+from typing import Iterator, List, Optional, Type, Dict, TYPE_CHECKING
import json
import attr
@@ -102,7 +102,7 @@ def format_docstring(self, ob: model.Documentable) -> Optional[str]:
def format_kind(self, ob:model.Documentable) -> str:
return epydoc2stan.format_kind(ob.kind) if ob.kind else ''
- def get_corpus(self) -> List[Tuple[Dict[str, Optional[str]], Dict[str, int]]]:
+ def get_corpus(self) -> list[dict[str, str | None]]:
return [
{
f:self.format(ob, f) for f in self.fields
From 3388a4b80063fdb6af5a633263a08b02facd86cc Mon Sep 17 00:00:00 2001
From: tristanlatr <19967168+tristanlatr@users.noreply.github.com>
Date: Tue, 11 Nov 2025 17:12:08 -0500
Subject: [PATCH 07/14] Order is not important for these search results
---
docs/tests/test.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/tests/test.py b/docs/tests/test.py
index d768e357f..b0ac7fe06 100644
--- a/docs/tests/test.py
+++ b/docs/tests/test.py
@@ -190,7 +190,7 @@ def test_search(query:str, expected:List[str], order_is_important:bool=True) ->
'pydoctor.factory.Factory.Class',
'pydoctor.model.DocumentableKind.CLASS',
'pydoctor.model.System.Class',
- ])
+ ], order_is_important=False)
to_stan_results = [
'pydoctor.epydoc.markup.ParsedDocstring.to_stan',
From 5b428c6156cc55226669aeb2203c968061c8581e Mon Sep 17 00:00:00 2001
From: tristanlatr <19967168+tristanlatr@users.noreply.github.com>
Date: Wed, 12 Nov 2025 21:03:46 -0500
Subject: [PATCH 08/14] Apply suggestions from code review
---
pydoctor/templatewriter/summary.py | 1 +
pydoctor/themes/base/search.js | 4 ++--
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/pydoctor/templatewriter/summary.py b/pydoctor/templatewriter/summary.py
index 2466dbb01..50e3d9ecb 100644
--- a/pydoctor/templatewriter/summary.py
+++ b/pydoctor/templatewriter/summary.py
@@ -471,6 +471,7 @@ class HelpPage(Page):
- ``doc`` -> matches names containing tokens that start with "doc" (equivalent to "doc*").
- ``ensure doc`` -> matches object whose matches "doc*" and "ensure*".
+ - ``doc kind:class`` -> matches classes whose matches "doc*".
- ``docstring:ansi`` -> matches object whose docstring matches "ansi*".
- ``+doc`` -> matches only where a token equals "doc" exactly.
- ``-test`` -> excludes any result containing a token equal to "test".
diff --git a/pydoctor/themes/base/search.js b/pydoctor/themes/base/search.js
index 88ade68c2..642494672 100644
--- a/pydoctor/themes/base/search.js
+++ b/pydoctor/themes/base/search.js
@@ -160,8 +160,8 @@ function _stopSearchingProcess(){
// Values configuring the search-as-you-type feature.
var SEARCH_DEFAULT_DELAY = 150; // in miliseconds
var SEARCH_INCREASED_DELAY = 300; // in miliseconds
-var SEARCH_INDEX_SIZE_TRESH_INCREASE_DELAY = 20; // in MB
-var SEARCH_INDEX_SIZE_TRESH_DISABLE_SEARCH_AS_YOU_TYPE = 35; // in MB
+var SEARCH_INDEX_SIZE_TRESH_INCREASE_DELAY = 15; // in MB
+var SEARCH_INDEX_SIZE_TRESH_DISABLE_SEARCH_AS_YOU_TYPE = 30; // in MB
// Search delay depends on index size in MB
function _getIndexSizePromise(indexURL){
From 07a4254a44ec1efdde73588903e1c1baa8052f2c Mon Sep 17 00:00:00 2001
From: tristanlatr <19967168+tristanlatr@users.noreply.github.com>
Date: Thu, 11 Dec 2025 01:03:07 +0000
Subject: [PATCH 09/14] Use a better box shadow for the selected results
---
pydoctor/themes/base/apidocs.css | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pydoctor/themes/base/apidocs.css b/pydoctor/themes/base/apidocs.css
index efb1b11a1..844f29b46 100644
--- a/pydoctor/themes/base/apidocs.css
+++ b/pydoctor/themes/base/apidocs.css
@@ -1152,7 +1152,7 @@ input[type="search"]::-webkit-search-results-decoration { display: none; }
}
.search-result-selected {
- box-shadow: inset -5px 0px 0px 1px rgb(109 161 219 / 70%);
+ box-shadow: 5px 0px 4px 1px rgba(109, 161, 219, 0.7), inset 5px 0px 4px 0px rgba(109, 161, 219, 0.7);
}
/* Constant values repr */
From cf4bb4bb415aacd72007ff841e3914e43daf2e08 Mon Sep 17 00:00:00 2001
From: tristanlatr <19967168+tristanlatr@users.noreply.github.com>
Date: Thu, 11 Dec 2025 05:30:57 +0000
Subject: [PATCH 10/14] Says when the JS files are still loading
---
pydoctor/themes/base/search.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/pydoctor/themes/base/search.js b/pydoctor/themes/base/search.js
index 642494672..aa82dec42 100644
--- a/pydoctor/themes/base/search.js
+++ b/pydoctor/themes/base/search.js
@@ -202,6 +202,7 @@ function searchAsYouType(){
if (input.value.length>0){
showResultContainer();
}
+ setStatus("Loading...");
_getIndexSizePromise("searchindex.json").then((indexSizeApprox) => {
if (indexSizeApprox > SEARCH_INDEX_SIZE_TRESH_DISABLE_SEARCH_AS_YOU_TYPE){
// Not searching as we type if "default" index size if greater than 20MB.
From 8cf66b9eb142a7e8a3626c254108c6ce910ce93f Mon Sep 17 00:00:00 2001
From: tristanlatr <19967168+tristanlatr@users.noreply.github.com>
Date: Thu, 11 Dec 2025 05:47:16 +0000
Subject: [PATCH 11/14] Reload queyr when pressing enter and no result is
selected
---
pydoctor/themes/base/search.js | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/pydoctor/themes/base/search.js b/pydoctor/themes/base/search.js
index aa82dec42..148f839d1 100644
--- a/pydoctor/themes/base/search.js
+++ b/pydoctor/themes/base/search.js
@@ -205,7 +205,7 @@ function searchAsYouType(){
setStatus("Loading...");
_getIndexSizePromise("searchindex.json").then((indexSizeApprox) => {
if (indexSizeApprox > SEARCH_INDEX_SIZE_TRESH_DISABLE_SEARCH_AS_YOU_TYPE){
- // Not searching as we type if "default" index size if greater than 20MB.
+ // Not searching as we type if "default" index size is greater than a certain treshold.
if (input.value.length===0){ // No actual query, this only resets some UI components.
launchSearch();
}
@@ -399,11 +399,14 @@ input.oninput = (event) => {
searchAsYouType();
}, 0);
};
-input.onkedown = (event) => {
+input.addEventListener('keydown', (event) => {
if (event.key === 'Enter') {
- launchSearch(true);
+ if (_getSelectedSearchResult() === -1){
+ // Only manually launch the search when no result is selected.
+ launchSearch(true);
+ }
}
-};
+});
input.onfocus = (event) => {
// Ensure the search bar is set-up.
// Load fullsearchindex.json, searchindex.json and all-documents.html to have them in the cache asap.
From e91ff5e81810f01aaab6542b22dfbe75a53e5d2c Mon Sep 17 00:00:00 2001
From: tristanlatr <19967168+tristanlatr@users.noreply.github.com>
Date: Fri, 12 Dec 2025 02:49:10 +0000
Subject: [PATCH 12/14] Ue background color property instead of box-shadow for
the selected item in search results
---
pydoctor/themes/base/apidocs.css | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/pydoctor/themes/base/apidocs.css b/pydoctor/themes/base/apidocs.css
index 844f29b46..cc21ef9ca 100644
--- a/pydoctor/themes/base/apidocs.css
+++ b/pydoctor/themes/base/apidocs.css
@@ -1151,8 +1151,8 @@ input[type="search"]::-webkit-search-results-decoration { display: none; }
cursor: pointer;
}
-.search-result-selected {
- box-shadow: 5px 0px 4px 1px rgba(109, 161, 219, 0.7), inset 5px 0px 4px 0px rgba(109, 161, 219, 0.7);
+.search-result-selected > * {
+ background-color: rgba(109, 161, 219, 0.2)!important;;
}
/* Constant values repr */
From e9db5b54a9b222976ce22f979aaa3407f0ece592 Mon Sep 17 00:00:00 2001
From: tristanlatr <19967168+tristanlatr@users.noreply.github.com>
Date: Wed, 28 Jan 2026 14:56:16 -0500
Subject: [PATCH 13/14] Apply suggestions from code review
---
pydoctor/themes/base/apidocs.css | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/pydoctor/themes/base/apidocs.css b/pydoctor/themes/base/apidocs.css
index cc21ef9ca..cd3b2f4b7 100644
--- a/pydoctor/themes/base/apidocs.css
+++ b/pydoctor/themes/base/apidocs.css
@@ -1151,8 +1151,12 @@ input[type="search"]::-webkit-search-results-decoration { display: none; }
cursor: pointer;
}
-.search-result-selected > * {
- background-color: rgba(109, 161, 219, 0.2)!important;;
+.search-result-selected{
+ background-color: rgba(109, 161, 219, 0.2);
+}
+
+.search-result-selected > *:first-child{
+ background-color: transparent;
}
/* Constant values repr */
From d1576fb0decdc3b917149c6a3e4e5a62bef1dcd0 Mon Sep 17 00:00:00 2001
From: tristanlatr <19967168+tristanlatr@users.noreply.github.com>
Date: Wed, 28 Jan 2026 21:42:45 -0500
Subject: [PATCH 14/14] Add important statement to CSS such that selection
style also applies to private items
---
pydoctor/themes/base/apidocs.css | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/pydoctor/themes/base/apidocs.css b/pydoctor/themes/base/apidocs.css
index cd3b2f4b7..7e5a9520f 100644
--- a/pydoctor/themes/base/apidocs.css
+++ b/pydoctor/themes/base/apidocs.css
@@ -1152,11 +1152,11 @@ input[type="search"]::-webkit-search-results-decoration { display: none; }
}
.search-result-selected{
- background-color: rgba(109, 161, 219, 0.2);
+ background-color: rgba(109, 161, 219, 0.2)!important;
}
.search-result-selected > *:first-child{
- background-color: transparent;
+ background-color: transparent!important;
}
/* Constant values repr */