Skip to content

Commit 3e9cbcc

Browse files
committed
Implement new search: support multiple words
Previously, a search would only return results with exact matches, that is if the search string is a substring of the text in the window buffer. With this new edition, we support searching for multiple words. A result would only be shown if *all* of those words appear at least once in the text.
1 parent 7f50419 commit 3e9cbcc

File tree

6 files changed

+67
-58
lines changed

6 files changed

+67
-58
lines changed

rednotebook/data.py

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -178,14 +178,14 @@ def get_words(self, with_special_chars=False):
178178
def get_number_of_words(self):
179179
return len(self.get_words(with_special_chars=True))
180180

181-
def search(self, text, tags):
181+
def search(self, queries, tags):
182182
"""
183183
This method is only called for days that have all given tags.
184184
Search in date first, then in the text, then in the tags.
185185
Uses case-insensitive search.
186186
"""
187187
results = []
188-
if not text:
188+
if not queries:
189189
# Only add text result once for all tags.
190190
add_text_to_results = False
191191
for day_tag, entries in self.get_category_content_pairs().items():
@@ -200,41 +200,46 @@ def search(self, text, tags):
200200
add_text_to_results = True
201201
if add_text_to_results:
202202
results.append(get_text_with_dots(self.text, 0, TEXT_RESULT_LENGTH))
203-
elif text in str(self):
204-
# Date contains searched text.
203+
elif any(text in str(self) for text in queries):
204+
# Any of the query matches with the date.
205205
results.append(get_text_with_dots(self.text, 0, TEXT_RESULT_LENGTH))
206206
else:
207-
text_result = self.search_in_text(text)
207+
text_result = self.search_in_text(queries)
208208
if text_result:
209209
results.append(text_result)
210-
results.extend(self.search_in_categories(text))
210+
results.extend(self.search_in_categories(queries))
211211
return str(self), results
212212

213-
def search_in_text(self, search_text):
214-
occurence = self.text.upper().find(search_text.upper())
215-
216-
# Check if search_text is in text
217-
if occurence < 0:
218-
return None
219-
220-
found_text = self.text[occurence : occurence + len(search_text)]
221-
result_text = get_text_with_dots(
222-
self.text, occurence, occurence + len(search_text), found_text
213+
def search_in_text(self, queries):
214+
"""All queries should be present in the text"""
215+
matches = {}
216+
for query in queries:
217+
occurrence = self.text.upper().find(query.upper())
218+
if occurrence < 0:
219+
return
220+
matches[query] = occurrence
221+
222+
first_query = min(matches, key=matches.get)
223+
first_occurrence = matches[first_query]
224+
225+
found_text = self.text[first_occurrence : first_occurrence + len(first_query)]
226+
return get_text_with_dots(
227+
self.text, first_occurrence, first_occurrence + len(first_query), found_text
223228
)
224-
return result_text
225229

226-
def search_in_categories(self, text):
230+
def search_in_categories(self, queries):
227231
results = []
228-
for category, content in self.get_category_content_pairs().items():
229-
if content:
230-
if text.upper() in category.upper():
231-
results.extend(content)
232-
else:
233-
results.extend(
234-
entry for entry in content if text.upper() in entry.upper()
235-
)
236-
elif text.upper() in category.upper():
237-
results.append(category)
232+
for query in queries:
233+
for category, content in self.get_category_content_pairs().items():
234+
if content:
235+
if query.upper() in category.upper():
236+
results.extend(content)
237+
else:
238+
results.extend(
239+
entry for entry in content if query.upper() in entry.upper()
240+
)
241+
elif query.upper() in category.upper():
242+
results.append(category)
238243
return results
239244

240245
def __str__(self):

rednotebook/gui/browser.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,13 @@ def set_font_size(self, size):
6565
zoom *= 0.90
6666
self.set_zoom_level(zoom)
6767

68-
def highlight(self, search_text):
68+
def highlight(self, search_queries):
6969
# Tell the webview which text to highlight after the html is loaded
70-
self.search_text = search_text
71-
self.get_find_controller().search(
72-
self.search_text, WebKit2.FindOptions.CASE_INSENSITIVE, MAX_HITS
73-
)
70+
self.search_queries = search_queries
71+
for query in search_queries:
72+
self.get_find_controller().search(
73+
query, WebKit2.FindOptions.CASE_INSENSITIVE, MAX_HITS
74+
)
7475

7576
def on_load_changed(self, webview, event):
7677
"""
@@ -82,7 +83,7 @@ def on_load_changed(self, webview, event):
8283
does not work.
8384
"""
8485
if event == WebKit2.LoadEvent.FINISHED:
85-
if self.search_text:
86-
self.highlight(self.search_text)
86+
if self.search_queries:
87+
self.highlight(self.search_queries)
8788
else:
8889
webview.get_find_controller().search_finish()

rednotebook/gui/editor.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,8 @@ def replace_selection_and_highlight(self, p1, p2, p3):
121121
end.backward_chars(len(p3))
122122
self.day_text_buffer.select_range(start, end)
123123

124-
def highlight(self, text):
125-
self.search_text = text
124+
def highlight(self, queries):
125+
self.search_queries = queries
126126
buf = self.day_text_buffer
127127

128128
# Clear previous highlighting
@@ -131,8 +131,8 @@ def highlight(self, text):
131131
buf.remove_tag_by_name("highlighter", start, end)
132132

133133
# Highlight matches
134-
if text:
135-
for match_start, match_end in self.iter_search_matches(text):
134+
for query in enumerate(queries):
135+
for match_start, match_end in self.iter_search_matches(query):
136136
buf.apply_tag_by_name("highlighter", match_start, match_end)
137137

138138
search_flags = (

rednotebook/gui/main_window.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ def show_day(self, new_day):
169169
)
170170
self.load_html(html)
171171

172-
def highlight(self, text):
172+
def highlight(self, queries):
173173
pass
174174

175175
self.html_editor = Preview(self.journal)
@@ -724,9 +724,9 @@ def set_date(self, new_month, new_date, day):
724724
def get_day_text(self):
725725
return self.day_text_field.get_text()
726726

727-
def highlight_text(self, search_text):
728-
self.html_editor.highlight(search_text)
729-
self.day_text_field.highlight(search_text)
727+
def highlight_text(self, search_queries):
728+
self.html_editor.highlight(search_queries)
729+
self.day_text_field.highlight(search_queries)
730730

731731
def show_message(self, title, msg, msg_type):
732732
if msg_type == Gtk.MessageType.ERROR:

rednotebook/gui/search.py

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -60,18 +60,21 @@ def search(self, search_text):
6060
else:
6161
queries.append(part)
6262

63-
search_text = " ".join(queries)
64-
65-
# Highlight all occurences in the current day's text
66-
self.main_window.highlight_text(search_text)
63+
# Highlight all occurrences in the current day's text
64+
self.main_window.highlight_text(queries)
6765

6866
# Scroll to query.
69-
if search_text:
70-
GObject.idle_add(
71-
self.main_window.day_text_field.scroll_to_text, search_text
72-
)
73-
74-
self.main_window.search_tree_view.update_data(search_text, tags)
67+
if queries:
68+
# TODO: Decide where to scroll to?
69+
# To scroll to the first match, we'd need to search for the first
70+
# occurrence of each query, and scroll to the first one. But I am
71+
# not sure if that is ideal performance-wise.
72+
# Would it make sense to inspect the data returned by
73+
# `self.journal.search` -- which will have information on what
74+
# query matched fist, and it's index?
75+
pass
76+
77+
self.main_window.search_tree_view.update_data(queries, tags)
7578

7679
# Without the following, showing the search results sometimes lets the
7780
# search entry lose focus and search phrases are added to a day's text.
@@ -89,18 +92,18 @@ def __init__(self, main_window, always_show_results):
8992

9093
self.connect("cursor_changed", self.on_cursor_changed)
9194

92-
def update_data(self, search_text, tags):
95+
def update_data(self, queries, tags):
9396
self.tree_store.clear()
9497

95-
if not self.always_show_results and not tags and not search_text:
98+
if not self.always_show_results and not tags and not queries:
9699
self.main_window.cloud.show()
97100
self.main_window.search_scroll.hide()
98101
return
99102

100103
self.main_window.cloud.hide()
101104
self.main_window.search_scroll.show()
102105

103-
for date_string, entries in self.journal.search(search_text, tags):
106+
for date_string, entries in self.journal.search(queries, tags):
104107
for entry in entries:
105108
entry = escape(entry)
106109
entry = entry.replace("STARTBOLD", "<b>").replace("ENDBOLD", "</b>")

rednotebook/journal.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -506,10 +506,10 @@ def get_entries(self, category):
506506
entries |= set(day.get_entries(category))
507507
return sorted(entries)
508508

509-
def search(self, text, tags):
509+
def search(self, queries, tags):
510510
results = []
511511
for day in reversed(self.get_days_with_tags(tags)):
512-
results.append(day.search(text, tags))
512+
results.append(day.search(queries, tags))
513513
return results
514514

515515
def get_days_with_tags(self, tags):

0 commit comments

Comments
 (0)