MCPcopy Create free account
hub / github.com/ZenNotes/zennotes / installExTabCompletion

Function installExTabCompletion

packages/app-core/src/components/Editor.tsx:1212–1317  ·  view source on GitHub ↗

* Global capture-phase Tab interceptor for the CodeMirror-Vim ex prompt. * * Keystrokes on the prompt input bubble up like any DOM event, but CM-Vim * also registers its own keydown listener on the same input. When two * listeners share a target, they fire in registration order — meaning * CM-V

()

Source from the content-addressed store, hash-verified

1210 * first match, first-Shift-Tab on the last — matches vim's wildmenu.
1211 */
1212function installExTabCompletion(): void {
1213 if (exTabListenerInstalled) return
1214 exTabListenerInstalled = true
1215 if (typeof window === 'undefined') return
1216
1217 window.addEventListener(
1218 'keydown',
1219 (e) => {
1220 if (e.key !== 'Tab') {
1221 // Reset cycle state when the user types anything else — but only
1222 // if the event target IS the ex prompt input. Keys elsewhere in
1223 // the app shouldn't clobber our state (modifier keys fire even
1224 // when the input is focused too, so we ignore those explicitly
1225 // instead of resetting on them).
1226 if (e.key === 'Shift' || e.key === 'Control' || e.key === 'Alt' || e.key === 'Meta') {
1227 return
1228 }
1229 const target = e.target as HTMLElement | null
1230 if (target && target instanceof HTMLInputElement) {
1231 if (target.closest('.cm-vim-panel')) {
1232 // Mark the cycle as expired so the next Tab re-seeds from
1233 // the freshly-typed prefix. `null` means "no cycle in
1234 // progress" — an empty-string prefix would look identical
1235 // to the legitimate "Tab with no input" case.
1236 if (exCycle && exCycle.input === target) exCycle.basePrefix = null
1237 // Enter / Escape / navigation keys dismiss the popup. Plain
1238 // character input also hides it — we want a clean slate on
1239 // the next Tab so the popup reflects the new prefix.
1240 hideWildmenu()
1241 }
1242 }
1243 return
1244 }
1245
1246 const target = e.target as HTMLElement | null
1247 if (!target || !(target instanceof HTMLInputElement)) return
1248 if (!target.closest('.cm-vim-panel')) return
1249
1250 // This is our prompt — take over Tab handling entirely.
1251 e.preventDefault()
1252 e.stopPropagation()
1253 e.stopImmediatePropagation()
1254
1255 const step = e.shiftKey ? -1 : 1
1256 const fresh = !exCycle || exCycle.input !== target || exCycle.basePrefix === null
1257
1258 if (fresh) {
1259 exCycle = {
1260 input: target,
1261 // Seed from whatever the user typed. `target.value` is the
1262 // current content of the prompt, which on a fresh cycle is
1263 // exactly the prefix we want to filter by.
1264 basePrefix: target.value,
1265 matches: computeExMatches(target.value),
1266 cycleIdx: step === 1 ? 0 : -1 // sentinel; normalized below
1267 }
1268 } else if (exCycle) {
1269 exCycle.cycleIdx += step

Callers 1

registerCommandPaletteExFunction · 0.85

Calls 4

hideWildmenuFunction · 0.85
computeExMatchesFunction · 0.85
renderWildmenuFunction · 0.85
positionWildmenuFunction · 0.85

Tested by

no test coverage detected