Compare commits

...

3 Commits

Author SHA1 Message Date
Julien Calixte
be86b82823 fix(completion): stop offering status once its quote is closed
Some checks failed
Deploy to GitHub Pages / build (push) Failing after 23s
Deploy to GitHub Pages / deploy (push) Has been skipped
The pattern's trailing optional quote let a complete `status = "x"`
match, so the popup reopened after the closing quote. Drop it so the
match ends with the value, while still typing remains supported.
2026-06-19 18:54:37 +02:00
Julien Calixte
e726675d77 fix(editor): don't open the completion popup on deletion
Backspace/delete/cut emit an input event too, which reopened the
popup mid-edit. Skip the refresh on any delete inputType.
2026-06-19 18:53:55 +02:00
Julien Calixte
5a4fbb452d fix(editor): close completion popup after accepting a value
Accepting an enum value (e.g. "on-track") re-ran the chained refresh,
which re-matched the value and reopened the popup. Chain only after
completing a key, when there are still values to offer.
2026-06-19 18:53:44 +02:00
3 changed files with 15 additions and 3 deletions

View File

@@ -87,7 +87,9 @@ function accept(i: number) {
nextTick(() => {
el.setSelectionRange(caret, caret)
el.focus()
refresh() // chain, e.g. `status = ` immediately offers the enum values
// Chain only after completing a key (insert ends `= `), so its values pop
// up next. A value or header is terminal — re-running would re-offer it.
if (item.insert.endsWith("= ")) refresh()
})
}
@@ -145,6 +147,12 @@ function escapeHtml(s: string): string {
function onInput(e: Event) {
emit("update:modelValue", (e.target as HTMLTextAreaElement).value)
// Deleting text (backspace, forward-delete, cut) shouldn't summon the popup —
// the author is removing, not asking for suggestions. Close it and stop.
if ((e as InputEvent).inputType?.startsWith("delete")) {
completion.value = null
return
}
refresh()
}

View File

@@ -66,6 +66,10 @@ describe("completion context", () => {
expect(labelsAt('[[feature]]\nstatus = "o|')).toEqual(["on-track", "off-track"])
})
it("offers nothing once the status value's quote is closed", () => {
expect(getCompletions(...atCaret('[[feature]]\nstatus = "on-track"|'))).toBeNull()
})
it("suggests feature names inside a milestone requires array", () => {
const source = '[[feature]]\nname = "Payments"\n\n[[milestone]]\nrequires = ["|'
expect(labelsAt(source)).toEqual(["Payments"])

View File

@@ -63,8 +63,8 @@ export function getCompletions(
const lineStart = source.lastIndexOf("\n", caret - 1) + 1
const linePrefix = source.slice(lineStart, caret)
// ── value: status = "<here>" ──────────────────────────────────────────────
const status = /^(\s*status\s*=\s*)"?([A-Za-z-]*)"?$/.exec(linePrefix)
// ── value: status = "<here>" — while typing, not once the quote is closed ──
const status = /^(\s*status\s*=\s*)"?([A-Za-z-]*)$/.exec(linePrefix)
if (status) {
const items = filter(
STATUSES.map((s) => ({ label: s, insert: `"${s}"` })),