diff --git a/apps/server/src/services/search/services/lex.spec.ts b/apps/server/src/services/search/services/lex.spec.ts index 668522b18d7..5b332da04ed 100644 --- a/apps/server/src/services/search/services/lex.spec.ts +++ b/apps/server/src/services/search/services/lex.spec.ts @@ -171,6 +171,18 @@ describe("Lexer expression", () => { expect(lex(`#!capital ~!neighbor`).expressionTokens.map((t) => t.token)).toEqual(["#!capital", "~!neighbor"]); }); + it("fuzzy operators ~= and ~* are tokenized as single operators", () => { + // regression: https://github.com/TriliumNext/Trilium/issues/9426 + expect(lex(`note.title ~= books`).expressionTokens.map((t) => t.token)).toEqual(["note", ".", "title", "~=", "books"]); + expect(lex(`note.title ~* books`).expressionTokens.map((t) => t.token)).toEqual(["note", ".", "title", "~*", "books"]); + expect(lex(`#author ~= tolkien`).expressionTokens.map((t) => t.token)).toEqual(["#author", "~=", "tolkien"]); + expect(lex(`#author ~*'lord of the rings'`).expressionTokens.map((t) => t.token)).toEqual(["#author", "~*", "lord of the rings"]); + }); + + it("relation prefix still works when ~ is not followed by = or *", () => { + expect(lex(`~author.title = Tolkien`).expressionTokens.map((t) => t.token)).toEqual(["~author", ".", "title", "=", "tolkien"]); + }); + it("negation of sub-expression", () => { expect(lex(`# not(#capital) and note.noteId != "root"`).expressionTokens.map((t) => t.token)).toEqual(["#", "not", "(", "#capital", ")", "and", "note", ".", "noteid", "!=", "root"]); }); diff --git a/apps/server/src/services/search/services/lex.ts b/apps/server/src/services/search/services/lex.ts index 2fcd99ca2ad..6d41146d255 100644 --- a/apps/server/src/services/search/services/lex.ts +++ b/apps/server/src/services/search/services/lex.ts @@ -106,6 +106,10 @@ function lex(str: string) { } else if (["#", "~"].includes(currentWord) && chr === "!") { currentWord += chr; continue; + } else if (currentWord === "~" && (chr === "=" || chr === "*")) { + // ~= and ~* are fuzzy-match operators, not a relation prefix followed by an operator + currentWord += chr; + continue; } else if (chr === " ") { finishWord(i - 1); continue;