mirror of
https://github.com/dnlbauer/obsidian-dataview-autocompletion.git
synced 2026-09-11 22:55:30 +00:00
feat: incremental index update on file change; use dataview api in suggester
This commit is contained in:
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user