Skip to content

Commit 63fe23d

Browse files
committed
fix(templater): bind api calls and await trigger-on-create
1 parent 1ac5ac4 commit 63fe23d

3 files changed

Lines changed: 114 additions & 12 deletions

File tree

src/engine/CaptureChoiceEngine.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import {
2626
openFile,
2727
overwriteTemplaterOnce,
2828
templaterParseTemplate,
29-
waitForFileToStopChanging,
29+
waitForTemplaterTriggerOnCreateToComplete,
3030
} from "../utilityObsidian";
3131
import { isCancellationError, reportError } from "../utils/errorUtils";
3232
import { QuickAddChoiceEngine } from "./QuickAddChoiceEngine";
@@ -467,7 +467,7 @@ export class CaptureChoiceEngine extends QuickAddChoiceEngine {
467467
) {
468468
await overwriteTemplaterOnce(this.app, file);
469469
} else if (isTemplaterTriggerOnCreateEnabled(this.app)) {
470-
await waitForFileToStopChanging(this.app, file);
470+
await waitForTemplaterTriggerOnCreateToComplete(this.app, file);
471471
}
472472

473473
// Read the file fresh from disk to avoid any potential cached content
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { describe, expect, it } from "vitest";
2+
import { App, TFile } from "obsidian";
3+
import { jumpToNextTemplaterCursorIfPossible, templaterParseTemplate } from "./utilityObsidian";
4+
5+
describe("templaterParseTemplate", () => {
6+
it("calls parse_template with the correct `this` context", async () => {
7+
const app = new App();
8+
const file = new TFile();
9+
file.path = "QA.md";
10+
file.extension = "md";
11+
12+
const templater = {
13+
functions_generator: { ok: true },
14+
parse_template: async function (
15+
this: any,
16+
_opts: unknown,
17+
content: string,
18+
): Promise<string> {
19+
expect(this?.functions_generator?.ok).toBe(true);
20+
return `rendered:${content}`;
21+
},
22+
};
23+
24+
(app as any).plugins.plugins["templater-obsidian"] = { templater };
25+
26+
const result = await templaterParseTemplate(app as any, "hello", file as any);
27+
expect(result).toBe("rendered:hello");
28+
});
29+
});
30+
31+
describe("jumpToNextTemplaterCursorIfPossible", () => {
32+
it("calls jump_to_next_cursor_location with the correct `this` context", async () => {
33+
const app = new App();
34+
const file = new TFile();
35+
file.path = "QA.md";
36+
file.extension = "md";
37+
38+
(app as any).workspace.getActiveFile = () => file;
39+
40+
const editorHandler = {
41+
plugin: { ok: true },
42+
jump_to_next_cursor_location: async function (
43+
this: any,
44+
_targetFile: unknown,
45+
_autoJump: unknown,
46+
): Promise<void> {
47+
expect(this?.plugin?.ok).toBe(true);
48+
},
49+
};
50+
51+
(app as any).plugins.plugins["templater-obsidian"] = {
52+
settings: { auto_jump_to_cursor: true },
53+
editor_handler: editorHandler,
54+
};
55+
56+
await jumpToNextTemplaterCursorIfPossible(app as any, file as any);
57+
});
58+
});

src/utilityObsidian.ts

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export type TemplaterPluginLike = {
3737
functions_generator?: { teardown?: () => Promise<void> };
3838
};
3939
editor_handler?: {
40+
plugin?: unknown;
4041
jump_to_next_cursor_location?: (
4142
file?: TFile | null,
4243
auto_jump?: boolean,
@@ -90,6 +91,41 @@ export function isTemplaterTriggerOnCreateEnabled(app: App): boolean {
9091
return !!getTemplaterPlugin(app)?.settings?.trigger_on_file_creation;
9192
}
9293

94+
export async function waitForTemplaterTriggerOnCreateToComplete(
95+
app: App,
96+
file: TFile,
97+
opts: { timeoutMs?: number; appearTimeoutMs?: number } = {},
98+
): Promise<void> {
99+
if (file.extension !== "md") return;
100+
if (!isTemplaterTriggerOnCreateEnabled(app)) return;
101+
102+
const plugin = getTemplaterPlugin(app);
103+
const pendingFiles = plugin?.templater?.files_with_pending_templates;
104+
if (!(pendingFiles instanceof Set)) {
105+
await waitForFileToStopChanging(app, file, {
106+
timeoutMs: opts.timeoutMs ?? 5000,
107+
gracePeriodMs: opts.appearTimeoutMs ?? 2500,
108+
quietPeriodMs: 200,
109+
});
110+
return;
111+
}
112+
113+
const { timeoutMs = 5000, appearTimeoutMs = 2500 } = opts;
114+
const start = Date.now();
115+
116+
while (Date.now() - start < appearTimeoutMs) {
117+
if (pendingFiles.has(file.path)) break;
118+
await new Promise((r) => setTimeout(r, 50));
119+
}
120+
121+
while (Date.now() - start < timeoutMs) {
122+
if (!pendingFiles.has(file.path)) break;
123+
await new Promise((r) => setTimeout(r, 50));
124+
}
125+
126+
await waitForFileSettle(app, file, 800);
127+
}
128+
93129
type TemplaterFileCreationSuppressionState = {
94130
count: number;
95131
hadPathInitially: boolean;
@@ -302,8 +338,9 @@ export async function overwriteTemplaterOnce(
302338
if (file.extension !== "md") return;
303339

304340
const plugin = getTemplaterPlugin(app);
305-
const overwrite = plugin?.templater?.overwrite_file_commands;
306-
if (!plugin || typeof overwrite !== "function") return;
341+
const templater = plugin?.templater;
342+
const overwrite = templater?.overwrite_file_commands;
343+
if (!plugin || !templater || typeof overwrite !== "function") return;
307344

308345
const { skipIfNoTags = true, postWait = true } = opts;
309346

@@ -327,7 +364,8 @@ export async function overwriteTemplaterOnce(
327364
}
328365

329366
try {
330-
await overwrite(file);
367+
// Preserve Templater's internal `this` context.
368+
await overwrite.call(templater, file);
331369
if (postWait) {
332370
await waitForFileSettle(app, file, 800);
333371
}
@@ -356,10 +394,14 @@ export async function templaterParseTemplate(
356394
if (targetFile.extension !== "md") return templateContent;
357395

358396
const plugin = getTemplaterPlugin(app);
359-
const parseTemplate = plugin?.templater?.parse_template;
360-
if (!plugin || typeof parseTemplate !== "function") return templateContent;
361-
362-
return await parseTemplate(
397+
const templater = plugin?.templater;
398+
const parseTemplate = templater?.parse_template;
399+
if (!plugin || !templater || typeof parseTemplate !== "function")
400+
return templateContent;
401+
402+
// Preserve Templater's internal `this` context.
403+
return await parseTemplate.call(
404+
templater,
363405
// `run_mode: 4` maps to Templater's internal `RunMode.DynamicProcessor`.
364406
{ target_file: targetFile, run_mode: 4 },
365407
templateContent,
@@ -375,20 +417,22 @@ export async function jumpToNextTemplaterCursorIfPossible(
375417

376418
const plugin = getTemplaterPlugin(app);
377419
const autoJumpEnabled = !!plugin?.settings?.auto_jump_to_cursor;
378-
const jump = plugin?.editor_handler?.jump_to_next_cursor_location;
420+
const editorHandler = plugin?.editor_handler;
421+
const jump = editorHandler?.jump_to_next_cursor_location;
379422

380423
if (!autoJumpEnabled) return;
381424

382425
if (typeof jump === "function") {
383426
try {
384-
await jump(file, true);
427+
// Preserve Templater's internal `this` context.
428+
await jump.call(editorHandler, file, true);
385429
return;
386430
} catch (err) {
387431
log.logWarning(
388432
`jumpToNextTemplaterCursorIfPossible: API failed – ${(err as Error).message}`,
389433
);
390-
}
391434
}
435+
}
392436

393437
try {
394438
(

0 commit comments

Comments
 (0)