#25 - fixed matches algorithm

This commit is contained in:
Simon Cambier
2022-04-30 20:18:26 +02:00
parent f2938cde88
commit ca04ed80c9
6 changed files with 42 additions and 34 deletions

View File

@@ -9,14 +9,15 @@ import {
excerptAfter,
type ResultNote,
type SearchMatch,
} from "../globals"
import { loopIndex } from "../utils"
} from "src/globals"
import { loopIndex } from "src/utils"
import { onDestroy, onMount, tick } from "svelte"
import { MarkdownView } from "obsidian"
import { getSuggestions } from "../search"
import { getSuggestions } from "src/search"
import ModalContainer from "./ModalContainer.svelte"
import type { OmnisearchInFileModal, OmnisearchVaultModal } from "src/modals"
import ResultItemInFile from "./ResultItemInFile.svelte"
import { Query } from "src/query"
export let modal: OmnisearchInFileModal
export let parent: OmnisearchVaultModal | null = null
@@ -26,6 +27,7 @@ export let searchQuery: string
let groupedOffsets: number[] = []
let selectedIndex = 0
let note: ResultNote | null = null
let query: Query
onMount(() => {
if (lastSearch && !searchQuery) {
@@ -44,7 +46,8 @@ onDestroy(() => {
$: (async () => {
if (searchQuery) {
note = (await getSuggestions(searchQuery, { singleFilePath }))[0] ?? null
query = new Query(searchQuery)
note = (await getSuggestions(query, { singleFilePath }))[0] ?? null
lastSearch = searchQuery
}
selectedIndex = 0

View File

@@ -7,17 +7,19 @@ import { TFile } from "obsidian"
import { onMount, tick } from "svelte"
import InputSearch from "./InputSearch.svelte"
import ModalContainer from "./ModalContainer.svelte"
import { eventBus, type ResultNote } from "../globals"
import { createNote, openNote } from "../notes"
import { getSuggestions } from "../search"
import { loopIndex } from "../utils"
import { eventBus, type ResultNote } from "src/globals"
import { createNote, openNote } from "src/notes"
import { getSuggestions } from "src/search"
import { loopIndex } from "src/utils"
import { OmnisearchInFileModal, type OmnisearchVaultModal } from "src/modals"
import ResultItemVault from "./ResultItemVault.svelte"
import { Query } from "src/query"
export let modal: OmnisearchVaultModal
let selectedIndex = 0
let searchQuery: string
let resultNotes: ResultNote[] = []
let query: Query
$: selectedNote = resultNotes[selectedIndex]
$: if (searchQuery) {
@@ -37,7 +39,8 @@ onMount(() => {
})
async function updateResults() {
resultNotes = await getSuggestions(searchQuery)
query = new Query(searchQuery)
resultNotes = await getSuggestions(query)
lastSearch = searchQuery
selectedIndex = 0
scrollIntoView()

View File

@@ -1,4 +1,5 @@
<script lang="ts">
import type { Query } from "src/query";
import type { ResultNote } from "../globals"
import { getMatches } from "../search"
import { highlighter, makeExcerpt, stringsToRegex } from "../utils"

View File

@@ -1,4 +1,4 @@
import { stripSurroundingQuotes } from './utils'
import { escapeRegex, stringsToRegex, stripSurroundingQuotes } from './utils'
type QueryToken = {
/**
@@ -16,19 +16,23 @@ type QueryToken = {
* This class is used to parse a query string into a structured object
*/
export class Query {
public words: QueryToken[] = []
public segments: QueryToken[] = []
public exclusions: QueryToken[] = []
constructor(text: string) {
constructor(text = '') {
const tokens = parseQuery(text.toLowerCase(), { tokenize: true })
this.exclusions = tokens.exclude.text
.map(this.formatToken)
.filter(o => !!o.value)
this.words = tokens.text.map(this.formatToken)
this.segments = tokens.text.map(this.formatToken)
}
public getWordsStr(): string {
return this.words.map(({ value }) => value).join(' ')
public segmentsToStr(): string {
return this.segments.map(({ value }) => value).join(' ')
}
public segmentsToRegex(): RegExp {
return stringsToRegex(this.segments.map(s => s.value))
}
/**
@@ -36,7 +40,7 @@ export class Query {
* @returns
*/
public getExactTerms(): string[] {
return this.words.filter(({ exact }) => exact).map(({ value }) => value)
return this.segments.filter(({ exact }) => exact).map(({ value }) => value)
}
private formatToken(str: string): QueryToken {

View File

@@ -7,16 +7,16 @@ import {
type SearchMatch,
} from './globals'
import {
escapeRegex,
extractHeadingsFromCache,
splitQuotes,
stringsToRegex,
stripMarkdownCharacters,
wait,
} from './utils'
import { Query } from './query'
import type { Query } from './query'
let minisearchInstance: MiniSearch<IndexedNote>
let indexedNotes: Record<string, IndexedNote> = {}
/**
@@ -65,8 +65,8 @@ export async function initGlobalSearchIndex(): Promise<void> {
* @returns
*/
async function search(query: Query): Promise<SearchResult[]> {
if (!query.getWordsStr()) return []
let results = minisearchInstance.search(query.getWordsStr(), {
if (!query.segmentsToStr()) return []
let results = minisearchInstance.search(query.segmentsToStr(), {
prefix: true,
fuzzy: term => (term.length > 4 ? 0.2 : false),
combineWith: 'AND',
@@ -127,11 +127,10 @@ export function getMatches(text: string, reg: RegExp): SearchMatch[] {
* @returns
*/
export async function getSuggestions(
queryStr: string,
query: Query,
options?: Partial<{ singleFilePath: string | null }>,
): Promise<ResultNote[]> {
// Get the raw results
const query = new Query(queryStr)
let results = await search(query)
if (!results.length) return []
@@ -153,20 +152,18 @@ export async function getSuggestions(
throw new Error(`Note "${result.id}" not indexed`)
}
// Clean search matches that match quoted expresins,
// Clean search matches that match quoted expressions,
// and inject those expressions instead
let words = Object.keys(result.match)
const quoted = splitQuotes(query.getWordsStr())
for (const quote of quoted) {
for (const q of quote.toLowerCase()) {
words = words.filter(w => !w.toLowerCase().startsWith(q))
}
words.push(quote)
}
const matches = getMatches(note.content, stringsToRegex(words))
const foundWords = [
...Object.keys(result.match).filter(w =>
query.segments.some(s => w.startsWith(s.value)),
),
...query.segments.filter(s => s.exact).map(s => s.value),
]
const matches = getMatches(note.content, stringsToRegex(foundWords))
const resultNote: ResultNote = {
score: result.score,
foundWords: words,
foundWords,
matches,
...note,
}

View File

@@ -57,7 +57,7 @@ export function getAllIndices(text: string, regex: RegExp): SearchMatch[] {
}
export function stringsToRegex(strings: string[]): RegExp {
return new RegExp(strings.map(escapeRegex).join('|'), 'gi')
return new RegExp(strings.map(s => `(${escapeRegex(s)})`).join('|'), 'gi')
}
export function replaceAll(