Describe the bug
A Capture with {{CURSOR}} in its format doesn't move the cursor when the destination note is large (~100KB+) and already open in the active editor. There's no error or notice. The cursor just stays where it was, or ends up at the start of the inserted line. Small notes work fine, so it looks intermittent.
To reproduce
- Capture choice: "Capture to active file", Insert after
# Encounters, format {{VALUE:Note}} {{CURSOR}}. "Run Templater on entire destination file" is off.
- Open a note of ~130KB that has a
# Encounters heading, and put the cursor somewhere below the heading.
- Run the capture.
- The text is inserted correctly, but the cursor doesn't move to the
{{CURSOR}} position.
Results from running the same choice via quickAddApi.executeChoice (cursor on the captured line, out of 3–4 runs):
| Note size |
Cursor placed |
editor.getValue() matches immediately after vault.modify |
| 2 KB |
3/3 |
yes |
| 40 KB |
3/3 |
yes |
| 132 KB |
0/3 |
no; matches after ~13–21 ms |
| 250 KB |
(only tested with the patch below) |
no; matches after ~25 ms |
Root cause (from reading 2.27.0 main.js)
- The capture writes with
app.vault.modify(file, newContent), then the cursor helper (minified Vg(app, file, offsets, content)) is called right away.
- The helper compares
editor.getValue() to content and returns false on mismatch (if(s!==n){if(s!==n.replace(/\r\n?/g,"\n"))return!1; …}).
- For small files, Obsidian applies an external
vault.modify to an open editor synchronously, so the check passes.
- For larger files, the editor update lands a few ms later, so the check fails every time and placement is skipped silently.
Suggested fix
Either option works:
- When the destination is the active file, wait for the editor to reflect the write before placing the cursor. For example, retry the comparison for up to ~1s, or wait on the editor's next change/
editor-change event.
- Or, for the active file, apply the capture through an editor transaction instead of
vault.modify, so the offsets are always in sync.
I'm running a local patch that retries the comparison every 25 ms for up to 40 tries (returning true while a retry is pending so callers don't fall through to a second placement). It fixed the problem at every size above (4/4 each at 2 KB, 132 KB and 250 KB).
Related, possibly worth documenting
When "Run Templater on entire destination file after capture" is on, the {{CURSOR}} position is discarded on purpose (cursor = null when afterCapture === "wholeFile"), even if the file contains no <% %>. Users switching from <% tp.file.cursor() %> (which needed that setting) to {{CURSOR}} will hit this. A note in the {{CURSOR}} docs, or only discarding when Templater actually changed the file, would help.
Environment
- QuickAdd 2.27.0
- Obsidian 1.13.7, macOS
- Templater installed (auto-jump to cursor on; no
tp.file.cursor markers in the note)
Describe the bug
A Capture with
{{CURSOR}}in its format doesn't move the cursor when the destination note is large (~100KB+) and already open in the active editor. There's no error or notice. The cursor just stays where it was, or ends up at the start of the inserted line. Small notes work fine, so it looks intermittent.To reproduce
# Encounters, format{{VALUE:Note}} {{CURSOR}}. "Run Templater on entire destination file" is off.# Encountersheading, and put the cursor somewhere below the heading.{{CURSOR}}position.Results from running the same choice via
quickAddApi.executeChoice(cursor on the captured line, out of 3–4 runs):editor.getValue()matches immediately aftervault.modifyRoot cause (from reading 2.27.0
main.js)app.vault.modify(file, newContent), then the cursor helper (minifiedVg(app, file, offsets, content)) is called right away.editor.getValue()tocontentand returnsfalseon mismatch (if(s!==n){if(s!==n.replace(/\r\n?/g,"\n"))return!1; …}).vault.modifyto an open editor synchronously, so the check passes.Suggested fix
Either option works:
editor-changeevent.vault.modify, so the offsets are always in sync.I'm running a local patch that retries the comparison every 25 ms for up to 40 tries (returning
truewhile a retry is pending so callers don't fall through to a second placement). It fixed the problem at every size above (4/4 each at 2 KB, 132 KB and 250 KB).Related, possibly worth documenting
When "Run Templater on entire destination file after capture" is on, the
{{CURSOR}}position is discarded on purpose (cursor = nullwhenafterCapture === "wholeFile"), even if the file contains no<% %>. Users switching from<% tp.file.cursor() %>(which needed that setting) to{{CURSOR}}will hit this. A note in the{{CURSOR}}docs, or only discarding when Templater actually changed the file, would help.Environment
tp.file.cursormarkers in the note)