feat: incremental index update on file change; use dataview api in suggester

This commit is contained in:
Daniel Bauer
2024-12-13 15:23:20 +01:00
parent 05baf11b6c
commit f1b2073ccc

View File

@@ -13,11 +13,15 @@ import uFuzzy from "@leeoniya/ufuzzy";
import { getAPI, DataviewApi } from "obsidian-dataview"; import { getAPI, DataviewApi } from "obsidian-dataview";
export class DataviewSuggester extends EditorSuggest<String> { export class DataviewSuggester extends EditorSuggest<String> {
suggestionsList: string[] = [];
maxSuggestions: number; maxSuggestions: number;
searcher: uFuzzy; searcher: uFuzzy;
dataviewApi: DataviewApi; dataviewApi: DataviewApi;
initialized: boolean = false;
suggestionsList: string[] = [];
suggestionsRefs: { [key: string]: string[] } = {}; // maps file paths to included suggestions
suggestionsRefCount: { [key: string]: number } = {}; // maps suggestions to number of files including them
constructor( constructor(
plugin: Plugin, plugin: Plugin,
maxSuggestions: number = 10, maxSuggestions: number = 10,
@@ -102,6 +106,7 @@ export class DataviewSuggester extends EditorSuggest<String> {
public onDataviewIndexReady() { public onDataviewIndexReady() {
this.buildNewIndex(); this.buildNewIndex();
this.initialized = true;
} }
// possible types: update, rename, delete. rename has oldPath // possible types: update, rename, delete. rename has oldPath
@@ -110,8 +115,13 @@ export class DataviewSuggester extends EditorSuggest<String> {
file: TFile, file: TFile,
oldPath?: string, oldPath?: string,
) { ) {
// TODO handle efficiently with delta updates if (!this.initialized) {
this.buildNewIndex(); console.log(
"Dataview Autocompletion index not ready yet. Skipping index update",
);
return;
}
this.updateIndex(type, file, oldPath);
} }
/** /**
@@ -146,14 +156,15 @@ export class DataviewSuggester extends EditorSuggest<String> {
buildNewIndex() { buildNewIndex() {
console.log("Begin Rebuilding dataview suggestion index"); console.log("Begin Rebuilding dataview suggestion index");
const startTime = performance.now(); const startTime = performance.now();
const newSuggestions: string[] = [];
// Iterate all pages of the Dataview index, and ingest all fields into suggestions const newSuggestions: string[] = [];
// TODO: can we use official dataview api? const newSuggestionsRefs: { [key: string]: string[] } = {};
// @ts-ignore const newSuggestionsRefCount: { [key: string]: number } = {};
for (const page of this.app.plugins.plugins.dataview.index.pages) {
const fields = page[1].fields; for (const page of this.dataviewApi.index.pages) {
for (let [key, val] of fields) { const pageRefs = [];
for (let [key, val] of page[1].fields) {
// fields can be a single value or a dict, so we need to handle both // fields can be a single value or a dict, so we need to handle both
let arrayVal; let arrayVal;
if (!Array.isArray(val)) { if (!Array.isArray(val)) {
@@ -169,18 +180,106 @@ export class DataviewSuggester extends EditorSuggest<String> {
let compositeValue = this.formatCompositeValue(key, value); let compositeValue = this.formatCompositeValue(key, value);
if (newSuggestions.indexOf(compositeValue) === -1) { if (newSuggestions.indexOf(compositeValue) === -1) {
// suggestion not seen on any page yet
pageRefs.push(compositeValue);
newSuggestionsRefCount[compositeValue] = 1;
newSuggestions.push(compositeValue); newSuggestions.push(compositeValue);
} else if (pageRefs.indexOf(compositeValue) === -1) {
// suggestion not seen on this page, but on another
pageRefs.push(compositeValue);
newSuggestionsRefCount[compositeValue] + -1;
} }
} }
} }
newSuggestionsRefs[page[0]] = pageRefs;
} }
// replace old index // replace old index
this.suggestionsList = newSuggestions; this.suggestionsList = newSuggestions;
this.suggestionsRefCount = newSuggestionsRefCount;
this.suggestionsRefs = newSuggestionsRefs;
const endTime = performance.now(); const endTime = performance.now();
console.log( console.log(
`Rebuilt dataview autocomplete index (${this.suggestionsList.length} elements, ${(endTime - startTime).toFixed(2)}ms)`, `Rebuilt dataview autocomplete index (${this.suggestionsList.length} elements, ${(endTime - startTime).toFixed(2)}ms)`,
); );
} }
updateIndex(type: string, file: TFile, oldPath?: string) {
// also triggers on create!
if (type === "update") {
const updateCompositeValues = [];
const page = this.dataviewApi.page(file.path);
const fields = Object.keys(page)
.filter((k) => k !== "file")
.map((k) => [k, page[k]]);
for (let [key, val] of fields) {
// fields can be a single value or a dict, so we need to handle both
let arrayVal;
if (!Array.isArray(val)) {
arrayVal = [val];
} else {
arrayVal = val;
}
for (const value of arrayVal) {
if (value === null || value === undefined) continue; // skip empty fields
let compositeValue = this.formatCompositeValue(key, value);
if (updateCompositeValues.indexOf(compositeValue) === -1) {
updateCompositeValues.push(compositeValue);
}
}
for (const compositeValue of this.suggestionsRefs[file.path]) {
if (updateCompositeValues.indexOf(compositeValue) === -1) {
// delete value
this.suggestionsRefCount[compositeValue] -= 1;
if (this.suggestionsRefCount[compositeValue] == 0) {
this.suggestionsList.splice(
this.suggestionsList.indexOf(compositeValue),
1,
);
}
}
}
for (const newCompositeValue of updateCompositeValues) {
if (
this.suggestionsRefs[file.path].indexOf(
newCompositeValue,
) === -1
) {
// add value (also check presence in other files via refcount first)
if (
this.suggestionsList.indexOf(newCompositeValue) ===
-1
) {
this.suggestionsList.push(newCompositeValue);
this.suggestionsRefCount[newCompositeValue] += 1;
}
}
}
}
this.suggestionsRefs[file.path] = updateCompositeValues;
} else if (type === "rename") {
this.suggestionsRefs[file.path] = this.suggestionsRefs[oldPath!];
delete this.suggestionsRefs[oldPath!];
} else if (type === "delete") {
// iterate suggestion refs in deleted file and decrement their ref count
// if the ref count reaches 0, remove the suggestion from the list
for (const value of this.suggestionsRefs[file.path]) {
this.suggestionsRefCount[value] -= 1;
if (this.suggestionsRefCount[value] == 0) {
this.suggestionsList.splice(
this.suggestionsList.indexOf(value),
1,
);
}
}
delete this.suggestionsRefs[file.path];
} else {
console.warn("Unknown update type:", type, file, oldPath);
}
}
} }