| 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..9bda172a88655fd73100db09fc64b83c51c2d557 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,9 @@
|
|
|
| #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_editor.h"
|
|
|
| +#include "base/i18n/break_iterator.h"
|
| +#include "base/mac/foundation_util.h"
|
| +#include "base/mac/scoped_nsobject.h"
|
| #include "base/mac/sdk_forward_declarations.h"
|
| #include "base/strings/string_util.h"
|
| #include "base/strings/sys_string_conversions.h"
|
| @@ -49,13 +52,58 @@ BOOL ThePasteboardIsTooDamnBig() {
|
| return [[pb stringForType:type] length] > kMaxPasteLength;
|
| }
|
|
|
| +NSRange VisualSelectedRangeFromRange(NSRange range, NSString* string) {
|
| + if (range.location >= string.length)
|
| + return range;
|
| + base::string16 text = base::SysNSStringToUTF16(string);
|
| + base::i18n::BreakIterator grapheme_iterator(
|
| + text, base::i18n::BreakIterator::BREAK_CHARACTER);
|
| + if (!grapheme_iterator.Init())
|
| + return range;
|
| + // This works because NSString uses UTF-16 code units.
|
| + while (range.location < text.length() &&
|
| + !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
|
|
|
| @implementation AutocompleteTextFieldEditor
|
|
|
| +@synthesize actualSelectedRange = actualSelectedRange_;
|
| +
|
| - (BOOL)shouldDrawInsertionPoint {
|
| return [super shouldDrawInsertionPoint] &&
|
| ![[[self delegate] cell] hideFocusState];
|
| @@ -400,6 +448,14 @@ BOOL ThePasteboardIsTooDamnBig() {
|
| return [dropHandler_ performDragOperation:sender];
|
| }
|
|
|
| +- (void)_userReplaceRange:(NSRange)range withString:(NSString*)string {
|
| + if (NSEqualRanges(visualSelectedRange_, range) &&
|
| + !NSEqualRanges(visualSelectedRange_, actualSelectedRange_)) {
|
| + range = actualSelectedRange_;
|
| + }
|
| + [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 +487,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(visualSelectedRange_, actualSelectedRange_)) {
|
| + [super replaceCharactersInRange:actualSelectedRange_ 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
|
| @@ -459,6 +519,24 @@ BOOL ThePasteboardIsTooDamnBig() {
|
| textChangedByKeyEvents_ = YES;
|
| }
|
|
|
| +- (void)setSelectedRanges:(NSArray*)ranges
|
| + affinity:(NSSelectionAffinity)affinity
|
| + stillSelecting:(BOOL)flag {
|
| + DCHECK(ranges.count > 0);
|
| + base::scoped_nsobject<NSMutableArray> mutableRanges([ranges mutableCopy]);
|
| + // |ranges| is sorted, and empirically, the first range passed is returned
|
| + // as selectedRange.
|
| + NSRange firstRange = [base::mac::ObjCCastStrict<NSValue>(
|
| + [mutableRanges firstObject]) rangeValue];
|
| + actualSelectedRange_ = firstRange;
|
| + visualSelectedRange_ =
|
| + VisualSelectedRangeFromRange(firstRange, [self string]);
|
| + NSValue* boxedVisualRange = [NSValue valueWithRange:visualSelectedRange_];
|
| + [mutableRanges replaceObjectAtIndex:0 withObject:boxedVisualRange];
|
| +
|
| + [super setSelectedRanges:mutableRanges affinity:affinity stillSelecting:flag];
|
| +}
|
| +
|
| - (void)interpretKeyEvents:(NSArray *)eventArray {
|
| DCHECK(!interpretingKeyEvents_);
|
| interpretingKeyEvents_ = YES;
|
| @@ -564,7 +642,7 @@ BOOL ThePasteboardIsTooDamnBig() {
|
|
|
| - (BOOL)validateMenuItem:(NSMenuItem*)item {
|
| if ([item action] == @selector(copyToFindPboard:))
|
| - return [self selectedRange].length > 0;
|
| + return actualSelectedRange_.length > 0;
|
| if ([item action] == @selector(pasteAndGo:)) {
|
| // TODO(rohitrao): If the clipboard is empty, should we show a
|
| // greyed-out "Paste and Go" or nothing at all?
|
| @@ -576,11 +654,10 @@ BOOL ThePasteboardIsTooDamnBig() {
|
| }
|
|
|
| - (void)copyToFindPboard:(id)sender {
|
| - NSRange selectedRange = [self selectedRange];
|
| - if (selectedRange.length == 0)
|
| + if (actualSelectedRange_.length == 0)
|
| return;
|
| NSAttributedString* selection =
|
| - [self attributedSubstringForProposedRange:selectedRange
|
| + [self attributedSubstringForProposedRange:actualSelectedRange_
|
| actualRange:NULL];
|
| if (!selection)
|
| return;
|
|
|