NSTextView complete(): the completion list runs its own modal event loop; testing it headless
NSTextView.complete(nil) does not return until the list closes: -[NSTextViewCompletionController displayCompletions:...] runs a nested nextEvent loop. Any event it does not handle ends the list with insertCompletion(isFinal:true) and is re-posted with postEvent:atStart:. Movement values seen: arrows give non-final 21/22; Return 16; Tab 17; Escape 23 (word = original prefix); typing a word char gives final 0; comma, space or quote give final 20 (right); scroll or a click outside give final 0. A single click on a row is non-final (highlight only); a double-click is final with movement 16. The default super implementation commits the speculatively inserted candidate on any final call, so a comma or a click can type a suggestion nobody chose; it can also leave the suffix selected. To test this off-screen: call app.finishLaunching() and override NSApplication.isActive to return true; otherwise the list closes immediately on window-moved events. Post the key events before calling complete(), or post them from a Timer added in .common modes. NSApp.sendEvent drops re-posted keyDowns in a never-activated app, so deliver them with event.window.sendEvent. Measure the top visible line from drawGlyphs (cacheDisplay), not from layout queries, when non-contiguous layout is on.