2026-09-05 03:09:12 -04:00
|
|
|
//! Plain-language tooltips: every configuration control, and any status line
|
|
|
|
|
//! whose wait needs explaining, explains itself on hover.
|
2026-08-09 16:25:43 -04:00
|
|
|
|
2026-08-05 18:05:04 -04:00
|
|
|
const TIP_WIDTH: f32 = 420.0;
|
|
|
|
|
|
|
|
|
|
pub struct Tip {
|
|
|
|
|
pub title: &'static str,
|
|
|
|
|
pub body: &'static str,
|
|
|
|
|
/// Concrete values and when to choose them, rendered small underneath.
|
|
|
|
|
pub examples: &'static [&'static str],
|
2026-08-23 00:33:53 -04:00
|
|
|
/// Rendered small and orange; reserved for rebuilds and deletions.
|
2026-08-05 18:05:04 -04:00
|
|
|
pub caution: Option<&'static str>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Tip {
|
|
|
|
|
pub fn show(&self, ui: &mut egui::Ui) {
|
|
|
|
|
ui.set_max_width(TIP_WIDTH);
|
|
|
|
|
ui.strong(self.title);
|
|
|
|
|
ui.label(self.body);
|
|
|
|
|
if !self.examples.is_empty() {
|
|
|
|
|
ui.add_space(4.0);
|
|
|
|
|
let single = self.examples.len() == 1;
|
|
|
|
|
for example in self.examples {
|
|
|
|
|
let line = if single {
|
|
|
|
|
format!("Example: {}", example)
|
|
|
|
|
} else {
|
|
|
|
|
format!("• {}", example)
|
|
|
|
|
};
|
|
|
|
|
ui.label(egui::RichText::new(line).small());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if let Some(caution) = self.caution {
|
|
|
|
|
ui.add_space(4.0);
|
2026-08-09 02:58:13 -04:00
|
|
|
let caution_color = crate::color::palette(ui.visuals().dark_mode).orange;
|
2026-08-09 16:25:43 -04:00
|
|
|
ui.label(crate::ui_util::hint_colored(caution, caution_color));
|
2026-08-05 18:05:04 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub trait Tipped {
|
2026-08-23 00:33:53 -04:00
|
|
|
/// Shown even while disabled: a greyed-out button is exactly when
|
|
|
|
|
/// someone wants to know what it would do.
|
2026-08-05 18:05:04 -04:00
|
|
|
fn tip(self, tip: &'static Tip) -> Self;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Tipped for egui::Response {
|
|
|
|
|
fn tip(self, tip: &'static Tip) -> egui::Response {
|
|
|
|
|
self.on_hover_ui(|ui| tip.show(ui))
|
|
|
|
|
.on_disabled_hover_ui(|ui| tip.show(ui))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-23 00:33:53 -04:00
|
|
|
/// One grid row; the label and the control share the tooltip.
|
2026-08-05 18:05:04 -04:00
|
|
|
pub fn tip_row(
|
|
|
|
|
ui: &mut egui::Ui,
|
2026-08-25 01:04:36 -04:00
|
|
|
label: impl Into<egui::WidgetText>,
|
2026-08-05 18:05:04 -04:00
|
|
|
tip: &'static Tip,
|
|
|
|
|
widget: impl FnOnce(&mut egui::Ui) -> egui::Response,
|
|
|
|
|
) {
|
|
|
|
|
ui.label(label).tip(tip);
|
|
|
|
|
widget(ui).tip(tip);
|
|
|
|
|
ui.end_row();
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-25 01:04:36 -04:00
|
|
|
// --- Settings: the advanced toggle ----------------------------------------
|
|
|
|
|
|
|
|
|
|
pub static SHOW_ADVANCED: Tip = Tip {
|
|
|
|
|
title: "Show advanced settings",
|
2026-09-25 14:58:51 -04:00
|
|
|
body: "Shows the rest of the Settings tab: the index location, how text \
|
|
|
|
|
is split for searching, how much of each file is read, search \
|
|
|
|
|
memory, and more.\n\n\
|
|
|
|
|
They are hidden because the defaults suit almost every \
|
|
|
|
|
installation and a wrong value can slow indexing, worsen searching \
|
|
|
|
|
or force a rebuild. Hidden settings keep working at their defaults.",
|
2026-08-25 01:04:36 -04:00
|
|
|
examples: &[
|
2026-09-25 14:58:51 -04:00
|
|
|
"on to move the index to another drive, or to tune a very large collection.",
|
2026-08-25 01:04:36 -04:00
|
|
|
"off for everyday use.",
|
|
|
|
|
],
|
|
|
|
|
caution: None,
|
|
|
|
|
};
|
|
|
|
|
|
2026-08-17 19:26:18 -04:00
|
|
|
// --- Settings: Paths ------------------------------------------------------
|
2026-08-05 18:05:04 -04:00
|
|
|
|
|
|
|
|
pub static DATABASE_PATH: Tip = Tip {
|
|
|
|
|
title: "Database file",
|
2026-09-25 14:58:51 -04:00
|
|
|
body: "The single file holding the index: the names, locations and text \
|
|
|
|
|
of everything indexed. It grows with your files, so put it on a \
|
|
|
|
|
drive with room to spare.\n\n\
|
|
|
|
|
Changing this switches to the index at the new location, building \
|
|
|
|
|
one if none exists; the old file stays until you delete it. Keep \
|
|
|
|
|
it out of cloud-synced folders such as OneDrive or Dropbox, which \
|
|
|
|
|
cannot keep up with it.",
|
|
|
|
|
examples: &["a path on a second, larger drive when the system drive is short of space."],
|
2026-08-05 18:05:04 -04:00
|
|
|
caution: None,
|
|
|
|
|
};
|
|
|
|
|
|
2026-08-17 19:26:18 -04:00
|
|
|
// --- Settings: Indexing ---------------------------------------------------
|
2026-08-05 18:05:04 -04:00
|
|
|
|
|
|
|
|
pub static REINDEX_INTERVAL: Tip = Tip {
|
|
|
|
|
title: "Full reindex every",
|
2026-09-25 14:58:51 -04:00
|
|
|
body: "Indexed folders are watched, so most changes appear within \
|
|
|
|
|
seconds. This full sweep is the safety net: it picks up what \
|
|
|
|
|
watching missed, such as changes made while QuickSearch was closed \
|
|
|
|
|
or on a drive that does not report them.\n\n\
|
|
|
|
|
A sweep costs a few minutes of disk activity; searching keeps \
|
|
|
|
|
working throughout.",
|
2026-08-05 18:05:04 -04:00
|
|
|
examples: &[
|
|
|
|
|
"1440, once a day, suits an ordinary laptop.",
|
2026-09-25 14:58:51 -04:00
|
|
|
"60 on a network share, or a drive other machines write to.",
|
2026-08-05 18:05:04 -04:00
|
|
|
"10080, once a week, for an archive that barely changes.",
|
|
|
|
|
],
|
|
|
|
|
caution: None,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub static FOLLOW_SYMLINKS: Tip = Tip {
|
|
|
|
|
title: "Follow symlinks",
|
2026-09-25 14:58:51 -04:00
|
|
|
body: "A symlink stands in for a file or folder stored somewhere else. \
|
|
|
|
|
Off, QuickSearch skips them, so nothing is indexed twice under two \
|
|
|
|
|
names. On, it indexes what they point at, under the real location \
|
|
|
|
|
rather than the link's.\n\n\
|
|
|
|
|
Turning this off removes the entries now out of reach; turning it \
|
|
|
|
|
on indexes what it now reaches. Neither rebuilds the index.",
|
2026-08-05 18:05:04 -04:00
|
|
|
examples: &[
|
|
|
|
|
"on when a folder you search lives outside your indexed folders and is reached \
|
|
|
|
|
through a link.",
|
|
|
|
|
],
|
|
|
|
|
caution: None,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub static INCLUDE_HIDDEN: Tip = Tip {
|
|
|
|
|
title: "Include hidden files",
|
2026-09-25 14:58:51 -04:00
|
|
|
body: "Files and folders the system keeps out of sight: names starting \
|
|
|
|
|
with a dot on Linux and macOS, and anything with the Hidden \
|
2026-08-05 18:05:04 -04:00
|
|
|
attribute on Windows, such as AppData and $RECYCLE.BIN. They are \
|
|
|
|
|
mostly program settings and caches, so leaving them out keeps the \
|
|
|
|
|
index smaller and the results cleaner.\n\n\
|
2026-09-25 14:58:51 -04:00
|
|
|
Folders marked System but not Hidden are always indexed: cloud \
|
|
|
|
|
sync folders and folders with a custom icon carry that mark only \
|
|
|
|
|
for the icon.",
|
|
|
|
|
examples: &["on to find configuration files such as .bashrc or .gitconfig."],
|
2026-08-05 18:05:04 -04:00
|
|
|
caution: None,
|
|
|
|
|
};
|
|
|
|
|
|
2026-08-17 19:26:18 -04:00
|
|
|
// --- Settings: Processing -------------------------------------------------
|
2026-08-05 18:05:04 -04:00
|
|
|
|
|
|
|
|
pub static TOKENIZER: Tip = Tip {
|
|
|
|
|
title: "Tokenizer",
|
|
|
|
|
body: "How the text inside your files is cut up so that it can be \
|
|
|
|
|
searched.\n\n\
|
2026-09-25 14:58:51 -04:00
|
|
|
trigram indexes every run of three characters, so \"port\" also \
|
|
|
|
|
finds \"airport\", and it works for languages written without \
|
|
|
|
|
spaces. The other two index whole words: a smaller, faster index, \
|
|
|
|
|
but a search matches only from the start of a word.",
|
2026-08-05 18:05:04 -04:00
|
|
|
examples: &[
|
|
|
|
|
"trigram, the default, for finding a fragment anywhere inside a word.",
|
2026-09-25 14:58:51 -04:00
|
|
|
"unicode61 when you only search whole words and want the smallest, fastest index.",
|
2026-08-05 18:05:04 -04:00
|
|
|
"porter to also match English word endings, so \"running\" finds \"run\".",
|
|
|
|
|
],
|
2026-09-25 14:58:51 -04:00
|
|
|
caution: Some("Changing this rebuilds the index from scratch."),
|
2026-08-05 18:05:04 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub static HASH_LENGTH: Tip = Tip {
|
|
|
|
|
title: "Hash sample size",
|
2026-09-25 14:58:51 -04:00
|
|
|
body: "How many bytes from the start of each file QuickSearch reads to \
|
|
|
|
|
recognise it. They make the fingerprint the Duplicates tab groups \
|
|
|
|
|
files by, tell what kind of file it is, and for a small text file \
|
|
|
|
|
are the whole text.\n\n\
|
|
|
|
|
Reading more is more reliable but slower, especially over a \
|
2026-08-05 18:05:04 -04:00
|
|
|
network.",
|
|
|
|
|
examples: &[
|
|
|
|
|
"8192, the default, is right for almost everyone.",
|
|
|
|
|
"higher when the Duplicates tab groups files that are not really identical, \
|
2026-09-25 14:58:51 -04:00
|
|
|
as happens with disk images and other formats that start with a lot of \
|
2026-08-05 18:05:04 -04:00
|
|
|
empty space.",
|
|
|
|
|
],
|
2026-09-25 14:58:51 -04:00
|
|
|
caution: Some("Changing this rebuilds the index from scratch."),
|
2026-08-05 18:05:04 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub static MAX_STORED_TEXT: Tip = Tip {
|
|
|
|
|
title: "Max stored text",
|
2026-09-25 14:58:51 -04:00
|
|
|
body: "The most text QuickSearch keeps from any one file. Text past this \
|
|
|
|
|
point is not stored, so a word that appears only deep inside a \
|
|
|
|
|
very long document is not found. Names, sizes and dates are \
|
|
|
|
|
unaffected.\n\n\
|
|
|
|
|
With the two settings below it, this has the most effect on how \
|
|
|
|
|
big the index grows.",
|
2026-08-05 18:05:04 -04:00
|
|
|
examples: &[
|
|
|
|
|
"262144, 256 KB, covers the whole of most documents.",
|
|
|
|
|
"65536, 64 KB, to shrink the index when you mostly search the opening pages.",
|
2026-09-25 14:58:51 -04:00
|
|
|
"higher for long books, transcripts or logs you expect to search to the end.",
|
2026-08-05 18:05:04 -04:00
|
|
|
],
|
|
|
|
|
caution: None,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub static MAX_TEXT_FILE_SIZE: Tip = Tip {
|
|
|
|
|
title: "Max text file size",
|
2026-09-25 14:58:51 -04:00
|
|
|
body: "Files larger than this are indexed without their text, so one \
|
|
|
|
|
huge file cannot hold up an indexing run. They are still found by \
|
|
|
|
|
name, size or date.",
|
2026-08-05 18:05:04 -04:00
|
|
|
examples: &[
|
2026-09-25 14:58:51 -04:00
|
|
|
"52428800, 50 MB, the default, reads inside large logs, scanned PDFs and \
|
|
|
|
|
presentations that are mostly pictures.",
|
|
|
|
|
"2097152, 2 MB, to read ordinary documents only and leave anything larger \
|
2026-09-18 18:09:05 -04:00
|
|
|
to be found by name.",
|
2026-08-05 18:05:04 -04:00
|
|
|
],
|
|
|
|
|
caution: None,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub static BATCH_SIZE: Tip = Tip {
|
|
|
|
|
title: "Batch size",
|
2026-09-25 14:58:51 -04:00
|
|
|
body: "How many files go into each write to the index while indexing. \
|
|
|
|
|
Larger batches mean fewer, bigger writes: a little faster, and \
|
|
|
|
|
more memory.\n\n\
|
|
|
|
|
It changes only speed, never what you can find.",
|
2026-08-05 18:05:04 -04:00
|
|
|
examples: &[
|
|
|
|
|
"500, the default, balances speed against memory.",
|
2026-09-25 14:58:51 -04:00
|
|
|
"about 50 on a machine with very little memory to spare.",
|
2026-08-05 18:05:04 -04:00
|
|
|
],
|
|
|
|
|
caution: None,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub static MAX_WAL_SIZE: Tip = Tip {
|
|
|
|
|
title: "Max WAL size",
|
2026-09-25 14:58:51 -04:00
|
|
|
body: "Indexing writes changes to a companion file (the WAL) and later \
|
|
|
|
|
folds them into the index. During a long run with searches going \
|
|
|
|
|
on, that file can outgrow the index itself. At this size \
|
|
|
|
|
QuickSearch pauses indexing and folds it in.\n\n\
|
|
|
|
|
A larger value makes indexing faster and searching during it \
|
|
|
|
|
slower.",
|
2026-08-05 18:05:04 -04:00
|
|
|
examples: &[
|
2026-09-25 14:58:51 -04:00
|
|
|
"2147483648, 2 GB, the default.",
|
2026-08-25 01:04:36 -04:00
|
|
|
"67108864, 64 MB, to favour searching while indexing runs.",
|
2026-09-25 14:58:51 -04:00
|
|
|
"0 to leave it to the database. Other values under 16 MB count as 16 MB.",
|
2026-08-05 18:05:04 -04:00
|
|
|
],
|
|
|
|
|
caution: None,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub static STORE_TEXT: Tip = Tip {
|
|
|
|
|
title: "Store text for snippets",
|
2026-09-25 14:58:51 -04:00
|
|
|
body: "Keeps the text read from your files, not just the search index \
|
|
|
|
|
built from it. This is what shows a preview line under each \
|
|
|
|
|
result.\n\n\
|
2026-08-05 18:05:04 -04:00
|
|
|
Off, searching inside files still works, but there are no \
|
|
|
|
|
previews, no ranking by how often a word appears, no telling \
|
2026-09-25 14:58:51 -04:00
|
|
|
Report from report, and no typo tolerance inside file contents. \
|
|
|
|
|
The index shrinks considerably.\n\n\
|
2026-08-05 18:05:04 -04:00
|
|
|
Turning it off discards the stored text at once; turning it back \
|
|
|
|
|
on reads your files again.",
|
|
|
|
|
examples: &[
|
|
|
|
|
"off when the index has grown larger than you want and you can do without previews.",
|
|
|
|
|
],
|
|
|
|
|
caution: None,
|
|
|
|
|
};
|
|
|
|
|
|
2026-08-17 19:26:18 -04:00
|
|
|
// --- Settings: Search -----------------------------------------------------
|
2026-08-05 18:05:04 -04:00
|
|
|
|
|
|
|
|
pub static FUZZY_DEFAULT: Tip = Tip {
|
|
|
|
|
title: "Fuzzy search ON by default",
|
2026-09-25 14:58:51 -04:00
|
|
|
body: "Whether the Fuzzy box on the Search tab starts ticked when \
|
|
|
|
|
QuickSearch opens. Fuzzy search also finds matches with typos, at \
|
|
|
|
|
some cost in speed. You can tick or untick it at any time.",
|
2026-08-05 18:05:04 -04:00
|
|
|
examples: &["on when you often look for names you are not sure how to spell."],
|
|
|
|
|
caution: None,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub static FUZZY_EDITS: Tip = Tip {
|
|
|
|
|
title: "Fuzzy edit distance",
|
2026-09-25 14:58:51 -04:00
|
|
|
body: "How far a word may be from what you typed and still match while \
|
|
|
|
|
Fuzzy is on. One edit is one letter added, removed or changed, so \
|
|
|
|
|
\"repot\" is one edit from \"report\".\n\n\
|
|
|
|
|
The allowance grows with what you type, one edit per three \
|
|
|
|
|
characters, up to this value, so short searches stay strict and \
|
|
|
|
|
three letters do not match half the index.",
|
2026-08-05 18:05:04 -04:00
|
|
|
examples: &[
|
2026-09-25 14:58:51 -04:00
|
|
|
"2, the default: one edit for words of three to five letters, two for longer ones.",
|
|
|
|
|
"0 turns typo matching off, even with the Fuzzy box ticked.",
|
|
|
|
|
"3 or more is allowed, but searches slow down and pull in many unrelated files.",
|
2026-08-05 18:05:04 -04:00
|
|
|
],
|
|
|
|
|
caution: None,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub static DISPLAY_LIMIT: Tip = Tip {
|
|
|
|
|
title: "Display limit",
|
2026-09-25 14:58:51 -04:00
|
|
|
body: "The most results one search gathers and shows. A search matching \
|
|
|
|
|
more stops here, which keeps the list quick to scroll and cheap to \
|
|
|
|
|
hold.\n\n\
|
2026-08-05 18:05:04 -04:00
|
|
|
The best matches come first, so a lower limit rarely hides what \
|
2026-09-25 14:58:51 -04:00
|
|
|
you are looking for.",
|
2026-08-05 18:05:04 -04:00
|
|
|
examples: &[
|
|
|
|
|
"1000, the default, is more than anyone scrolls through.",
|
2026-09-25 14:58:51 -04:00
|
|
|
"higher to list every file of a kind, such as type:Image, all at once.",
|
2026-08-05 18:05:04 -04:00
|
|
|
],
|
|
|
|
|
caution: None,
|
|
|
|
|
};
|
|
|
|
|
|
2026-08-25 01:04:36 -04:00
|
|
|
pub static SEARCH_CACHE: Tip = Tip {
|
|
|
|
|
title: "Search cache",
|
2026-09-25 14:58:51 -04:00
|
|
|
body: "Memory, in MiB, that searching keeps index pages in. 0 sizes it \
|
|
|
|
|
for your index automatically, up to 128 MiB; the value it picks is \
|
|
|
|
|
shown below.\n\n\
|
|
|
|
|
It matters on an encrypted index, which must decrypt every page the \
|
|
|
|
|
cache misses: a cache smaller than the one shown can make searches \
|
|
|
|
|
about four times slower. An unencrypted index gets a small fixed \
|
|
|
|
|
cache, because a larger one measures no faster. The memory is \
|
|
|
|
|
released after a long idle.",
|
2026-08-25 01:04:36 -04:00
|
|
|
examples: &[
|
2026-09-25 14:58:51 -04:00
|
|
|
"0, the default, is right unless your folders are nested unusually deep.",
|
|
|
|
|
"a fixed value to cap the memory QuickSearch holds, at the cost of slower \
|
|
|
|
|
searches on a large index.",
|
2026-08-25 01:04:36 -04:00
|
|
|
],
|
2026-09-25 14:58:51 -04:00
|
|
|
caution: None,
|
2026-08-25 01:04:36 -04:00
|
|
|
};
|
|
|
|
|
|
2026-08-05 18:05:04 -04:00
|
|
|
pub static RESULTS_PER_PAGE: Tip = Tip {
|
|
|
|
|
title: "Stream batch size",
|
2026-09-25 14:58:51 -04:00
|
|
|
body: "How many results arrive in each batch while a search runs. Smaller \
|
|
|
|
|
batches show the first results sooner and update the list more \
|
|
|
|
|
often; larger ones do less work in total.\n\n\
|
|
|
|
|
This is not a page size: scrolling does not fetch more.",
|
2026-08-05 18:05:04 -04:00
|
|
|
examples: &[
|
|
|
|
|
"100, the default, feels immediate on most machines.",
|
|
|
|
|
"25 when the first results are slow to appear on a large index.",
|
|
|
|
|
],
|
|
|
|
|
caution: None,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub static DEBOUNCE: Tip = Tip {
|
|
|
|
|
title: "Debounce",
|
2026-09-25 14:58:51 -04:00
|
|
|
body: "How long QuickSearch waits after your last keystroke before \
|
|
|
|
|
searching, so typing a word does not start a search for every \
|
|
|
|
|
letter. In milliseconds: 1000 is one second.",
|
2026-08-05 18:05:04 -04:00
|
|
|
examples: &[
|
|
|
|
|
"150, the default, keeps up with ordinary typing.",
|
2026-09-25 14:58:51 -04:00
|
|
|
"0 to search on every keystroke on a fast machine with a modest index.",
|
|
|
|
|
"300 or more when results flicker or stutter as you type.",
|
2026-08-05 18:05:04 -04:00
|
|
|
],
|
|
|
|
|
caution: None,
|
|
|
|
|
};
|
|
|
|
|
|
2026-08-17 19:26:18 -04:00
|
|
|
pub static LIVE_RESULTS: Tip = Tip {
|
|
|
|
|
title: "Live results",
|
2026-09-25 14:58:51 -04:00
|
|
|
body: "Keeps the rows on screen current as their files change. A rename, \
|
|
|
|
|
edit or deletion made elsewhere shows here within a second, read \
|
|
|
|
|
from the file itself rather than from the index. Each row is also \
|
|
|
|
|
checked against the disk as it scrolls into view, which corrects a \
|
|
|
|
|
stale index; on a network share that check is all you get.\n\n\
|
|
|
|
|
Only visible rows are watched, and only until you change the \
|
|
|
|
|
search. Nothing is added, removed or re-sorted while you read.",
|
2026-08-17 19:26:18 -04:00
|
|
|
examples: &[
|
2026-09-25 14:58:51 -04:00
|
|
|
"off if a stopped index must mean nothing is written to it, since the files \
|
|
|
|
|
on screen are otherwise updated in the index even while indexing is stopped.",
|
2026-08-17 19:26:18 -04:00
|
|
|
],
|
2026-09-25 14:58:51 -04:00
|
|
|
caution: None,
|
2026-08-17 19:26:18 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub static COLUMNS: Tip = Tip {
|
|
|
|
|
title: "Search columns",
|
2026-09-25 14:58:51 -04:00
|
|
|
body: "Which columns the results table shows. The path is always shown, \
|
|
|
|
|
because it is the only column that identifies a result on its \
|
|
|
|
|
own.\n\n\
|
|
|
|
|
Size and Modified start off: their width is usually better spent \
|
|
|
|
|
on the path and the matched text. Turning a column on also lets \
|
|
|
|
|
you sort by it. Right-clicking a column header on the Search tab \
|
|
|
|
|
does the same.",
|
|
|
|
|
examples: &[],
|
2026-08-17 19:26:18 -04:00
|
|
|
caution: None,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// --- Settings: Interface --------------------------------------------------
|
2026-08-05 18:05:04 -04:00
|
|
|
|
|
|
|
|
pub static UI_SCALE: Tip = Tip {
|
|
|
|
|
title: "UI scale",
|
|
|
|
|
body: "Zooms the whole window: text, spacing and controls together. 1.00 \
|
|
|
|
|
is the ordinary size for your screen.\n\n\
|
2026-09-25 14:58:51 -04:00
|
|
|
Ctrl with + or - changes it for now without saving, and Ctrl 0 \
|
|
|
|
|
puts it back. This slider sets the size QuickSearch starts at.",
|
2026-08-05 18:05:04 -04:00
|
|
|
examples: &[
|
2026-09-25 14:58:51 -04:00
|
|
|
"1.40 or more on a high-resolution screen where text looks small.",
|
|
|
|
|
"0.80 to fit more results on screen.",
|
2026-08-05 18:05:04 -04:00
|
|
|
],
|
|
|
|
|
caution: None,
|
|
|
|
|
};
|
|
|
|
|
|
2026-08-09 02:58:13 -04:00
|
|
|
pub static SEARCH_HOTKEY: Tip = Tip {
|
|
|
|
|
title: "Search shortcut",
|
2026-09-25 14:58:51 -04:00
|
|
|
body: "Brings QuickSearch to the front from anywhere, with the previous \
|
|
|
|
|
search selected so you can start typing.\n\n\
|
|
|
|
|
Click the button, then press a key together with any of Ctrl, Alt \
|
|
|
|
|
and Shift. Clear turns the shortcut off.\n\n\
|
|
|
|
|
While running, QuickSearch holds the key itself on Windows and \
|
|
|
|
|
X11. For the key to also start QuickSearch, your desktop must bind \
|
|
|
|
|
it; the set-up button below does that where it can. On Wayland the \
|
|
|
|
|
desktop binding is the only option.",
|
|
|
|
|
examples: &["Ctrl+Shift+F, the default, which few other programs use."],
|
2026-08-09 02:58:13 -04:00
|
|
|
caution: None,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub static COLOR_SCHEME: Tip = Tip {
|
|
|
|
|
title: "Color scheme",
|
2026-09-25 14:58:51 -04:00
|
|
|
body: "Dark or light. Takes effect when you apply it, with no \
|
|
|
|
|
restart.\n\n\
|
|
|
|
|
QuickSearch does not follow your desktop's theme: on Linux that \
|
|
|
|
|
would mean listening to your session's settings over the message \
|
|
|
|
|
bus, which a search tool has no business doing.",
|
|
|
|
|
examples: &["Light for a bright room, or to match a light desktop."],
|
2026-08-09 02:58:13 -04:00
|
|
|
caution: None,
|
|
|
|
|
};
|
|
|
|
|
|
2026-08-17 19:26:18 -04:00
|
|
|
// --- Settings: Security ---------------------------------------------------
|
2026-08-05 18:05:04 -04:00
|
|
|
|
|
|
|
|
pub static ENABLE_PASSWORD: Tip = Tip {
|
|
|
|
|
title: "Enable password protection",
|
2026-09-25 14:58:51 -04:00
|
|
|
body: "Encrypts the index with a password you choose. The index holds the \
|
|
|
|
|
names and text of your files, so without encryption anyone who can \
|
|
|
|
|
read it can read those.\n\n\
|
|
|
|
|
QuickSearch then asks for the password at every start, unless you \
|
|
|
|
|
let it remember.",
|
2026-08-05 18:05:04 -04:00
|
|
|
examples: &[],
|
|
|
|
|
caution: Some(
|
2026-09-25 14:58:51 -04:00
|
|
|
"Turning protection on deletes and rebuilds the index. Your files are not touched.",
|
2026-08-05 18:05:04 -04:00
|
|
|
),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub static CHANGE_PASSWORD: Tip = Tip {
|
|
|
|
|
title: "Change password",
|
2026-09-25 14:58:51 -04:00
|
|
|
body: "Replaces the index password. You are asked for a new one, and the \
|
|
|
|
|
index is encrypted again with it.",
|
2026-08-05 18:05:04 -04:00
|
|
|
examples: &[],
|
|
|
|
|
caution: Some(
|
2026-09-25 14:58:51 -04:00
|
|
|
"Changing the password deletes and rebuilds the index. Your files are not touched.",
|
2026-08-05 18:05:04 -04:00
|
|
|
),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub static DISABLE_PASSWORD: Tip = Tip {
|
|
|
|
|
title: "Disable protection",
|
2026-09-25 14:58:51 -04:00
|
|
|
body: "Removes the password and stores the index unencrypted. Anyone who \
|
|
|
|
|
can read that file can then see your file names and the text \
|
|
|
|
|
inside them.",
|
2026-08-05 18:05:04 -04:00
|
|
|
examples: &[],
|
|
|
|
|
caution: Some(
|
2026-09-25 14:58:51 -04:00
|
|
|
"Turning protection off deletes and rebuilds the index. Your files are not touched.",
|
2026-08-05 18:05:04 -04:00
|
|
|
),
|
|
|
|
|
};
|
|
|
|
|
|
2026-08-17 19:26:18 -04:00
|
|
|
pub static SHOW_KEY: Tip = Tip {
|
|
|
|
|
title: "Show database key",
|
2026-09-25 14:58:51 -04:00
|
|
|
body: "Shows the raw SQLCipher key the index is encrypted with, after you \
|
|
|
|
|
confirm your password. Tools such as DB Browser for SQLCipher \
|
|
|
|
|
accept it in the 0x form shown and can then open the index \
|
|
|
|
|
file.\n\n\
|
|
|
|
|
The key is derived from your password and the salt in the config \
|
|
|
|
|
file, so it stays the same until the password changes. Anyone \
|
|
|
|
|
holding it can read the index without the password, so guard a \
|
|
|
|
|
copy as you would the password itself.",
|
2026-08-17 19:26:18 -04:00
|
|
|
examples: &[],
|
2026-09-25 14:58:51 -04:00
|
|
|
caution: None,
|
2026-08-17 19:26:18 -04:00
|
|
|
};
|
|
|
|
|
|
2026-08-05 18:05:04 -04:00
|
|
|
pub static REMEMBER_KEYCHAIN: Tip = Tip {
|
|
|
|
|
title: "Remember on this device",
|
2026-09-25 14:58:51 -04:00
|
|
|
body: "Stores the index key in your system's password store, such as \
|
|
|
|
|
GNOME Keyring, KWallet or Windows Credential Manager, so \
|
2026-08-05 18:05:04 -04:00
|
|
|
QuickSearch can unlock the index without asking at startup.\n\n\
|
2026-09-25 14:58:51 -04:00
|
|
|
The password itself is never stored, only a key derived from it, \
|
|
|
|
|
and only on this machine. Off, you type the password each time \
|
|
|
|
|
QuickSearch starts.",
|
2026-08-05 18:05:04 -04:00
|
|
|
examples: &[],
|
|
|
|
|
caution: None,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// --- Manage Index tab: indexing controls ---------------------------------
|
|
|
|
|
|
|
|
|
|
pub static START_NOW: Tip = Tip {
|
|
|
|
|
title: "Start indexing now",
|
2026-09-25 14:58:51 -04:00
|
|
|
body: "Runs a full pass over your indexed folders now instead of at the \
|
|
|
|
|
next scheduled time. Worth doing after adding a folder or changing \
|
|
|
|
|
a filter, or when the computer has been off for a while.\n\n\
|
|
|
|
|
Searching keeps working meanwhile. Unavailable while a run is \
|
|
|
|
|
under way.",
|
2026-08-05 18:05:04 -04:00
|
|
|
examples: &[],
|
|
|
|
|
caution: None,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub static STOP_INDEXING: Tip = Tip {
|
|
|
|
|
title: "Stop",
|
|
|
|
|
body: "Stops the run in progress and switches to manual, so QuickSearch \
|
2026-09-25 14:58:51 -04:00
|
|
|
stops watching for changes and reindexing on a schedule. The index \
|
|
|
|
|
stays as it is and searching still works, but it falls behind as \
|
|
|
|
|
your files change.\n\n\
|
|
|
|
|
Saved at once: QuickSearch starts in manual next time too.",
|
2026-08-05 18:05:04 -04:00
|
|
|
examples: &[],
|
|
|
|
|
caution: None,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub static RETURN_TO_AUTO: Tip = Tip {
|
|
|
|
|
title: "Return to Automatic",
|
|
|
|
|
body: "Goes back to watching your folders and reindexing on a schedule, \
|
2026-09-25 14:58:51 -04:00
|
|
|
first catching up on everything that changed while indexing was \
|
|
|
|
|
manual.\n\n\
|
|
|
|
|
Also saved, so QuickSearch starts this way from now on.",
|
2026-08-05 18:05:04 -04:00
|
|
|
examples: &[],
|
|
|
|
|
caution: None,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub static CLEAR_INDEX: Tip = Tip {
|
|
|
|
|
title: "Clear index",
|
2026-09-25 14:58:51 -04:00
|
|
|
body: "Deletes the index database. Searches find nothing until it is \
|
|
|
|
|
built again, which takes a while for large folders. Your own files \
|
|
|
|
|
are never touched.\n\n\
|
|
|
|
|
QuickSearch asks first, then switches to manual so it does not \
|
|
|
|
|
rebuild what you just deleted.",
|
2026-08-05 18:05:04 -04:00
|
|
|
examples: &[],
|
2026-09-25 14:58:51 -04:00
|
|
|
caution: Some("This cannot be undone: the index has to be built again from scratch."),
|
2026-08-05 18:05:04 -04:00
|
|
|
};
|
|
|
|
|
|
2026-09-05 03:09:12 -04:00
|
|
|
// --- Index status --------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
pub static REBUILDING_FTS: Tip = Tip {
|
|
|
|
|
title: "Rebuilding the full-text search cache",
|
2026-09-25 14:58:51 -04:00
|
|
|
body: "A setting that decides which text is searchable changed, so \
|
|
|
|
|
QuickSearch is updating the search index. On a large index this \
|
|
|
|
|
takes several minutes and the progress can look frozen. Searching \
|
|
|
|
|
keeps working meanwhile.",
|
2026-09-05 03:09:12 -04:00
|
|
|
examples: &[],
|
|
|
|
|
caution: None,
|
|
|
|
|
};
|
|
|
|
|
|
2026-08-05 18:05:04 -04:00
|
|
|
// --- Manage Index tab: indexed folders -----------------------------------
|
|
|
|
|
|
|
|
|
|
pub static ADD_ROOT: Tip = Tip {
|
|
|
|
|
title: "Add an indexed folder",
|
2026-09-25 14:58:51 -04:00
|
|
|
body: "Adds a folder and everything inside it to the index. Choose it \
|
|
|
|
|
with the browser, or type the path and press Add.\n\n\
|
|
|
|
|
An indexing pass adds the new folder to the current index; the \
|
|
|
|
|
rest of the index is left alone and nothing is rebuilt.\n\n\
|
|
|
|
|
Indexed folders may not overlap, so a folder inside another \
|
|
|
|
|
indexed folder, or containing one, is refused.",
|
2026-08-05 18:05:04 -04:00
|
|
|
examples: &["a second drive, or a network share you search often."],
|
|
|
|
|
caution: None,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub static REMOVE_ROOT: Tip = Tip {
|
|
|
|
|
title: "Remove this folder",
|
2026-09-25 14:58:51 -04:00
|
|
|
body: "Stops indexing the folder on this line and removes its entries \
|
|
|
|
|
from the index. Everything else stays searchable and nothing is \
|
|
|
|
|
rebuilt; your files are never touched.\n\n\
|
2026-08-05 18:05:04 -04:00
|
|
|
Takes effect when you click Apply & Save.",
|
|
|
|
|
examples: &[],
|
|
|
|
|
caution: None,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub static ROOT_WORKERS: Tip = Tip {
|
|
|
|
|
title: "Workers",
|
2026-09-25 14:58:51 -04:00
|
|
|
body: "How many folders QuickSearch reads at once inside this indexed \
|
|
|
|
|
folder. More workers finish sooner on fast storage and network \
|
|
|
|
|
shares, but compete for the same disk and can slow your \
|
|
|
|
|
system.\n\n\
|
|
|
|
|
auto means 4 on local storage and 16 on a network mount. Takes \
|
2026-08-05 18:05:04 -04:00
|
|
|
effect on the next indexing run.",
|
2026-09-25 14:58:51 -04:00
|
|
|
examples: &["1 or 2 to keep indexing low-priority."],
|
2026-08-05 18:05:04 -04:00
|
|
|
caution: None,
|
|
|
|
|
};
|
|
|
|
|
|
2026-08-09 18:36:47 -04:00
|
|
|
pub static ROOT_COUNTS: Tip = Tip {
|
|
|
|
|
title: "What this folder holds",
|
2026-09-25 14:58:51 -04:00
|
|
|
body: "How many files under this folder are in the index, and how many \
|
|
|
|
|
of those had their text extracted, so they can be found by content \
|
|
|
|
|
and not only by name. The difference is files with no readable \
|
|
|
|
|
text (images, videos, archives, programs) plus whatever the \
|
|
|
|
|
extensions whitelist excludes.\n\n\
|
|
|
|
|
Both update when an indexing run finishes.",
|
2026-08-09 18:36:47 -04:00
|
|
|
examples: &[],
|
|
|
|
|
caution: None,
|
|
|
|
|
};
|
|
|
|
|
|
2026-08-05 18:05:04 -04:00
|
|
|
// --- Manage Index tab: content filters -----------------------------------
|
|
|
|
|
|
|
|
|
|
pub static EXT_WHITELIST: Tip = Tip {
|
|
|
|
|
title: "Full-text extensions whitelist",
|
2026-09-25 14:58:51 -04:00
|
|
|
body: "Which kinds of file QuickSearch reads the text of. It limits \
|
|
|
|
|
contents only: every file is still indexed and found by name and \
|
|
|
|
|
path, but a file left off the list cannot be found by the text \
|
|
|
|
|
inside it.\n\n\
|
|
|
|
|
Empty, the default, means every kind QuickSearch understands. To \
|
|
|
|
|
narrow it, enter one extension per line, leading dot optional. A \
|
|
|
|
|
non-empty list also leaves out files with no extension, such as \
|
|
|
|
|
Makefile or README, unless you add the line (none). Anything after \
|
|
|
|
|
a # is a comment.",
|
2026-08-05 18:05:04 -04:00
|
|
|
examples: &[
|
2026-09-04 21:39:54 -04:00
|
|
|
"txt, md and pdf to keep the stored text small and focused on documents, \
|
|
|
|
|
with everything else still findable by name.",
|
2026-08-05 18:05:04 -04:00
|
|
|
"empty to search inside everything QuickSearch can read.",
|
|
|
|
|
],
|
|
|
|
|
caution: None,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub static IGNORE_PATTERNS: Tip = Tip {
|
|
|
|
|
title: "Ignore patterns",
|
2026-09-25 14:58:51 -04:00
|
|
|
body: "Files and folders kept out of the index entirely, name and text \
|
|
|
|
|
alike. Patterns match names and paths, never file contents.\n\n\
|
|
|
|
|
Without a slash, a pattern must match a whole file or folder name, \
|
|
|
|
|
anywhere. With a slash, it matches a whole path and everything \
|
|
|
|
|
under it. * is any run of characters, ? exactly one.\n\n\
|
|
|
|
|
Adding a pattern removes what it matches; deleting it indexes \
|
|
|
|
|
those files again. The Help tab has worked examples.",
|
2026-08-05 18:05:04 -04:00
|
|
|
examples: &[
|
2026-09-25 14:58:51 -04:00
|
|
|
"node_modules skips that folder wherever it appears.",
|
|
|
|
|
"*.log skips every file ending in .log.",
|
|
|
|
|
"a full path, such as your Videos folder, skips it and everything inside.",
|
2026-08-05 18:05:04 -04:00
|
|
|
],
|
|
|
|
|
caution: None,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// --- Shared --------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
pub static APPLY_SAVE: Tip = Tip {
|
|
|
|
|
title: "Apply & Save",
|
2026-09-25 14:58:51 -04:00
|
|
|
body: "Saves these settings to the configuration file and applies them at \
|
|
|
|
|
once. Until then, your edits are only staged.\n\n\
|
2026-08-05 18:05:04 -04:00
|
|
|
Narrowing a setting removes the entries it now excludes; widening \
|
2026-09-25 14:58:51 -04:00
|
|
|
one indexes what it now allows. Only the tokenizer, the hash \
|
|
|
|
|
sample size and password protection rebuild the index from \
|
|
|
|
|
scratch, and those ask first.",
|
2026-08-05 18:05:04 -04:00
|
|
|
examples: &[],
|
|
|
|
|
caution: None,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
2026-09-25 14:58:51 -04:00
|
|
|
/// Every tip, in file order. `all_lists_every_tip` catches one left out,
|
|
|
|
|
/// which would otherwise skip every check below.
|
2026-08-05 18:05:04 -04:00
|
|
|
const ALL: &[&Tip] = &[
|
2026-09-25 14:58:51 -04:00
|
|
|
&SHOW_ADVANCED,
|
2026-08-05 18:05:04 -04:00
|
|
|
&DATABASE_PATH,
|
|
|
|
|
&REINDEX_INTERVAL,
|
|
|
|
|
&FOLLOW_SYMLINKS,
|
|
|
|
|
&INCLUDE_HIDDEN,
|
|
|
|
|
&TOKENIZER,
|
|
|
|
|
&HASH_LENGTH,
|
|
|
|
|
&MAX_STORED_TEXT,
|
|
|
|
|
&MAX_TEXT_FILE_SIZE,
|
|
|
|
|
&BATCH_SIZE,
|
|
|
|
|
&MAX_WAL_SIZE,
|
|
|
|
|
&STORE_TEXT,
|
|
|
|
|
&FUZZY_DEFAULT,
|
|
|
|
|
&FUZZY_EDITS,
|
|
|
|
|
&DISPLAY_LIMIT,
|
2026-09-25 14:58:51 -04:00
|
|
|
&SEARCH_CACHE,
|
2026-08-05 18:05:04 -04:00
|
|
|
&RESULTS_PER_PAGE,
|
|
|
|
|
&DEBOUNCE,
|
2026-09-25 14:58:51 -04:00
|
|
|
&LIVE_RESULTS,
|
|
|
|
|
&COLUMNS,
|
2026-08-05 18:05:04 -04:00
|
|
|
&UI_SCALE,
|
2026-08-09 02:58:13 -04:00
|
|
|
&SEARCH_HOTKEY,
|
|
|
|
|
&COLOR_SCHEME,
|
2026-08-05 18:05:04 -04:00
|
|
|
&ENABLE_PASSWORD,
|
|
|
|
|
&CHANGE_PASSWORD,
|
|
|
|
|
&DISABLE_PASSWORD,
|
2026-08-17 19:26:18 -04:00
|
|
|
&SHOW_KEY,
|
2026-08-05 18:05:04 -04:00
|
|
|
&REMEMBER_KEYCHAIN,
|
|
|
|
|
&START_NOW,
|
|
|
|
|
&STOP_INDEXING,
|
|
|
|
|
&RETURN_TO_AUTO,
|
|
|
|
|
&CLEAR_INDEX,
|
2026-09-05 03:09:12 -04:00
|
|
|
&REBUILDING_FTS,
|
2026-08-05 18:05:04 -04:00
|
|
|
&ADD_ROOT,
|
|
|
|
|
&REMOVE_ROOT,
|
|
|
|
|
&ROOT_WORKERS,
|
2026-08-09 18:36:47 -04:00
|
|
|
&ROOT_COUNTS,
|
2026-08-05 18:05:04 -04:00
|
|
|
&EXT_WHITELIST,
|
|
|
|
|
&IGNORE_PATTERNS,
|
|
|
|
|
&APPLY_SAVE,
|
|
|
|
|
];
|
|
|
|
|
|
2026-09-25 14:58:51 -04:00
|
|
|
#[test]
|
|
|
|
|
fn all_lists_every_tip() {
|
|
|
|
|
// Split so this line does not count itself.
|
|
|
|
|
let defined = include_str!("tips.rs")
|
|
|
|
|
.matches(concat!(": Tip = ", "Tip {"))
|
|
|
|
|
.count();
|
|
|
|
|
assert_eq!(defined, ALL.len(), "a tip is missing from ALL");
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-05 18:05:04 -04:00
|
|
|
fn all_text(tip: &Tip) -> String {
|
|
|
|
|
let mut text = format!("{}\n{}", tip.title, tip.body);
|
|
|
|
|
for example in tip.examples {
|
|
|
|
|
text.push('\n');
|
|
|
|
|
text.push_str(example);
|
|
|
|
|
}
|
|
|
|
|
if let Some(caution) = tip.caution {
|
|
|
|
|
text.push('\n');
|
|
|
|
|
text.push_str(caution);
|
|
|
|
|
}
|
|
|
|
|
text
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn no_tip_uses_an_em_dash() {
|
|
|
|
|
for tip in ALL {
|
|
|
|
|
assert!(
|
|
|
|
|
!all_text(tip).contains('—'),
|
|
|
|
|
"{} uses an em-dash",
|
|
|
|
|
tip.title
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn every_tip_is_filled_in() {
|
|
|
|
|
for tip in ALL {
|
|
|
|
|
assert!(!tip.title.trim().is_empty(), "a tip has no title");
|
|
|
|
|
assert!(
|
|
|
|
|
!tip.title.ends_with('.'),
|
|
|
|
|
"{}: title is not a sentence",
|
|
|
|
|
tip.title
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
tip.body.trim().len() > 40,
|
|
|
|
|
"{}: body says too little",
|
|
|
|
|
tip.title
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
tip.body.trim_end().ends_with('.'),
|
|
|
|
|
"{}: body is not a finished sentence",
|
|
|
|
|
tip.title
|
|
|
|
|
);
|
2026-08-23 00:33:53 -04:00
|
|
|
// "Stop. Stops the run" is the restatement worth catching; the
|
|
|
|
|
// title only counts as repeated when a word ends there.
|
2026-08-05 18:05:04 -04:00
|
|
|
let restates = tip
|
|
|
|
|
.body
|
|
|
|
|
.strip_prefix(tip.title)
|
|
|
|
|
.is_some_and(|rest| !rest.starts_with(|c: char| c.is_alphanumeric()));
|
|
|
|
|
assert!(!restates, "{}: body repeats the title", tip.title);
|
|
|
|
|
for example in tip.examples {
|
|
|
|
|
assert!(
|
|
|
|
|
!example.trim().is_empty() && example.trim_end().ends_with('.'),
|
|
|
|
|
"{}: bad example {:?}",
|
|
|
|
|
tip.title,
|
|
|
|
|
example
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
if let Some(caution) = tip.caution {
|
|
|
|
|
assert!(
|
|
|
|
|
caution.trim_end().ends_with('.'),
|
|
|
|
|
"{}: caution is not a finished sentence",
|
|
|
|
|
tip.title
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn no_tip_is_a_wall_of_text() {
|
|
|
|
|
for tip in ALL {
|
|
|
|
|
let len = all_text(tip).chars().count();
|
|
|
|
|
assert!(len <= 900, "{} is {} characters long", tip.title, len);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn titles_are_distinct() {
|
|
|
|
|
let mut seen: Vec<&str> = ALL.iter().map(|t| t.title).collect();
|
|
|
|
|
seen.sort_unstable();
|
|
|
|
|
let count = seen.len();
|
|
|
|
|
seen.dedup();
|
|
|
|
|
assert_eq!(count, seen.len(), "two tips share a title: {:?}", seen);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn show_paints_every_part() {
|
2026-08-17 22:30:43 -04:00
|
|
|
let ctx = crate::test_ui::ctx();
|
2026-09-25 14:58:51 -04:00
|
|
|
let full = crate::test_ui::run_panel(&ctx, egui::vec2(800.0, 600.0), vec![], |ui| {
|
|
|
|
|
TOKENIZER.show(ui)
|
2026-08-05 18:05:04 -04:00
|
|
|
});
|
|
|
|
|
let painted = crate::test_ui::painted_text(&full).join("\n");
|
|
|
|
|
assert!(painted.contains(TOKENIZER.title), "no title: {painted}");
|
|
|
|
|
assert!(painted.contains("trigram indexes every run"), "no body");
|
|
|
|
|
assert!(painted.contains("• trigram, the default"), "no examples");
|
|
|
|
|
assert!(
|
|
|
|
|
painted.contains(TOKENIZER.caution.unwrap()),
|
|
|
|
|
"no caution line"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn a_single_example_is_prefixed_with_example() {
|
2026-08-17 22:30:43 -04:00
|
|
|
let ctx = crate::test_ui::ctx();
|
2026-09-25 14:58:51 -04:00
|
|
|
let full = crate::test_ui::run_panel(&ctx, egui::vec2(800.0, 600.0), vec![], |ui| {
|
|
|
|
|
INCLUDE_HIDDEN.show(ui)
|
2026-08-05 18:05:04 -04:00
|
|
|
});
|
|
|
|
|
let painted = crate::test_ui::painted_text(&full).join("\n");
|
|
|
|
|
assert!(
|
|
|
|
|
painted.contains(&format!("Example: {}", INCLUDE_HIDDEN.examples[0])),
|
|
|
|
|
"{painted}"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-23 00:33:53 -04:00
|
|
|
/// egui shows nothing on a disabled widget unless the *disabled*
|
|
|
|
|
/// tooltip is set too.
|
2026-08-05 18:05:04 -04:00
|
|
|
#[test]
|
|
|
|
|
fn a_disabled_control_still_explains_itself() {
|
2026-08-17 22:30:43 -04:00
|
|
|
let ctx = crate::test_ui::ctx();
|
2026-09-25 14:58:51 -04:00
|
|
|
crate::test_ui::instant_tooltips(&ctx);
|
2026-08-05 18:05:04 -04:00
|
|
|
let run = |events: Vec<egui::Event>| {
|
2026-09-25 14:58:51 -04:00
|
|
|
crate::test_ui::run_panel(&ctx, egui::vec2(600.0, 400.0), events, |ui| {
|
|
|
|
|
ui.add_enabled(false, egui::Button::new("Stop"))
|
|
|
|
|
.tip(&STOP_INDEXING);
|
2026-08-05 18:05:04 -04:00
|
|
|
})
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
run(vec![]);
|
|
|
|
|
let settled = run(vec![]);
|
|
|
|
|
let pos = crate::test_ui::painted_text_center(&settled, "Stop").expect("button painted");
|
|
|
|
|
let opening: String = STOP_INDEXING.body.chars().take(40).collect();
|
2026-09-25 14:58:51 -04:00
|
|
|
assert!(
|
|
|
|
|
crate::test_ui::hover_shows(run, pos, &opening),
|
|
|
|
|
"a disabled control said nothing on hover"
|
|
|
|
|
);
|
2026-08-05 18:05:04 -04:00
|
|
|
}
|
|
|
|
|
}
|