Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
## [Unreleased]

### Added
- **GUI: a "Sort by relevance" checkbox in Advanced Search Options** — the
desktop equivalent of `--rank`. Orders Standard Search matches by relevance;
needs the index (a tooltip says so). Persists with your other saved settings.
Ranking is now available in all four surfaces: CLI, Python API, MCP, and GUI.
- **MCP `search_documents` gains a `rank` option — relevance-order the matches
the assistant sees.** When results are capped, ranking makes the returned
window the *most relevant* matches rather than just the first ones. Opt-in
Expand Down
2 changes: 1 addition & 1 deletion docs/USER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ The Search Wizard has its own **Change Folder** button and operates independentl

**Advanced Search Options:**

Click the **▶ Advanced Search Options** header to expand the inline panel in the left pane (the chevron flips to **▼**). The panel carries every search-tuning option: AND mode, recursive search, fuzzy matching, wildcards, OCR, regex, whole-word matching, expression mode, inverse search, **Use Index**, exclude terms, file type filtering, proximity, context lines, CPU cores, max matches, range filters, specific files, save as, append to, output directory, **Redirect cloud-synced output paths to `~/peekdocs_reports`** (the sticky cloud-output guard — silently redirects report writes when the output folder is inside OneDrive / Google Drive / iCloud Drive / Dropbox; see [Cloud-synced folders](#cloud-synced-folders)), additional output formats (CSV, JSON, PDF, HTML), timestamp filenames, **Delete on Close**, **Clear history on close**, Restrict permissions, and Notify on Search Complete. Every terminal flag is available in the GUI. You don't need any of them for a basic search. Hover over any option to see a description of what it does. At the bottom of the panel are two button rows: top row holds **Save Defaults** (saves your current search terms, folder, and all options as defaults to `~/.peekdocsrc` — the next time you open the GUI, everything will be pre-filled), **Restore Saved Defaults** (reloads saved defaults from `~/.peekdocsrc` into the GUI), and **Inspect .peekdocsrc** (shows the current saved settings, read-only). Bottom row holds the destructive pair, both red: **Reset All Fields** (clears fields and restores the GUI to its default state — current session only; saved defaults in `~/.peekdocsrc` are unchanged unless you also click Save Defaults after resetting), and **Restore Factory Settings** (deletes `~/.peekdocsrc` entirely and returns everything to first-launch defaults — destructive, this cannot be undone). To collapse the panel, click the **▼ Advanced Search Options** header at the top of the panel (the chevron flips back to **▶**). Your settings persist for the next search either way.
Click the **▶ Advanced Search Options** header to expand the inline panel in the left pane (the chevron flips to **▼**). The panel carries every search-tuning option: AND mode, recursive search, fuzzy matching, wildcards, OCR, regex, whole-word matching, expression mode, inverse search, **Use Index**, **Sort by relevance** (orders matches most-relevant-first — needs Use Index), exclude terms, file type filtering, proximity, context lines, CPU cores, max matches, range filters, specific files, save as, append to, output directory, **Redirect cloud-synced output paths to `~/peekdocs_reports`** (the sticky cloud-output guard — silently redirects report writes when the output folder is inside OneDrive / Google Drive / iCloud Drive / Dropbox; see [Cloud-synced folders](#cloud-synced-folders)), additional output formats (CSV, JSON, PDF, HTML), timestamp filenames, **Delete on Close**, **Clear history on close**, Restrict permissions, and Notify on Search Complete. Every terminal flag is available in the GUI. You don't need any of them for a basic search. Hover over any option to see a description of what it does. At the bottom of the panel are two button rows: top row holds **Save Defaults** (saves your current search terms, folder, and all options as defaults to `~/.peekdocsrc` — the next time you open the GUI, everything will be pre-filled), **Restore Saved Defaults** (reloads saved defaults from `~/.peekdocsrc` into the GUI), and **Inspect .peekdocsrc** (shows the current saved settings, read-only). Bottom row holds the destructive pair, both red: **Reset All Fields** (clears fields and restores the GUI to its default state — current session only; saved defaults in `~/.peekdocsrc` are unchanged unless you also click Save Defaults after resetting), and **Restore Factory Settings** (deletes `~/.peekdocsrc` entirely and returns everything to first-launch defaults — destructive, this cannot be undone). To collapse the panel, click the **▼ Advanced Search Options** header at the top of the panel (the chevron flips back to **▶**). Your settings persist for the next search either way.

> **Which checkboxes auto-save vs require Save Defaults.** Most Advanced Search Options checkboxes only persist across sessions if you click **Save Defaults** at the bottom of the panel. Three checkboxes are treated as session defaults and auto-save on every toggle: **Recursive**, **Whole Word**, and **Use Index**. The rest (**AND/OR mode**, **Fuzzy**, **Wildcard**, **Regex**, **OCR**, **Expression**, **Inverse**) are per-search toggles — they apply to the next search but reset to OFF the next time you launch peekdocs unless you click **Save Defaults** after flipping them. **OCR** specifically is non-persisting by design: Tesseract can take many seconds per scanned PDF, so silently leaving OCR on across sessions would surprise users with much slower runs they didn't ask for. If you regularly search scanned documents, click **Save Defaults** with OCR checked once, and it'll be on at every launch.

Expand Down
3 changes: 3 additions & 0 deletions peekdocs/gui/_cli_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ def _build_command_from_values(
inverse=False,
expression=False,
whole_word=False,
rank=False,
max_matches="",
max_file_size_mb="",
timestamp_suffix="",
Expand Down Expand Up @@ -281,6 +282,8 @@ def _build_command_from_values(
cmd.append("-x")
if whole_word:
cmd.append("-W")
if rank:
cmd.append("--rank")
if inverse:
cmd.append("--inverse")

Expand Down
10 changes: 10 additions & 0 deletions peekdocs/gui/_mixin_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ def _build_search_row(self):
self.and_mode_var = ctk.StringVar(value="off")
self.whole_word_var = ctk.StringVar(value="on")
self.index_search_var = ctk.StringVar(value="off")
self.rank_var = ctk.StringVar(value="off")

import tkinter as _tk_step2
from peekdocs.i18n import t as _t
Expand Down Expand Up @@ -746,6 +747,15 @@ def _build_advanced_panel(self):
self._adv_cb_whole_word.grid(row=2, column=2, padx=(0, 15), pady=(5, 0), sticky="w")
Tooltip(self._adv_cb_whole_word, "Matches complete words only. 'bob' matches 'bob' but not 'bobcat'.")

self._adv_cb_rank = ctk.CTkCheckBox(
cb_frame, text="Sort by relevance", variable=self.rank_var,
onvalue="on", offvalue="off",
command=lambda: self._save_ui_preference("rank", self.rank_var.get() == "on"),
)
self._adv_cb_rank.grid(row=3, column=0, padx=(0, 15), pady=(5, 0), sticky="w")
Tooltip(self._adv_cb_rank, "Order matches by relevance (most relevant first) instead of file order. "
"Needs the search index (turn on Use Index); otherwise results stay in file order.")

# Use Index used to sit at row 1 / col 4 of this grid. It now
# lives below the Output Dir entry — see the cb_index_search
# creation after that row.
Expand Down
4 changes: 4 additions & 0 deletions peekdocs/gui/_mixin_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ def _save_current_settings(self):
settings["inverse"] = (self.inverse_var.get() == "on")
settings["expression"] = (self.expression_var.get() == "on")
settings["whole_word"] = (self.whole_word_var.get() == "on")
settings["rank"] = (self.rank_var.get() == "on")
settings["timestamp"] = (self.timestamp_var.get() == "on")
# Integer settings
cores_val = self.cores_entry.get().strip()
Expand Down Expand Up @@ -194,6 +195,7 @@ def _load_saved_settings(self):
self.inverse_var.set("on" if config.get("inverse") else "off")
self.expression_var.set("on" if config.get("expression") else "off")
self.whole_word_var.set("on" if config.get("whole_word", _whole_word_default) else "off")
self.rank_var.set("on" if config.get("rank", False) else "off")
self.timestamp_var.set("on" if config.get("timestamp", False) else "off")
self.delete_reports_var.set("on" if config.get("delete_reports_on_close", False) else "off")
self.clear_history_var.set("on" if config.get("clear_history_on_close", False) else "off")
Expand Down Expand Up @@ -1054,6 +1056,7 @@ def _collect_gui_params(self):
"inverse": self.inverse_var.get() == "on",
"expression": self.expression_var.get() == "on",
"whole_word": self.whole_word_var.get() == "on",
"rank": self.rank_var.get() == "on",
"output_docx": self.output_docx_var.get() == "on",
"output_csv": self.output_csv_var.get() == "on",
"output_json": self.output_json_var.get() == "on",
Expand Down Expand Up @@ -1101,6 +1104,7 @@ def _apply_params_to_gui(self, params):
self.inverse_var.set("on" if params.get("inverse") else "off")
self.expression_var.set("on" if params.get("expression") else "off")
self.whole_word_var.set("on" if params.get("whole_word") else "off")
self.rank_var.set("on" if params.get("rank") else "off")
if params.get("expression"):
self.search_entry.configure(placeholder_text='e.g. (budget OR revenue) AND NOT draft')
else:
Expand Down
2 changes: 2 additions & 0 deletions peekdocs/gui/_mixin_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ def start_search(self):
inverse=self.inverse_var.get() == "on",
expression=self.expression_var.get() == "on",
whole_word=self.whole_word_var.get() == "on",
rank=self.rank_var.get() == "on",
max_matches=self.max_matches_entry.get(),
max_file_size_mb=self.max_file_size_entry.get(),
timestamp_suffix=self._last_ts_suffix,
Expand Down Expand Up @@ -394,6 +395,7 @@ def _multi_folder_thread(self, folders, search_text):
inverse=self.inverse_var.get() == "on",
expression=self.expression_var.get() == "on",
whole_word=self.whole_word_var.get() == "on",
rank=self.rank_var.get() == "on",
max_matches=self.max_matches_entry.get(),
max_file_size_mb=self.max_file_size_entry.get(),
range_filters=self.range_entry.get(),
Expand Down
1 change: 1 addition & 0 deletions peekdocs/gui/_mixin_suites.py
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,7 @@ def _run():
inverse=params.get("inverse", False),
expression=params.get("expression", False),
whole_word=params.get("whole_word", False),
rank=params.get("rank", False),
max_matches=str(params.get("max_matches", "")) if params.get("max_matches") else "",
max_file_size_mb=str(params.get("max_file_size_mb", "")) if params.get("max_file_size_mb") else "",
range_filters=params.get("range_filters", ""),
Expand Down
19 changes: 19 additions & 0 deletions tests/test_gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,25 @@ def test_build_command_basic(tmp_path):
assert "-r" not in cmd


def test_build_command_rank(tmp_path):
on = _build_command_from_values(
search_text="budget", folder=str(tmp_path),
and_mode=False, recursive=False, fuzzy=False,
wildcard=False, ocr=False, regex=False,
exclude="", file_types="", proximity="",
context_before="", context_after="", index_search=True, rank=True,
)
assert "--rank" in on
off = _build_command_from_values(
search_text="budget", folder=str(tmp_path),
and_mode=False, recursive=False, fuzzy=False,
wildcard=False, ocr=False, regex=False,
exclude="", file_types="", proximity="",
context_before="", context_after="", index_search=True, rank=False,
)
assert "--rank" not in off


def test_build_command_all_flags(tmp_path):
cmd = _build_command_from_values(
search_text="hello world",
Expand Down
Loading