quick_search/config_example.toml
Jeremy Karst be30ddc9af
All checks were successful
CI / linux (push) Successful in 16m22s
CI / windows-cross (push) Successful in 5m9s
CI / release (push) Successful in 15s
Tail-check duplicates, re-measure benchmarks and fix CI's release-only test failures
- Duplicates tab: groups are confirmed by each file's last 8 KiB and
  keyed by head+tail; unchecked files are never shown. False-positive
  duplicates drop from 3.4% to 0.1%, and indexing is unchanged.
- Encrypted search cache cap raised to 192 MiB at 150 B per file: a
  keystroke at 1M files takes 59 ms, down from 99 ms.
- Case-insensitive filename matching uses a memchr2 scan (1.18x).
- Benches run again on the compact index, seed with the indexer's 2 GiB
  WAL cap (peak scratch 6.2 GB -> 2.45 GB), and every recorded figure
  is re-measured.
- A fresh index no longer logs a rebuild warning.
- CI: the sandbox memory test's allocation survives release builds, and
  --toggle retries an interrupted read.
2026-10-12 00:14:10 -04:00

232 lines
12 KiB
TOML

# QuickSearch configuration reference.
#
# The live config is created at ~/.config/quicksearch/config.toml
# (Windows: %APPDATA%\quicksearch\config.toml). A config.toml next to the
# quicksearch binary, or the .AppImage, overrides it entirely (portable mode),
# unless it holds a top-level table not listed below: another program's
# config.toml in the same folder is passed over, with a warning in the log.
# One that fails to parse is still taken, and shows its error in the app.
# Relative paths resolve against the config file's directory, so a portable
# folder can be moved wholesale.
#
# Every key is optional; missing keys take the defaults shown here.
[paths]
# One or more folders to index; `~` expands to home. No two may be the same
# folder or nested one inside the other: indexing pauses until the overlap is
# removed. Adding a folder reindexes to pick it up; removing one deletes only
# its entries. Order and spelling do not matter: "~/docs", "/home/you/docs"
# and "/home/you/docs/" are one folder. A folder listed here may itself be a
# symbolic link, but links inside it are never followed: to index what one
# points at, list the target as a folder of its own.
indexing_paths = ["~"]
# The index file. Default: ~/.local/share/quicksearch/index.sqlite (Windows:
# %LOCALAPPDATA%\quicksearch\index.sqlite). Write Windows paths as TOML
# literal strings (single quotes), and keep the index out of a roaming
# profile: it is far too large to sync. Created if missing and replaced if it
# is an index from an older QuickSearch; a SQLite database belonging to
# another program is refused with an error, and nothing is deleted.
database_path = "~/.local/share/quicksearch/index.sqlite"
[indexing]
# true = automatic: filesystem watchers apply changes a couple of seconds
# after they happen, and a full reindex every reindex_interval_minutes
# catches whatever they missed. false = manual: nothing is indexed until you
# ask. The Manage Index tab's Stop and Return to Automatic buttons write this,
# so the app starts in the mode you left it in.
auto_index = true
reindex_interval_minutes = 60
# Index hidden files and directories: dot-files everywhere, plus anything with
# the Hidden attribute on Windows (AppData, $RECYCLE.BIN, System Volume
# Information ...). The System attribute alone does not count: cloud sync
# roots carry it only for a branded folder icon, and are indexed normally.
include_hidden = false
# Extensions whose text is extracted; empty means every supported format.
# Other files are still indexed by name. Case-insensitive, leading dot
# optional; "(none)" includes files with no extension (Makefile, README,
# .bashrc), which a non-empty list otherwise skips. "#" starts a comment
# inside an entry:
# content_extensions = ["txt", "md # docs", "# pdf, too slow", "(none)"]
# Narrowing drops stored text for newly excluded files; widening extracts the
# newly allowed ones.
content_extensions = []
# Excluded from the index entirely. A pattern without a separator matches any
# single path component (so ".git" prunes whole subtrees); one with a
# separator matches full paths. Glob syntax (*, ?, [..]); a name pattern must
# match the whole name, so ignoring an extension needs the wildcard ("*.jpg").
# Matching is case-insensitive on Windows and macOS. The Windows defaults add
# "$RECYCLE.BIN", "System Volume Information", "pagefile.sys", "hiberfil.sys",
# "swapfile.sys", "Thumbs.db" and "desktop.ini"; when indexing a whole Windows
# drive, add 'C:\Windows' and 'C:\Windows\WinSxS' by hand. The index's own
# files are always skipped.
ignore_patterns = [".git", "node_modules", "*.tmp", ".venv", "venv"]
# Walker threads per root, keyed by the exact root string from
# indexing_paths. Absent or 0 = auto (4 on local storage, 16 on network
# mounts, detected per root). Applies from the next run. Set it for a share
# that auto reads as local: on Linux, WebDAV (davfs2), rclone, GVfs, Ceph and
# GlusterFS mounts, and a root that is an automount point not yet mounted.
# root_workers = { "/media/share" = 24 }
[processing]
# Bytes read from the start of each file to hash it (with its size) for
# duplicate detection and to detect its type. Files of equal size with
# identical heads hash alike: 3.4% of the files on a measured disk at 8192,
# 1.6% at 16384. The Duplicates tab also reads this many bytes from the end
# of each file it lists and splits apart files whose ends differ, which
# brings those to 0.1% and 0.06%. Files that differ only in between are
# still listed together, so verify on the Duplicates tab before deleting.
# 8192 is the default; 16384 is also reasonable. Changing it rebuilds the
# index. Clamped to 262..1048576.
hash_length = 8192
# Most extracted text stored per file (bytes). Clamped to 1..16777216.
maximum_text_size = 262144
# Files larger than this skip text extraction entirely (bytes). Clamped to
# 1..4294967296.
maximum_text_file_size = 52428800
# Rows per database write batch when indexing and reconciling. Clamped to
# 1..100000.
batch_size = 500
# Longest one root may hold the index writer per turn (ms), so a root
# extracting large documents cannot stall another root's walk. 0 = one batch
# per turn. Clamped to 0..10000.
writer_turn_slice_ms = 100
# How large the write-ahead log (index.sqlite-wal) may grow during an indexing
# run before a checkpoint is forced (bytes). 0 disables the limit; any other
# value below 16777216 is raised to it. On a nearly full volume the indexer
# checkpoints sooner and stops with an error before the disk fills.
#
# A checkpoint pauses indexing, so a low value stalls runs; a high one slows
# searches that run alongside indexing and lengthens crash recovery. The
# default favours faster runs.
maximum_wal_size = 2147483648
# Build the full-text index without word positions. Several times smaller
# and faster to build (1.1 GiB of source code: a 219 MiB index instead of
# 1.3 GiB, built in a third less time), and searches return exactly the same
# files, because every candidate is checked against the stored text. The cost
# is more candidates to check: searching contents for a word can take up to
# twice as long, for a phrase of common words up to three times. Changing it
# rebuilds the index.
compact_text_index = true
[security]
# Encrypt the index with a password (SQLCipher). The password is asked for at
# every start; turning this on or off deletes and rebuilds the index. Change
# it from the GUI (Settings → Security), not by hand: enabling protection also
# generates the KDF salt below.
password_protected = false
# Store the derived key in the OS keychain (Secret Service / KWallet on Linux,
# Credential Manager on Windows) and skip the startup prompt.
use_keychain = false
# With a password set, the app writes a `salt` value here (32 hex digits). It
# is not a secret, but it is unique to your index: do not create or edit it by
# hand, and keep it if you copy this file, because the password unlocks the
# index only together with its salt.
[ui]
# Zoom for the whole window (0.5 to 2.5): text, spacing and controls scale
# together. Ctrl +/- and Ctrl 0 adjust it until the app closes; this is the
# size it starts at.
scale = 1.25
# Written by QuickSearch: the folders that have already shown the "more
# subfolders than the watcher can follow" warning, so a restart does not
# repeat it. Deleting it just brings each warning back once.
watch_cap_warned_roots = []
# The shortcut QuickSearch claims while it is running: brings it to the front,
# switches to the Search tab and selects the search text. Modifiers are Ctrl,
# Alt and Shift, joined to one key with "+"; "" for none. To also start
# QuickSearch when it is closed, use "Set up system shortcut" on the Settings
# tab, or bind "quicksearch --toggle" in your desktop's keyboard settings.
search_hotkey = "Ctrl+Shift+F"
# 'dark' or 'light' (anything else means dark); applies as soon as it is
# changed on the Settings tab.
color_scheme = "dark"
# Written by QuickSearch: whether the Settings tab shows the technical
# settings (byte budgets, the database path and the like) alongside the
# everyday ones. The checkbox at the top of that tab writes it at once,
# without an Apply.
show_advanced_settings = false
# Written by QuickSearch: whether the introduction shown on a new installation
# has been dismissed. Absent means the config predates it, so an upgraded
# installation is not offered it. The Help tab can show it again at any time.
tutorial_seen = false
# How a search folds case. Plain words match exactly apart from ASCII case:
# "cafe" does not find "café", and "café" does not find "CAFÉ". '*' patterns
# and regex: fold Unicode case ("résumé*" finds "RÉSUMÉ"), in file contents
# only as far as SQLite's full-text case table, which predates some newer
# case pairs (Georgian Mtavruli capitals, for one).
[search]
# Whether the Fuzzy box on the Search tab starts ticked. Off by default: the
# fuzzy passes cost noticeable time on every keystroke, and most searches do
# not need them.
fuzzy_default = false
# Most typos the fuzzy stages allow: one edit per three letters (bytes, for
# non-ASCII text) of the search term, up to this value and never more than 8,
# so 2 means 1 edit for 3-5 letter terms and 2 for longer ones. 0 turns the
# fuzzy stages off. Above 3 is allowed but not recommended: matches become
# coincidence and every fuzzy pass slows.
fuzzy_max_edits = 2
# Most results per search (the length of the GUI's list). Clamped to
# 1..1000000: at zero every search would return nothing.
display_limit = 1000
# Results per streamed batch (a latency knob, not a page size).
results_per_page = 100
# How long the GUI waits after the last keystroke before searching (ms).
debounce_ms = 150
# Watch the visible results and show renames, deletions and content changes as
# they happen. A row's details are read from the file itself, so this works
# whether or not indexing is running, and files that changed are refreshed in
# the index. Editing the query drops the watches.
live_results = true
# Memory, in MiB, the search connection keeps index pages in, held while
# searching and released after a long idle. 0 (recommended) sizes it from the
# index: about 150 bytes per indexed file, capped at 192 MiB.
#
# It matters for an encrypted index, which must decrypt every page the cache
# misses (at 1M files: 99 ms per keystroke with too small a cache, 59 ms with
# enough). An unencrypted index gets a flat 16 MiB to save memory, though one
# large enough to hold its file list measured 10 to 25% faster.
#
# Set it by hand only for deeply nested folders (wider rows) or indexes over
# ~1.3M files, where the automatic cap is too low. Clamped to 16..1024.
cache_size_mib = 0
# Which columns the Search tab shows; the right-click menu of any column
# header and Settings → Search both write here at once, without an Apply.
# There is no 'path' key: the path is always shown, because it is the only
# column that identifies a result on its own.
[search.columns]
name = true
# The excerpt of a file's contents around the match. Rows that matched on
# their name or path show a dash there instead.
content_match = true
# Off by default: their width is usually better spent on the path and the
# matched text. Turning one on also makes it sortable; sorting by a hidden
# column falls back to rank.
size = false
modified = false
rank = true
# What the Duplicates tab lists. The tab writes both keys itself, from its
# exclusion box and its right-click menu. Neither changes what is indexed or
# found: a file left out of the listing is still in the index.
[duplicates]
# Paths left out of the listing. Same syntax and matcher as
# indexing.ignore_patterns: a pattern without a separator matches any single
# name ("*.iso"), one with a separator matches a whole path and everything
# under it ("/home/you/Backups/*").
#
# A matching member is not counted, and the group is sized from the copies
# left; a group down to one copy is not listed. For a folder meant to hold
# copies, this is the key to use.
exclude_patterns = []
# Groups dismissed with "Hide this group", for groups that only look like
# duplicates, since grouping reads just each file's size and its first and
# last processing.hash_length bytes. Stored by content (the head hash, then
# the tail digest, in lowercase hex), so a hidden group stays hidden when its
# files move and returns if their contents change. Entries saved by earlier
# versions hold the head hash alone and no longer match any group. Changing
# processing.hash_length rebuilds the index and strands these entries; the
# tab's Clear button empties the list.
hidden_groups = []