Chromium Code Reviews| Index: chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_editor.mm |
| diff --git a/chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_editor.mm b/chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_editor.mm |
| index 83126cf2e5eee10622c834fac7b0c7ca7142aed2..447058ed02775cb1a97b54518174c53a05a45a42 100644 |
| --- a/chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_editor.mm |
| +++ b/chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_editor.mm |
| @@ -4,6 +4,7 @@ |
| #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_editor.h" |
| +#include "base/i18n/break_iterator.h" |
| #include "base/mac/sdk_forward_declarations.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/sys_string_conversions.h" |
| @@ -49,8 +50,54 @@ BOOL ThePasteboardIsTooDamnBig() { |
| return [[pb stringForType:type] length] > kMaxPasteLength; |
| } |
| +NSRange VisualSelectionRangeFromRange(NSRange range, NSString* string) { |
| + if (range.location >= string.length) { |
| + return range; |
| + } |
| + base::string16 text = base::SysNSStringToUTF16(string); |
| + // TODO(lgrey): Should the break iterator be cached? |
|
erikchen
2016/10/12 22:37:48
sounds like premature optimization to me.
lgrey
2016/10/13 15:42:52
Removed comment
|
| + base::i18n::BreakIterator grapheme_iterator( |
| + text, base::i18n::BreakIterator::BREAK_CHARACTER); |
| + if (!grapheme_iterator.Init()) { |
| + return range; |
| + } |
| + while (range.location < text.length() && |
|
erikchen
2016/10/12 22:37:48
interesting. This works because NSString's interna
lgrey
2016/10/13 15:42:52
Added a comment. I could also change it to |string
erikchen
2016/10/13 17:08:28
Comments seems good enough.
|
| + !grapheme_iterator.IsGraphemeBoundary( |
| + static_cast<size_t>(range.location))) { |
| + range.location++; |
| + if (range.length > 0) { |
| + range.length--; |
| + } |
| + } |
| + return range; |
| +} |
| + |
| } // namespace |
| +// Method exposed for the purpose of overriding. |
| +// Used to restore model's selection range when the view doesn't |
| +// match the model due to combining characters. |
| +// |
| +// In some cases, (completing 'y' to 'ÿour', for example), the autocomplete |
| +// system requests a selection range that begins on a combining character. |
| +// setSelectedRange: and friends document that the range passed to them |
| +// "must begin and end on glyph boundaries and not split base glyphs and |
| +// their nonspacing marks". If passed such a range, the selection is |
| +// expanded to include the original user input, preventing the user |
| +// from being able to type other words beginning with that letter. |
| +// |
| +// To resolve this, the field editor modifies the selection to start |
| +// on the next glyph boundary, then keeps track of the original and |
| +// modified selections, substituting the original when the user takes |
| +// actions that operate on the selection. Since there are many methods |
| +// in NSResponder (for example deleteToBeginningOfLine:) that operate |
| +// on the selection, rather than shimming them all, we override this |
| +// private method that they're implemented in terms of. |
| +// |
| +@interface NSTextView (PrivateTextEditing) |
| +- (void)_userReplaceRange:(NSRange)range withString:(NSString*)string; |
| +@end |
| + |
| @interface AutocompleteTextFieldEditor ()<NSDraggingSource> |
| @end |
| @@ -400,6 +447,14 @@ BOOL ThePasteboardIsTooDamnBig() { |
| return [dropHandler_ performDragOperation:sender]; |
| } |
| +- (void)_userReplaceRange:(NSRange)range withString:(NSString*)string { |
| + if (NSEqualRanges(visualSelectionRange_, range) && |
| + !NSEqualRanges(visualSelectionRange_, actualSelectionRange_)) { |
| + range = actualSelectionRange_; |
| + } |
| + [super _userReplaceRange:range withString:string]; |
| +} |
| + |
| // Prevent control characters from being entered into the Omnibox. |
| // This is invoked for keyboard entry, not for pasting. |
| - (void)insertText:(id)aString { |
| @@ -431,11 +486,15 @@ BOOL ThePasteboardIsTooDamnBig() { |
| DCHECK_EQ(range.length, 0U); |
| } |
| - // NOTE: If |aString| is empty, this intentionally replaces the |
| - // selection with empty. This seems consistent with the case where |
| - // the input contained a mixture of characters and the string ended |
| - // up not empty. |
| - [super insertText:aString]; |
| + if (!NSEqualRanges(visualSelectionRange_, actualSelectionRange_)) { |
| + [super replaceCharactersInRange:actualSelectionRange_ withString:aString]; |
| + } else { |
| + // NOTE: If |aString| is empty, this intentionally replaces the |
| + // selection with empty. This seems consistent with the case where |
| + // the input contained a mixture of characters and the string ended |
| + // up not empty. |
| + [super insertText:aString]; |
| + } |
| } |
| - (NSRange)selectionRangeForProposedRange:(NSRange)proposedSelRange |
| @@ -451,7 +510,12 @@ BOOL ThePasteboardIsTooDamnBig() { |
| - (void)setSelectedRange:(NSRange)charRange |
| affinity:(NSSelectionAffinity)affinity |
| stillSelecting:(BOOL)flag { |
| - [super setSelectedRange:charRange affinity:affinity stillSelecting:flag]; |
| + actualSelectionRange_ = charRange; |
| + visualSelectionRange_ = |
| + VisualSelectionRangeFromRange(charRange, [self string]); |
| + [super setSelectedRange:visualSelectionRange_ |
| + affinity:affinity |
| + stillSelecting:flag]; |
| // We're only interested in selection changes directly caused by keyboard |
| // input from the user. |
| @@ -459,6 +523,10 @@ BOOL ThePasteboardIsTooDamnBig() { |
| textChangedByKeyEvents_ = YES; |
| } |
| +- (NSRange)selectedRange { |
| + return actualSelectionRange_; |
|
erikchen
2016/10/12 22:37:48
Is there any way we can avoid overriding this meth
lgrey
2016/10/13 15:42:51
We could expose a new method like |actualRange| or
erikchen
2016/10/13 17:08:28
If the only reason this code is necessary is to pr
lgrey
2016/10/13 21:03:56
Exposed actualSelectedRange_ as a property and use
|
| +} |
| + |
| - (void)interpretKeyEvents:(NSArray *)eventArray { |
| DCHECK(!interpretingKeyEvents_); |
| interpretingKeyEvents_ = YES; |