-
Notifications
You must be signed in to change notification settings - Fork 6k
[macOS] Remove isComposing workaround #33838
Changes from all commits
67cfb7d
d7d60a7
891adf6
d2c4e44
c6bf312
43bc30d
1fe79ed
737cc8c
794d35d
178229c
a098e6d
82c653f
8efd6fb
465a875
0b9a5c4
1e60b0a
295e539
a0e98d2
389dbb9
5f0b88a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,6 +32,7 @@ | |
static NSString* const kUpdateEditStateWithDeltasResponseMethod = | ||
@"TextInputClient.updateEditingStateWithDeltas"; | ||
static NSString* const kPerformAction = @"TextInputClient.performAction"; | ||
static NSString* const kPerformSelectors = @"TextInputClient.performSelectors"; | ||
static NSString* const kMultilineInputType = @"TextInputType.multiline"; | ||
|
||
static NSString* const kTextAffinityDownstream = @"TextAffinity.downstream"; | ||
|
@@ -174,6 +175,13 @@ @interface FlutterTextInputPlugin () | |
*/ | ||
@property(nonatomic) BOOL enableDeltaModel; | ||
|
||
/** | ||
* Used to gather multiple selectors performed in one run loop turn. These | ||
* will be all sent in one platform channel call so that the framework can process | ||
* them in single microtask. | ||
*/ | ||
@property(nonatomic) NSMutableArray* pendingSelectors; | ||
|
||
/** | ||
* Handles a Flutter system message on the text input channel. | ||
*/ | ||
|
@@ -212,6 +220,11 @@ - (void)updateTextAndSelection; | |
*/ | ||
- (NSString*)textAffinityString; | ||
|
||
/** | ||
* Allow overriding run loop mode for test. | ||
*/ | ||
@property(readwrite, nonatomic) NSString* customRunLoopMode; | ||
|
||
@end | ||
|
||
@implementation FlutterTextInputPlugin { | ||
|
@@ -504,10 +517,6 @@ - (NSString*)textAffinityString { | |
: kTextAffinityDownstream; | ||
} | ||
|
||
- (BOOL)isComposing { | ||
return _activeModel && !_activeModel->composing_range().collapsed(); | ||
} | ||
|
||
- (BOOL)handleKeyEvent:(NSEvent*)event { | ||
if (event.type == NSEventTypeKeyUp || | ||
(event.type == NSEventTypeFlagsChanged && event.modifierFlags < _previouslyPressedFlags)) { | ||
|
@@ -615,6 +624,11 @@ - (NSTextInputContext*)inputContext { | |
#pragma mark - | ||
#pragma mark NSTextInputClient | ||
|
||
- (void)insertTab:(id)sender { | ||
// Implementing insertTab: makes AppKit send tab as command, instead of | ||
// insertText with '\t'. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. /cc @loic-sharma who's looking at tab handling on Windows. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since flutter text doesn't define tab shortcut and thus it's always handled as focus, when I wanted to insert tab I simply used this Action class InsertTabAction extends Action<NextFocusIntent> {
final TextEditingController controller;
InsertTabAction(this.controller);
void _sendIntent(Intent intent) {
final BuildContext? primaryContext = primaryFocus?.context;
if (primaryContext != null) {
Actions.invoke(primaryContext, intent);
}
}
@override
Object? invoke(NextFocusIntent intent) {
_sendIntent(ReplaceTextIntent(controller.value, '', controller.selection,
SelectionChangedCause.keyboard));
_sendIntent(ReplaceTextIntent(controller.value, '\t', controller.selection,
SelectionChangedCause.keyboard));
return null;
}
} and bound it to /cc @loic-sharma There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you illustrate why using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It wouldn't. That's why I said it requires binding to |
||
} | ||
|
||
- (void)insertText:(id)string replacementRange:(NSRange)range { | ||
if (_activeModel == nullptr) { | ||
return; | ||
|
@@ -670,6 +684,36 @@ - (void)doCommandBySelector:(SEL)selector { | |
void (*func)(id, SEL, id) = reinterpret_cast<void (*)(id, SEL, id)>(imp); | ||
func(self, selector, nil); | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the selector is implemented by There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All selectors are implemented by We only seem to be handling We could also check with objc runtime to see whether particular selector is overridden, which seems a bit of overkill, but that will need special exception for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's a |
||
if (selector == @selector(insertNewline:)) { | ||
// Already handled through text insertion (multiline) or action. | ||
return; | ||
} | ||
|
||
// Group multiple selectors received within a single run loop turn so that | ||
// the framework can process them in single microtask. | ||
NSString* name = NSStringFromSelector(selector); | ||
if (_pendingSelectors == nil) { | ||
_pendingSelectors = [NSMutableArray array]; | ||
} | ||
[_pendingSelectors addObject:name]; | ||
|
||
if (_pendingSelectors.count == 1) { | ||
__weak NSMutableArray* selectors = _pendingSelectors; | ||
__weak FlutterMethodChannel* channel = _channel; | ||
__weak NSNumber* clientID = self.clientID; | ||
|
||
CFStringRef runLoopMode = self.customRunLoopMode != nil | ||
? (__bridge CFStringRef)self.customRunLoopMode | ||
: kCFRunLoopCommonModes; | ||
|
||
CFRunLoopPerformBlock(CFRunLoopGetMain(), runLoopMode, ^{ | ||
if (selectors.count > 0) { | ||
[channel invokeMethod:kPerformSelectors arguments:@[ clientID, selectors ]]; | ||
[selectors removeAllObjects]; | ||
} | ||
}); | ||
} | ||
} | ||
|
||
- (void)insertNewline:(id)sender { | ||
|
@@ -696,6 +740,19 @@ - (void)setMarkedText:(id)string | |
if (!_activeModel->composing()) { | ||
_activeModel->BeginComposing(); | ||
} | ||
|
||
if (replacementRange.location != NSNotFound) { | ||
// According to the NSTextInputClient documentation replacementRange is | ||
// computed from the beginning of the marked text. That doesn't seem to be | ||
// the case, because in situations where the replacementRange is actually | ||
// specified (i.e. when switching between characters equivalent after long | ||
// key press) the replacementRange is provided while there is no composition. | ||
_activeModel->SetComposingRange( | ||
knopp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
flutter::TextRange(replacementRange.location, | ||
replacementRange.location + replacementRange.length), | ||
0); | ||
} | ||
|
||
flutter::TextRange composingBeforeChange = _activeModel->composing_range(); | ||
flutter::TextRange selectionBeforeChange = _activeModel->selection(); | ||
|
||
|
Uh oh!
There was an error while loading. Please reload this page.