Share vault reference providers across chat panels

This commit is contained in:
murashit 2026-07-10 12:41:53 +09:00
parent e9ca2b0448
commit c0e4fc590c
3 changed files with 109 additions and 0 deletions

View file

@ -13,6 +13,42 @@ interface EventSource {
}
export class VaultComposerContextReferenceProvider implements ComposerContextReferenceProvider {
private readonly shared: SharedComposerContext;
private disposed = false;
constructor(private readonly app: App) {
const existing = sharedComposerContexts.get(app);
if (existing) {
existing.consumers += 1;
this.shared = existing;
return;
}
this.shared = { tracker: new VaultComposerContextTracker(app), consumers: 1 };
sharedComposerContexts.set(app, this.shared);
}
contextReferences(sourcePath: string): ComposerContextReferences {
return this.shared.tracker.contextReferences(sourcePath);
}
dispose(): void {
if (this.disposed) return;
this.disposed = true;
this.shared.consumers -= 1;
if (this.shared.consumers > 0) return;
this.shared.tracker.dispose();
sharedComposerContexts.delete(this.app);
}
}
interface SharedComposerContext {
readonly tracker: VaultComposerContextTracker;
consumers: number;
}
const sharedComposerContexts = new WeakMap<App, SharedComposerContext>();
class VaultComposerContextTracker {
private readonly unregisterEvents: (() => void)[] = [];
private lastMarkdownView: MarkdownView | null = null;

View file

@ -22,7 +22,51 @@ interface MetadataCacheWithTags {
getTags?: () => unknown;
}
interface SharedCandidateCatalog {
readonly catalog: VaultNoteCandidateCatalog;
consumers: number;
}
const candidateCatalogs = new WeakMap<App, SharedCandidateCatalog>();
export class VaultNoteCandidateProvider implements NoteCandidateProvider {
private readonly shared: SharedCandidateCatalog;
private disposed = false;
constructor(private readonly app: App) {
const existing = candidateCatalogs.get(app);
if (existing) {
existing.consumers += 1;
this.shared = existing;
return;
}
this.shared = { catalog: new VaultNoteCandidateCatalog(app), consumers: 1 };
candidateCatalogs.set(app, this.shared);
}
candidates(sourcePath: string): readonly NoteCandidate[] {
return this.shared.catalog.candidates(sourcePath);
}
tags(): readonly string[] {
return this.shared.catalog.tags();
}
resolveMention(target: string, sourcePath: string): WikiLinkMention | null {
return this.shared.catalog.resolveMention(target, sourcePath);
}
dispose(): void {
if (this.disposed) return;
this.disposed = true;
this.shared.consumers -= 1;
if (this.shared.consumers > 0) return;
this.shared.catalog.dispose();
candidateCatalogs.delete(this.app);
}
}
class VaultNoteCandidateCatalog {
private readonly unregisterEvents: (() => void)[] = [];
private fileCandidatesCache: FileCandidate[] | null = null;
private tagCandidatesCache: string[] | null = null;

View file

@ -147,6 +147,23 @@ describe("VaultNoteCandidateProvider", () => {
expect(app.offref).toHaveBeenCalledTimes(6);
});
it("shares candidate caches and event subscriptions across panel providers", () => {
const getFiles = vi.fn(() => vaultFiles([{ basename: "Alpha", path: "Alpha.md", stat: { mtime: 1 } }]));
const app = appFixture({ getFiles });
const first = new VaultNoteCandidateProvider(app);
const second = new VaultNoteCandidateProvider(app);
first.candidates("Inbox.md");
second.candidates("Inbox.md");
first.dispose();
expect(getFiles).toHaveBeenCalledOnce();
expect(app.offref).not.toHaveBeenCalled();
second.dispose();
expect(app.offref).toHaveBeenCalledTimes(6);
});
it("resolves wikilinks through metadata cache before direct path fallback", () => {
const linked = tFile("notes/Alpha.md", "Alpha");
const direct = tFile("Alpha.md", "Alpha direct");
@ -218,6 +235,18 @@ describe("VaultNoteCandidateProvider", () => {
},
});
});
it("shares active-view event tracking across panel context providers", () => {
const app = appFixture();
const first = new VaultComposerContextReferenceProvider(app);
const second = new VaultComposerContextReferenceProvider(app);
first.dispose();
expect(app.offref).not.toHaveBeenCalled();
second.dispose();
expect(app.offref).toHaveBeenCalledTimes(2);
});
});
interface AppFixture extends App {