## 概要 [[🦉Various Complements]]の[[Intelligent suggestion prioritization]]に表題の対応を行う。 ## Issue https://github.com/tadashi-aikawa/obsidian-various-complements-plugin/issues/131 ## 事前調査 `suggester.ts`のsortロジックについて ```ts const candidate = Array.from(words) .map((x) => judge(x, query, queryStartWithUpper)) .filter((x) => x.value !== undefined) .sort((a, b) => { const aWord = a.word as HitWord; const bWord = b.word as HitWord; const notSameWordType = aWord.type !== bWord.type; if (frontMatter && notSameWordType) { return bWord.type === "frontMatter" ? 1 : -1; } if (selectionHistoryStorage) { const ret = selectionHistoryStorage.compare( aWord as HitWord, bWord as HitWord ); if (ret !== 0) { return ret; } } if (a.value!.length !== b.value!.length) { return a.value!.length > b.value!.length ? 1 : -1; } if (notSameWordType) { return WordTypeMeta.of(bWord.type).priority > WordTypeMeta.of(aWord.type).priority ? 1 : -1; } if (a.alias !== b.alias) { return a.alias ? 1 : -1; } return 0; }) .map((x) => x.word) .slice(0, max); ``` [[フロントマター]]は優先していいので、手を加えるのは下記の部分。 ```ts if (selectionHistoryStorage) { const ret = selectionHistoryStorage.compare( aWord as HitWord, bWord as HitWord ); if (ret !== 0) { return ret; } } ``` Historyを取得してScoreを計算する流れ。 ```ts compare(w1: HitWord, w2: HitWord): -1 | 0 | 1 { const score1 = calcScore(this.getSelectionHistory(w1)); const score2 = calcScore(this.getSelectionHistory(w2)); if (score1 === score2) { return 0; } return score1 > score2 ? -1 : 1; } ``` wordから - ヒットした値は何か? (タグやエイリアスになることも) - 値は何か? (ファイル名) - typeは何か? (`currentFile`や`internalLink`など) をもとに履歴を取得する。 ```ts getSelectionHistory(word: HitWord): SelectionHistory | undefined { return this.data[word.hit]?.[word.value]?.[word.type]; } ``` 選択された回数 x 調整値によってスコアが計算される。 ```ts function calcScore(history: SelectionHistory | undefined): number { if (!history) { return 0; } const behind = Date.now() - history.lastUpdated; // noinspection IfStatementWithTooManyBranchesJS if (behind < MIN) { return 8 * history.count; } else if (behind < HOUR) { return 4 * history.count; } else if (behind < DAY) { return 2 * history.count; } else if (behind < WEEK) { return 0.5 * history.count; } else { return 0.25 * history.count; } } ``` ## 実装方針 パフォーマンスが落ちないことを考慮して対応する。 - `SelectionHistory`に最後に選択したものかどうかの情報を保持し、その場合はint maxを返す - メリット - 現在のフローがそのまま使える - デメリット - 最後の選択が入れ替わったときに、変更前のフラグを変更するのが面倒そう - historyを捜査してlastUseをfalseにする必要あり (パフォーマンスが少し落ちる) - `SelectionHistory`とは別に、最後に選択した情報を記録し、それに一致する場合はint max返す - メリット - 現在のフローがそのまま使え、パフォーマンスも落ちない - デメリット - 最後の挿入時間を保管し、`calcScore`や`compare`から参照させなければならない **- 同じ `SelectionHistoryStorage` だからスムーズにいけそう** ✨ - すべての候補を取得したあと、最もlastUpdatedが新しいhistoryを持っていた候補を一番上釣り上げる - メリット - 新しいデータを持つ必要がない - デメリット - 現在のソートが終わった後に、もう一度一巡する必要がある (パフォーマンスが落ちる) `version`が使えそう。 ```ts export class SelectionHistoryStorage { data: SelectionHistoryTree; version: number; persistedVersion: number; ``` いや... これだと最後と一致しなければダメだから無理だ... あくまでも **候補の中で最も新しいもの** でなければいけない...。となると - すべての候補を取得したあと、最もlastUpdatedが新しいhistoryを持っていた候補を一番上釣り上げる でないとダメ。 ## パフォーマンス ```ts const start = performance.now(); const latestUpdated = max( filteredJudgement.map( (x) => selectionHistoryStorage?.getSelectionHistory(x.word as HitWord) ?.lastUpdated ?? 0 ), 0 ); console.log(performance.now() - start); ``` `0.1`だったので0.1msだから大丈夫そうな気がする...。