AppKit: Let an editable text view take the Tab key

CPWindow handled every Tab key press itself and moved to the next key view, so a text view never received Tab and could not insert a tab character.

It now follows Cocoa: an editable text view takes plain Tab and inserts a tab, while Control-Tab, Shift-Tab and Tab in other views still move through the key view loop.

* CPResponder.j: Add _wantsTabKey, which returns NO by default.

* CPTextView/CPTextView.j: Want the Tab key while editable.

* CPWindow/CPWindow.j: Pass plain Tab to a first responder that wants it, and keep the browser from moving its own focus.
This commit is contained in:
David Richardson committed 2026-10-23 20:20:51 -06:00
1 parent 1622990436
commit b222ee0320
3 files changed
+31 -1

No files matched your search

+10
View File
@@ -290,6 +290,16 @@ CPDeleteForwardKeyCode = 46;
{
}
/*
@ignore
Returns YES if the receiver handles the Tab key itself, as an editable text view does by
inserting a tab character. Otherwise the window uses Tab to move to the next key view.
*/
- (BOOL)_wantsTabKey
{
return NO;
}
- (void)insertBackTab:(id)sender
{
}
+10
View File
@@ -1839,6 +1839,16 @@ Sets the selection to a range of characters in response to user action.
[self insertText:@"\t"];
}
/*
@ignore
As in Cocoa, an editable text view takes the Tab key and inserts a tab character.
Control-Tab still moves to the next key view.
*/
- (BOOL)_wantsTabKey
{
return [self isEditable];
}
- (void)insertTabIgnoringFieldEditor:(id)sender
{
[self insertTab:sender];
+11 -1
View File
@@ -2007,8 +2007,14 @@ CPTexturedBackgroundWindowMask
return [[self firstResponder] keyUp:anEvent];
case CPKeyDown:
// Tab moves to the next key view, unless the first responder takes plain Tab itself, as an
// editable text view does to insert a tab character. Control-Tab always moves on, as in Cocoa.
var modifierFlags = [anEvent modifierFlags],
firstResponderTakesTab = !(modifierFlags & (CPShiftKeyMask | CPControlKeyMask)) && [[self firstResponder] _wantsTabKey];
if ([anEvent charactersIgnoringModifiers] === CPTabCharacter &&
!([anEvent modifierFlags] & (CPAlternateKeyMask | CPCommandKeyMask)))
!(modifierFlags & (CPAlternateKeyMask | CPCommandKeyMask)) &&
!firstResponderTakesTab)
{
if ([anEvent modifierFlags] & CPShiftKeyMask)
[self selectPreviousKeyView:self];
@@ -2044,6 +2050,10 @@ CPTexturedBackgroundWindowMask
return;
}
// A first responder that takes Tab gets the key, and the browser must not also move its own focus.
if (firstResponderTakesTab && [anEvent charactersIgnoringModifiers] === CPTabCharacter && CPDOMAvailable)
[[[anEvent window] platformWindow] _propagateCurrentDOMEvent:NO];
[[self firstResponder] keyDown:anEvent];
// Trigger the default button if needed