This document describes the implemented NoteManager full-text search system. Search is operated entirely through expressions entered in the existing Search notes box. There is no advanced-search window or query-builder UI.
The implementation provides:
- strict and best-match modes;
- words, phrases, grouping, and Boolean operators;
- required and excluded operands;
- file-name, tag, relative-path, and body field operators;
- literal punctuation, including
/and\; - deterministic search-owned ordering;
- a Unicode word index and a punctuation-preserving literal index.
The query language follows these rules:
- A query without a mode selector behaves like the previous strict search.
- Explicit operators have the same meaning in every mode.
- The mode changes the implicit relationship between adjacent terms and the result ordering.
- Unrelated notes are not best-match results unless the user explicitly adds the match-all operator.
- The search expression is the complete and only search configuration.
- Invalid and stale queries never replace a completed result.
Searchable fields are:
- note file name;
- parsed tags;
- path relative to the opened vault;
- complete Markdown source.
The absolute vault path is not indexed. Search is applied inside the selected tag, All notes, or Untagged navigation scope.
A mode selector is recognized only at the beginning of the trimmed search text. Keyword selectors are case-insensitive.
| Mode | Keyword | Symbol | Adjacent terms | Ordering |
|---|---|---|---|---|
| Strict | all: |
= |
Implicit AND |
Modified descending |
| Best match | best: |
~ |
Implicit OR |
Relevance descending |
Strict is the default:
project plan
all: project plan
= project plan
All three expressions require both project and plan.
Best-match examples:
best: project plan
~ project plan
These expressions return notes matching project or plan, with notes
matching both normally ranked first.
The symbol may touch the expression, so =project and ~project are valid.
A keyword selector must include its colon. all and best without a colon
are ordinary terms.
A selector without an expression is an empty search. It does not activate search ordering or clear the normal sort selection.
A bare term containing only Unicode letters, numbers, or underscores is a word-prefix search:
plan
It can match plan, plans, or planning. An exact word receives a ranking
bonus over a prefix-only match.
Matching is case-insensitive. Text and queries are normalized consistently for diacritics and whitespace.
A bare term containing other punctuation is a literal substring:
docs/search.md
C:\Projects\NoteManager
customer@example.com
release-1.2
#planning
Every punctuation character must be present in the indexed field. / is not
a regular-expression delimiter, and \ is not an escape character. Slash
direction is significant.
A leading - is the exclusion operator. To search for literal text beginning
with a dash, place it in double quotes.
Double quotes create one contiguous phrase, including spaces:
"quarterly project plan"
"C:\Shared Notes\roadmap.md"
The phrase must occur inside one indexed field. It cannot start in the file name and finish in the body. Runs of whitespace are normalized to one space; other punctuation remains significant.
Backslash is always literal. A double quote inside a phrase is written as two double quotes:
"the ""approved"" plan"
An unmatched or empty quoted phrase is a syntax error.
| Operator | Meaning |
|---|---|
AND |
Both operands must match |
OR |
At least one operand must match |
NOT |
Exclude the following operand |
+operand |
Require the following term, phrase, or group |
-operand |
Short form of NOT operand |
( ... ) |
Group an expression |
* |
Match the complete current navigation scope |
AND, OR, and NOT are case-insensitive only when they are complete,
unquoted words. "AND" searches for the word as a phrase, and not-ready
remains a literal term.
Operator precedence is:
NOT,+, and-;AND;OR.
Parentheses override precedence:
(invoice OR receipt) NOT draft
all: "project plan" AND approved
Required and excluded operands apply to the complete result:
~ +invoice paid -archived
This requires invoice, uses paid as an additional ranking signal, and
excludes every note matching archived.
Best-match adjacency is an implicit OR, but an explicit AND still requires
both sides:
~ invoice paid
~ invoice AND paid
The first expression accepts either term. The second accepts only notes matching both.
Best match normally returns notes matching at least one positive term. The
* operator explicitly includes every note in the current navigation scope:
~ * project plan
Notes matching project or plan are ranked first. Notes matching neither
remain at the end with zero relevance. This is the only implicit way to retain
unrelated notes in a best-match result.
* NOT archived returns the complete current scope except notes matching
archived.
| Operator | Indexed field |
|---|---|
name: |
Note file name |
title: |
Alias for name: |
tag: |
Parsed tags |
path: |
Vault-relative path |
body: |
Markdown source |
A field operator accepts one word, literal term, or quoted phrase:
name:roadmap
name:"project plan"
tag:active
path:Clients/Acme
path:"Clients\Acme Notes"
body:"approved budget"
Field operands combine with every other operator:
~ +tag:active body:roadmap -path:archive/
(name:invoice OR tag:receipt) NOT body:draft
Only the known field names above are parsed as field operators. Other
colon-containing values, such as https://example.com, are literal terms.
A field operator without an operand is a syntax error.
Strict mode returns only notes for which the complete expression is true.
Adjacent terms are implicitly joined with AND.
Examples:
| Query | Required result |
|---|---|
alpha beta |
Both terms |
"alpha beta" |
The contiguous phrase |
alpha OR beta |
Either term |
alpha NOT beta |
alpha without beta |
path:docs\search.md |
Literal relative path |
Results are ordered by:
- file modification time descending;
- file name ascending, case-insensitive;
- relative path ascending, case-insensitive.
Relevance does not change strict ordering.
Best match returns notes satisfying the positive expression and all required
and excluded operands. Adjacent positive terms are implicitly joined with
OR.
Ranking considers:
- number of distinct positive terms matched;
- weighted field matches;
- FTS5 BM25 relevance;
- exact-word matches over prefix-only matches;
- quoted phrases and punctuation-preserving literal matches;
- repeated occurrences with bounded frequency contribution;
- coverage of explicit
ANDgroups.
Initial field weights are:
| Field | Weight |
|---|---|
| File name | 6 |
| Tags | 4 |
| Relative path | 2 |
| Markdown content | 1 |
Each distinct positive term supplies a coverage bonus before field-level signals are compared. This makes a note matching more of the requested terms normally outrank a note with one strong field match.
Results are ordered by:
- relevance score descending;
- distinct positive term count descending;
- file modification time descending;
- file name ascending, case-insensitive;
- relative path ascending, case-insensitive.
Modification time is a tie breaker, not part of relevance.
The persisted normal sort and active search order are separate states:
SelectedSortTyperemains the preferred Title, Created, Updated, or Size sort for the vault;- an accepted non-empty result activates strict or best-match search order.
When the latest valid search result is accepted:
- all normal sort-menu checkmarks are cleared;
- the sort button is disabled;
- its tooltip explains that search controls note order;
- strict or best-match ordering is applied;
- the persisted normal sort preference remains unchanged.
Clearing the search box cancels pending work, deactivates search ordering, restores the preferred sort, and restores its checkmark.
An incomplete or invalid expression does not clear the normal sort and does not replace the last accepted search result.
Typing starts a 250 millisecond debounce so search updates without requiring a
button. Pressing Enter cancels that wait and submits the current text
immediately. Enter is a submission action only and is not part of the search
grammar.
Search is unavailable until the current folder's index is complete. During an index build, the search box is disabled and its placeholder reads Indexing in progress. The box is enabled with its normal Search notes placeholder only after the status becomes Full-text ready. If indexing fails, it remains disabled and reads Search unavailable.
An accepted search with zero matches displays an empty note list and a visible
No notes found message. Its status remains a normal successful search
status, such as Strict search · 0 notes; it is not reported as a parser or
index error.
NoteSearchQueryParser produces a typed expression tree. Raw user input is
never passed directly to FTS5 syntax.
The parser:
- removes only an initial mode selector;
- retains literal punctuation;
- distinguishes terms, phrases, fields, groups, and operators;
- records required and excluded expressions separately;
- inserts the mode-specific implicit operator;
- normalizes literal values;
- returns a readable error and character position.
Errors include:
- unmatched or empty quotes;
- empty groups;
- missing closing parentheses;
- leading or trailing binary operators;
- missing field operands.
The application retains the last completed result while showing the error in the status area.
The per-vault database remains:
<selected folder>\.notes\search.db
Schema version 2 contains:
indexed_notes, holding identity and file metadata;note_search, usingunicode61 remove_diacritics 2for word-prefix and BM25 searches;note_literal_search, using the FTS5 trigram tokenizer over normalized fields for phrases and punctuation-preserving substring searches.
Literal values containing % or _ use exact instr verification so those
characters never become SQL wildcard operators.
When an older disposable index is opened, the search tables and index metadata are rebuilt. Source Markdown notes are never modified or deleted by migration.
The service returns ordered NoteSearchHit values containing:
Path
Name
RelativePath
RelevanceScore
MatchedPositiveTermCount
ModifiedUtcTicks
The view model retains hit scores and counts rather than converting results to an unordered set. It intersects hit membership with the active navigation scope and reapplies the documented deterministic ordering.
Search retains the 250-millisecond debounce. Each operation is guarded by:
- opened-folder generation;
- exact search-box text;
- monotonically increasing search generation;
- cancellation token.
Only the latest matching generation may update note membership, selection, status, or sort state.
Index updates commit batches of 200 notes through SQLite WAL. The search box remains disabled for the complete update so the interface never accepts a query against a partial index. A previously entered expression is rerun after indexing completes.
Automated coverage verifies:
- all mode selector forms and mode-only input;
- strict implicit
ANDand best-match implicitOR; AND,OR,NOT,+,-,*, and grouping;- global required and excluded operands;
- quoted phrases and syntax errors;
- literal slash and backslash matching;
name:,title:,tag:,path:, andbody:;- case and diacritic normalization;
- strict modification-time ordering;
- best-match coverage and field ranking;
- explicit match-all zero-score ordering;
- tag navigation combined with search;
- physical typing and Enter submission;
- disabled search and indexing placeholder until the index is ready;
- the visible zero-result empty state;
- clearing and restoring normal sort state;
- cancellation and partial-index behavior.
The implementation is accepted when:
- unqualified,
all:, and=strict searches require every adjacent term; best:and~use any adjacent positive term and preserve relevance order;+requires andNOT/-excludes across the complete result;*explicitly includes the complete current navigation scope;- phrases and literal punctuation differ from separated word queries;
- field operators restrict both membership and scoring;
- strict results are newest-modified first;
- best-match results are strongest first with deterministic ties;
- accepted searches clear and disable normal sorting;
- clearing search restores the persisted sort;
- invalid and stale queries cannot replace a valid result;
- Enter submits the current search immediately;
- a successful zero-match search shows an empty list and No notes found;
- indexing disables search and displays Indexing in progress until ready;
- the portable and executable UI test suites and solution build pass without warnings.