# QuickSearch configuration reference. # # The live config is auto-created at ~/.config/quicksearch/config.toml # (Windows: %APPDATA%\quicksearch\config.toml). A config.toml placed next # to the quicksearch binary overrides it entirely (portable mode). # Relative paths resolve against the directory containing the config # file, so a portable folder can be moved wholesale. # # Every key is optional; missing keys take the defaults shown here. [paths] # One or more directory roots to index. Walked in order; duplicate and # nested roots are de-duplicated automatically. `~` expands to home. # Adding a folder reindexes to pick it up; removing one deletes its entries # and leaves the rest of the index alone. Order and spelling do not matter: # "~/docs", "/home/you/docs" and "/home/you/docs/" are one folder. indexing_paths = ["~"] # SQLite index location. 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 synchronise). The file here is # created if missing, and replaced if it is an index from an older # QuickSearch layout; a SQLite database belonging to some other program # is refused with an error, and nothing is deleted. database_path = "~/.local/share/quicksearch/index.sqlite" [indexing] # The indexing mode. true = automatic: filesystem watchers apply changes a # couple of seconds after they happen, and a full reindex runs every # reindex_interval_minutes to cover whatever the watcher missed. # false = manual: nothing is indexed until you ask for it. The Stop and # Return to Automatic buttons on the Manage Index tab write this value, so # the mode you left the app in is the mode it starts in. auto_index = true reindex_interval_minutes = 60 # Follow symbolic links during directory walks. Applies to links pointing # at files as well as at directories: off, a symlink is not resolved at # all, so its target is never indexed — a target can live outside every # root listed above. A resolved target is stored under its own real path, # not the link's. follow_symlinks = false # Index hidden files and directories: dot-files everywhere, and # additionally anything carrying the Hidden attribute on Windows (AppData, # $RECYCLE.BIN, System Volume Information ...). The System attribute on # its own does not count — cloud sync roots carry it purely to get a # branded folder icon, and are indexed normally. include_hidden = false # Empty = extract text from every supported format. Non-empty = content # indexing only for these extensions; other files are still listed for # filename search. Entries are case-insensitive, leading dot optional. # The reserved entry "(none)" whitelists files that have no extension at # all (Makefile, README, .bashrc); a non-empty list without it skips them. # Inside an entry, "#" starts a comment that runs to its end: # content_extensions = ["txt", "md # docs", "# pdf — too slow", "(none)"] # Narrowing this drops the stored text of the files it now excludes; # widening it reindexes to extract the ones it now allows. content_extensions = [] # Excluded from the index entirely. A pattern without a separator matches # any single path component (so ".git" prunes whole subtrees); patterns # containing one match 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, adding 'C:\Windows' # and 'C:\Windows\WinSxS' by hand is worthwhile. The index's own files # are always skipped and need no pattern here. 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 at the start of the next run. # root_workers = { "/media/share" = 24 } [processing] # Bytes read from the start of each file for its content hash, # `sha256(size || first hash_length bytes)`, which backs duplicate # detection; the same bytes are also the detection window for magic-byte # matching and the text sniff. Known limitation: files of identical size # whose heads match are reported as duplicates when they may not be — # verify from the Duplicates tab before deleting anything. 8192 is tuned; # raising it to 16384 is defensible, lowering it is not. Clamped to # 262..1048576 on load. hash_length = 8192 # Maximum extracted text stored per file (bytes). maximum_text_size = 262144 # Files larger than this skip text extraction entirely (bytes). Clamped to # 1..4294967296 on load. maximum_text_file_size = 2097152 # Files per batch during walks / inserts / extraction. batch_size = 500 # Writer time one indexing root's turn may take before the round-robin # moves on (milliseconds), so a root extracting large documents cannot # leave another root's walkers parked behind it. 0 gives each turn one # batch_size quantum and no more. Clamped to 0..10000 on load. writer_turn_slice_ms = 100 # Files per transaction for incremental FTS updates. fts_update_batch_size = 1000 # How large the write-ahead log (index.sqlite-wal) may grow during an # indexing run before the indexer forces a checkpoint (bytes); left # alone, the log grows for the whole run. Not a safety knob: on a volume # short of space the indexer checkpoints sooner than asked and stops the # run with an error before the disk fills (which would kill the process # with SIGBUS through SQLite's mmap'd wal-index). 0 disables it; any # other value below 16777216 is raised to it. # # Both directions cost. A checkpoint blocks indexing for its whole # copy-back, so a low value stalls the run often. A high one is paid by # readers instead: SQLite searches the log before every page it fetches # from the index, so a larger log slows searches running alongside a # run, and it lengthens recovery after a crash or a force-quit. The # default trades toward fewer stalls; lower it if searching while # indexing matters more than the run finishing quickly. maximum_wal_size = 2147483648 # FTS5 tokenizer: 'trigram' (substring matching, the default; gets # remove_diacritics 1 appended), 'unicode61', 'porter', or a full FTS5 # option string. See https://www.sqlite.org/fts5.html#tokenizers tokenize = "trigram" # Store extracted text (zstd-compressed) alongside the FTS index. Off: # the index shrinks to roughly stock-Baloo size, but search loses snippet # previews, occurrence ranking, case verification, and fuzzy full-text. # Turning it off discards the stored text immediately; turning it on # re-extracts, because the text of files already indexed was never kept. store_text_for_snippets = true [security] # Encrypt the index with a password (SQLCipher). The password is asked # for every time QuickSearch starts; 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 # When a password is 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 — the # password only unlocks the index together with its salt. [ui] # Zoom factor for the whole GUI: fonts, spacing, and widgets scale # together (0.5 – 2.5). Ctrl +/- and Ctrl 0 adjust it temporarily at # runtime; this value is the persistent baseline. scale = 1.25 # Written by QuickSearch, not by you: the folders that have already shown # the "more subfolders than the watcher can follow" warning, so restarting # does not repeat it. Deleting it just means the warnings come back once # each. watch_cap_warned_roots = [] # The shortcut QuickSearch claims for itself while it is running: brings it to # the front, switches to the Search tab and selects whatever is in the search # box. Modifiers are Ctrl, Alt and Shift, joined to one key with "+". Leave it # empty ("") for no shortcut. To start QuickSearch when it is closed, bind # "quicksearch --toggle" in your desktop's keyboard settings. search_hotkey = "Ctrl+Shift+F" # 'dark' or 'light'; anything other than 'light' is dark. Applied as soon # as it is changed on the Settings tab. Following the desktop's own # light/dark setting would need the session's D-Bus settings portal, so it # is deliberately not offered. color_scheme = "dark" # Written by QuickSearch, not by you: whether the Settings tab shows the # technical settings alongside the everyday ones. The checkbox at the top of # that tab writes it immediately, without an Apply. Off, the tab hides the # byte budgets, the tokenizer, the database path and the other knobs whose # defaults suit almost every installation. show_advanced_settings = false # Written by QuickSearch, not by you: whether the short introduction shown # on a brand-new installation has been dismissed. Absent means this config # predates that introduction - an installation that upgraded into this # version, which is not offered it. The Help tab can show it again at any # time. tutorial_seen = false [search] # Start with the fuzzy passes enabled. fuzzy_default = false # Ceiling on the fuzzy stages' typo budget. The allowance grows with the # search term, one edit per three characters, up to this value, so 2 # means "1 edit for 3-5 character terms, 2 for anything longer". 0 turns # the fuzzy stages off. Above 3 is allowed but not recommended: matches # become dominated by coincidence and every fuzzy pass slows down. fuzzy_max_edits = 2 # Hard cap on results per search (the GUI's scroll list length). Clamped to # 1..1000000 on load: at zero the cascade stops before its first pass, so # every search returns nothing and calls the empty answer truncated. display_limit = 1000 # Results per streamed batch (latency/overhead 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 search results and show renames, deletions and content # changes as they happen. What a row shows is read from the file itself, # so this works whether or not indexing is running, and the index is # brought up to date for those files. Editing the query drops the watches. live_results = true # Memory the search connection keeps database pages in, in MiB, held for the # length of a search session and released after a long idle. # # 0 sizes it from the index and is almost always right. Every keystroke # rescans the whole file list, so what has to stay resident is that list: # roughly 168 bytes per indexed file, capped at 128 MiB automatically. # # It matters on an *encrypted* index, which must decrypt any page the cache # does not already hold. Measured at 600k files: 127 ms per keystroke with a # cache too small, 34 ms once it fit. An unencrypted index reads a miss # straight from the operating system and is given a flat 16 MiB, because # sweeping 1 MiB to 256 MiB on one measured no faster than noise. # # Set it explicitly only when the automatic value is wrong for your tree — # deeply nested folders make wider rows and want more — or when your index is # over ~800k files, where the automatic cap lands below what it wants. An # explicit value may exceed that cap; it is clamped to 16..1024 on load. cache_size_mib = 0 # Which columns the Search tab shows; the right-click menu of any column # header and Settings → Search both write here immediately, without an # Apply. There is deliberately 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: the width these take is usually better spent on the path # and the matched text. Turning one on also makes it available to sort by; # sorting by a column that is hidden falls back to sorting by rank. size = false modified = false rank = true # What the Duplicates tab lists. Both keys are written by the tab itself, from # its exclusion box and its right-click menu; they are here because they are # yours to read and edit. Neither changes what is indexed or what a search # finds: a file left out of the duplicate listing is still in the index. [duplicates] # Paths left out of the listing. Same syntax and same 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 member whose path matches is not counted, and the group is re-priced # around the copies that are left; a group down to one copy is not a duplicate # of anything and is not listed at all. For a folder that is *meant* to hold # copies, this is the key to use. exclude_patterns = [] # Groups dismissed with "Hide this group", by content hash, lowercase hex. # For the other case: a group that is not really a duplicate at all, because # grouping only reads each file's size and its first processing.hash_length # bytes. # # Keyed by hash, not by path, so a hidden group stays hidden when its files # are renamed or moved, and comes back if their contents change. Changing # processing.hash_length rebuilds the index and gives every file a new hash, # which strands every entry here; the tab's Clear button empties the list. hidden_groups = []