diff --git a/src/editors/automerge/livePreview/wikiLinkComplete.ts b/src/editors/automerge/livePreview/wikiLinkComplete.ts index 74c5a6e..bb3a8db 100644 --- a/src/editors/automerge/livePreview/wikiLinkComplete.ts +++ b/src/editors/automerge/livePreview/wikiLinkComplete.ts @@ -13,10 +13,12 @@ * editing is untouched the rest of the time. */ import { Prec, type Extension } from '@codemirror/state'; -import { EditorView, keymap } from '@codemirror/view'; +import { EditorView, keymap, type Command } from '@codemirror/view'; import { autocompletion, completionKeymap, + selectedCompletion, + startCompletion, type Completion, type CompletionContext, type CompletionResult, @@ -166,6 +168,46 @@ function renderHeadingLevel(completion: Completion): Node | null { return el; } +/** + * Typing `#` inside the note-name popup accepts whatever is highlighted and + * moves straight on to choosing a heading. + * + * Without this, `[[Dai#` leaves the literal text you typed as the target, which + * usually resolves to nothing — so the heading popup never appears and you have + * to go back and finish the name by hand. Accepting the highlighted option + * first means you can type a few letters and keep going. + * + * Deliberately does NOT close the link: the `]]` is added when the heading is + * accepted, so the caret stays inside `[[Name#` ready for the next popup. + */ +const acceptTargetAndStartAnchor: Command = (view) => { + const { state } = view; + const selected = selectedCompletion(state); + if (!selected) return false; + + const pos = state.selection.main.head; + const line = state.doc.lineAt(pos); + const before = line.text.slice(0, pos - line.from); + const open = before.lastIndexOf('[['); + if (open < 0) return false; + + const typed = before.slice(open + 2); + // Only in the name part; past a `#` or `|` this key is ordinary text. + if (!STILL_A_TARGET.test(typed)) return false; + + const from = line.from + open + 2; + const insert = `${selected.label}#`; + view.dispatch({ + changes: { from, to: pos, insert }, + selection: { anchor: from + insert.length }, + userEvent: 'input.complete', + }); + // The transaction is flagged as a completion, so ask for the heading popup + // explicitly rather than relying on activate-on-typing. + startCompletion(view); + return true; +}; + export const wikiLinkCompletion: Extension = [ autocompletion({ override: [wikiLinkCompletionSource], @@ -176,5 +218,12 @@ export const wikiLinkCompletion: Extension = [ defaultKeymap: false, icons: false, }), - Prec.highest(keymap.of(completionKeymap)), + // Above completionKeymap so it sees `#` before the popup does; it falls + // through to ordinary typing whenever no option is highlighted. + Prec.highest( + keymap.of([ + { key: '#', run: acceptTargetAndStartAnchor }, + ...completionKeymap, + ]), + ), ];