mirror of
https://github.com/gnustep/libs-gui.git
synced 2025-05-31 21:00:47 +00:00
NSTextView continuous spell checking
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@32534 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
parent
0713446436
commit
7e691f69c9
6 changed files with 353 additions and 7 deletions
|
@ -1,3 +1,11 @@
|
||||||
|
2011-03-11 Eric Wasylishen <ewasylishen@gmail.com>
|
||||||
|
|
||||||
|
* Source/NSLayoutManager.m:
|
||||||
|
* Source/externs.m:
|
||||||
|
* Source/NSTextView.m:
|
||||||
|
* Headers/AppKit/NSAttributedString.h:
|
||||||
|
* Headers/AppKit/NSTextView.h: Implement continuous spell checking
|
||||||
|
|
||||||
2011-03-11 Fred Kiefer <FredKiefer@gmx.de>
|
2011-03-11 Fred Kiefer <FredKiefer@gmx.de>
|
||||||
|
|
||||||
* ColorPickers/GSStandardColorPicker.m
|
* ColorPickers/GSStandardColorPicker.m
|
||||||
|
|
|
@ -142,6 +142,9 @@ APPKIT_EXPORT NSString *NSWebResourceLoadDelegateDocumentOption;
|
||||||
|
|
||||||
APPKIT_EXPORT NSString *NSCharacterShapeAttributeName;
|
APPKIT_EXPORT NSString *NSCharacterShapeAttributeName;
|
||||||
APPKIT_EXPORT const unsigned NSUnderlineByWordMask;
|
APPKIT_EXPORT const unsigned NSUnderlineByWordMask;
|
||||||
|
APPKIT_EXPORT NSString *NSSpellingStateAttributeName;
|
||||||
|
APPKIT_EXPORT const unsigned NSSpellingStateSpellingFlag;
|
||||||
|
APPKIT_EXPORT const unsigned NSSpellingStateGrammarFlag;
|
||||||
|
|
||||||
// readFrom... attributes
|
// readFrom... attributes
|
||||||
|
|
||||||
|
|
|
@ -111,6 +111,7 @@ therefore be stored in the NSLayoutManager to avoid problems.
|
||||||
would be very awkward if they weren't. */
|
would be very awkward if they weren't. */
|
||||||
unsigned allows_undo:1;
|
unsigned allows_undo:1;
|
||||||
unsigned smart_insert_delete:1;
|
unsigned smart_insert_delete:1;
|
||||||
|
unsigned continuous_spell_checking:1;
|
||||||
/* End of shared attributes. */
|
/* End of shared attributes. */
|
||||||
|
|
||||||
|
|
||||||
|
@ -243,6 +244,10 @@ therefore be stored in the NSLayoutManager to avoid problems.
|
||||||
NSParagraphStyle *_defaultParagraphStyle;
|
NSParagraphStyle *_defaultParagraphStyle;
|
||||||
NSDictionary *_linkTextAttributes;
|
NSDictionary *_linkTextAttributes;
|
||||||
NSRange _markedRange;
|
NSRange _markedRange;
|
||||||
|
|
||||||
|
// Text checking (spelling/grammar)
|
||||||
|
NSTimer *_textCheckingTimer;
|
||||||
|
NSRect _lastCheckedRect;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -107,6 +107,7 @@ first. Remaining cases, highest priority first:
|
||||||
|
|
||||||
#import <Foundation/NSEnumerator.h>
|
#import <Foundation/NSEnumerator.h>
|
||||||
#import <Foundation/NSException.h>
|
#import <Foundation/NSException.h>
|
||||||
|
#import <Foundation/NSValue.h>
|
||||||
#import "AppKit/NSAttributedString.h"
|
#import "AppKit/NSAttributedString.h"
|
||||||
#import "AppKit/NSColor.h"
|
#import "AppKit/NSColor.h"
|
||||||
#import "AppKit/NSImage.h"
|
#import "AppKit/NSImage.h"
|
||||||
|
@ -122,6 +123,16 @@ first. Remaining cases, highest priority first:
|
||||||
#import "GNUstepGUI/GSLayoutManager_internal.h"
|
#import "GNUstepGUI/GSLayoutManager_internal.h"
|
||||||
|
|
||||||
|
|
||||||
|
@interface NSLayoutManager (spelling)
|
||||||
|
|
||||||
|
-(void) _drawSpellingState: (NSInteger)spellingState
|
||||||
|
forGylphRange: (NSRange)range
|
||||||
|
lineFragmentRect: (NSRect)fragmentRect
|
||||||
|
lineFragmentGlyphRange: (NSRange)fragmentGlyphRange
|
||||||
|
containerOrigin: (NSPoint)containerOrigin;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
@interface NSLayoutManager (LayoutHelpers)
|
@interface NSLayoutManager (LayoutHelpers)
|
||||||
-(void) _doLayoutToContainer: (int)cindex point: (NSPoint)p;
|
-(void) _doLayoutToContainer: (int)cindex point: (NSPoint)p;
|
||||||
@end
|
@end
|
||||||
|
@ -1718,6 +1729,41 @@ for (i = 0; i < gbuf_len; i++) printf(" %3i : %04x\n", i, gbuf[i]); */
|
||||||
}
|
}
|
||||||
i += underlinedCharacterRange.length;
|
i += underlinedCharacterRange.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Draw spelling state (i.e. red underline for misspelled words)
|
||||||
|
|
||||||
|
for (i=characterRange.location; i<NSMaxRange(characterRange); )
|
||||||
|
{
|
||||||
|
NSRange underlinedCharacterRange;
|
||||||
|
id underlineValue = [self temporaryAttribute: NSSpellingStateAttributeName
|
||||||
|
atCharacterIndex: i
|
||||||
|
longestEffectiveRange: &underlinedCharacterRange
|
||||||
|
inRange: characterRange];
|
||||||
|
if (underlineValue != nil && [underlineValue integerValue] != 0)
|
||||||
|
{
|
||||||
|
const NSRange underlinedGylphRange = [self glyphRangeForCharacterRange: underlinedCharacterRange
|
||||||
|
actualCharacterRange: NULL];
|
||||||
|
|
||||||
|
// we have a range of glpyhs that need underlining, which might span
|
||||||
|
// multiple line fragments, so we need to iterate though the line fragments
|
||||||
|
for (j=underlinedGylphRange.location; j<NSMaxRange(underlinedGylphRange); )
|
||||||
|
{
|
||||||
|
NSRange lineFragmentGlyphRange;
|
||||||
|
const NSRect lineFragmentRect = [self lineFragmentRectForGlyphAtIndex: j
|
||||||
|
effectiveRange: &lineFragmentGlyphRange];
|
||||||
|
const NSRange rangeToUnderline = NSIntersectionRange(underlinedGylphRange, lineFragmentGlyphRange);
|
||||||
|
|
||||||
|
[self _drawSpellingState: [underlineValue integerValue]
|
||||||
|
forGylphRange: rangeToUnderline
|
||||||
|
lineFragmentRect: lineFragmentRect
|
||||||
|
lineFragmentGlyphRange: lineFragmentGlyphRange
|
||||||
|
containerOrigin: containerOrigin];
|
||||||
|
|
||||||
|
j = NSMaxRange(rangeToUnderline);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i += underlinedCharacterRange.length;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1902,6 +1948,55 @@ static void GSDrawPatternLine(NSPoint start, NSPoint end, NSInteger pattern, CGF
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@implementation NSLayoutManager (spelling)
|
||||||
|
|
||||||
|
-(void) _drawSpellingState: (NSInteger)spellingState
|
||||||
|
forGylphRange: (NSRange)range
|
||||||
|
lineFragmentRect: (NSRect)fragmentRect
|
||||||
|
lineFragmentGlyphRange: (NSRange)fragmentGlyphRange
|
||||||
|
containerOrigin: (NSPoint)containerOrigin
|
||||||
|
{
|
||||||
|
NSBezierPath *path;
|
||||||
|
const float pattern[2] = {2.5, 1.0};
|
||||||
|
NSFont *largestFont = [self effectiveFontForGlyphAtIndex: range.location // NOTE: GS private method
|
||||||
|
range: NULL];
|
||||||
|
NSPoint start = [self locationForGlyphAtIndex: range.location];
|
||||||
|
NSPoint end = [self locationForGlyphAtIndex: NSMaxRange(range) - 1]; //FIXME: check length > 0
|
||||||
|
|
||||||
|
if (spellingState == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: calculate the underline position correctly, using the font on both the start and end glyph
|
||||||
|
start.y += [largestFont pointSize] * 0.07;
|
||||||
|
end.y += [largestFont pointSize] * 0.07;
|
||||||
|
|
||||||
|
end.x += [largestFont advancementForGlyph: [self glyphAtIndex: (NSMaxRange(range) - 1)]].width;
|
||||||
|
|
||||||
|
start = NSMakePoint(start.x + containerOrigin.x + fragmentRect.origin.x, start.y + containerOrigin.y + fragmentRect.origin.y);
|
||||||
|
end = NSMakePoint(end.x + containerOrigin.x + fragmentRect.origin.x, end.y + containerOrigin.y + fragmentRect.origin.y);
|
||||||
|
|
||||||
|
path = [NSBezierPath bezierPath];
|
||||||
|
[path setLineDash: pattern count: 2 phase: 0];
|
||||||
|
[path setLineWidth: 1.5];
|
||||||
|
[path moveToPoint: start];
|
||||||
|
[path lineToPoint: end];
|
||||||
|
|
||||||
|
if ((spellingState & NSSpellingStateGrammarFlag) != 0)
|
||||||
|
{
|
||||||
|
[[NSColor greenColor] set];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
[[NSColor redColor] set];
|
||||||
|
}
|
||||||
|
|
||||||
|
[path stroke];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
|
||||||
@implementation NSLayoutManager
|
@implementation NSLayoutManager
|
||||||
|
|
||||||
|
|
|
@ -148,6 +148,14 @@ Interface for a bunch of internal methods that need to be cleaned up.
|
||||||
//
|
//
|
||||||
- (void) copySelection;
|
- (void) copySelection;
|
||||||
- (void) pasteSelection;
|
- (void) pasteSelection;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Text checking
|
||||||
|
*/
|
||||||
|
- (void) _scheduleTextCheckingInVisibleRectIfNeeded;
|
||||||
|
- (void) _textDidChange: (NSNotification*)notif;
|
||||||
|
- (void) _textCheckingTimerFired: (NSTimer *)t;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
@ -187,6 +195,7 @@ Interface for a bunch of internal methods that need to be cleaned up.
|
||||||
([tv isFieldEditor]?0x10:0) |
|
([tv isFieldEditor]?0x10:0) |
|
||||||
([tv usesFontPanel]?0x20:0) |
|
([tv usesFontPanel]?0x20:0) |
|
||||||
([tv isRulerVisible]?0x40:0) |
|
([tv isRulerVisible]?0x40:0) |
|
||||||
|
([tv isContinuousSpellCheckingEnabled]?0x80:0) |
|
||||||
([tv usesRuler]?0x100:0) |
|
([tv usesRuler]?0x100:0) |
|
||||||
([tv smartInsertDeleteEnabled]?0x200:0) |
|
([tv smartInsertDeleteEnabled]?0x200:0) |
|
||||||
([tv allowsUndo]?0x400:0) |
|
([tv allowsUndo]?0x400:0) |
|
||||||
|
@ -738,6 +747,10 @@ If a text view is added to an empty text network, it keeps its attributes.
|
||||||
name: NSViewFrameDidChangeNotification
|
name: NSViewFrameDidChangeNotification
|
||||||
object: self];
|
object: self];
|
||||||
|
|
||||||
|
[notificationCenter addObserver: self
|
||||||
|
selector: @selector(_textDidChange:)
|
||||||
|
name: NSTextDidChangeNotification
|
||||||
|
object: self];
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -903,6 +916,7 @@ that makes decoding and encoding compatible with the old code.
|
||||||
_tf.is_field_editor = ((0x10 & flags) > 0);
|
_tf.is_field_editor = ((0x10 & flags) > 0);
|
||||||
_tf.uses_font_panel = ((0x20 & flags) > 0);
|
_tf.uses_font_panel = ((0x20 & flags) > 0);
|
||||||
_tf.is_ruler_visible = ((0x40 & flags) > 0);
|
_tf.is_ruler_visible = ((0x40 & flags) > 0);
|
||||||
|
_tf.continuous_spell_checking = ((0x80 & flags) > 0);
|
||||||
_tf.uses_ruler = ((0x100 & flags) > 0);
|
_tf.uses_ruler = ((0x100 & flags) > 0);
|
||||||
_tf.smart_insert_delete = ((0x200 & flags) > 0);
|
_tf.smart_insert_delete = ((0x200 & flags) > 0);
|
||||||
_tf.allows_undo = ((0x400 & flags) > 0);
|
_tf.allows_undo = ((0x400 & flags) > 0);
|
||||||
|
@ -1019,7 +1033,10 @@ that makes decoding and encoding compatible with the old code.
|
||||||
selector: @selector(_updateState:)
|
selector: @selector(_updateState:)
|
||||||
name: NSViewFrameDidChangeNotification
|
name: NSViewFrameDidChangeNotification
|
||||||
object: self];
|
object: self];
|
||||||
|
[notificationCenter addObserver: self
|
||||||
|
selector: @selector(_textDidChange:)
|
||||||
|
name: NSTextDidChangeNotification
|
||||||
|
object: self];
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1051,6 +1068,11 @@ that makes decoding and encoding compatible with the old code.
|
||||||
[notificationCenter removeObserver: self
|
[notificationCenter removeObserver: self
|
||||||
name: NSViewFrameDidChangeNotification
|
name: NSViewFrameDidChangeNotification
|
||||||
object: self];
|
object: self];
|
||||||
|
[notificationCenter removeObserver: self
|
||||||
|
name: NSTextDidChangeNotification
|
||||||
|
object: self];
|
||||||
|
[_textCheckingTimer invalidate];
|
||||||
|
|
||||||
[[NSRunLoop currentRunLoop] cancelPerformSelector: @selector(_updateState:)
|
[[NSRunLoop currentRunLoop] cancelPerformSelector: @selector(_updateState:)
|
||||||
target: self
|
target: self
|
||||||
argument: nil];
|
argument: nil];
|
||||||
|
@ -1471,18 +1493,35 @@ to make sure syncing is handled properly in all cases.
|
||||||
|
|
||||||
/* Continuous spell checking */
|
/* Continuous spell checking */
|
||||||
|
|
||||||
/* TODO */
|
|
||||||
- (BOOL) isContinuousSpellCheckingEnabled
|
- (BOOL) isContinuousSpellCheckingEnabled
|
||||||
{
|
{
|
||||||
NSLog(@"Method %s is not implemented for class %s",
|
return _tf.continuous_spell_checking;
|
||||||
__PRETTY_FUNCTION__, "NSTextView");
|
|
||||||
return NO;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void) setContinuousSpellCheckingEnabled: (BOOL)flag
|
- (void) setContinuousSpellCheckingEnabled: (BOOL)flag
|
||||||
{
|
{
|
||||||
NSLog(@"Method %s is not implemented for class %s",
|
NSTEXTVIEW_SYNC;
|
||||||
__PRETTY_FUNCTION__, "NSTextView");
|
|
||||||
|
if (_tf.continuous_spell_checking && !flag)
|
||||||
|
{
|
||||||
|
_tf.continuous_spell_checking = 0;
|
||||||
|
|
||||||
|
const NSRange allRange = NSMakeRange(0, [[self string] length]);
|
||||||
|
[_layoutManager removeTemporaryAttribute: @"NSTextChecked"
|
||||||
|
forCharacterRange: allRange];
|
||||||
|
[_layoutManager removeTemporaryAttribute: NSSpellingStateAttributeName
|
||||||
|
forCharacterRange: allRange];
|
||||||
|
_lastCheckedRect = NSZeroRect;
|
||||||
|
|
||||||
|
[_textCheckingTimer invalidate];
|
||||||
|
_textCheckingTimer = nil;
|
||||||
|
}
|
||||||
|
else if (!_tf.continuous_spell_checking && flag)
|
||||||
|
{
|
||||||
|
_tf.continuous_spell_checking = 1;
|
||||||
|
|
||||||
|
[self _scheduleTextCheckingInVisibleRectIfNeeded];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -3813,6 +3852,8 @@ Figure out how the additional layout stuff is supposed to work.
|
||||||
drawnRange = NSMakeRange(0, 0);
|
drawnRange = NSMakeRange(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[self _scheduleTextCheckingInVisibleRectIfNeeded];
|
||||||
|
|
||||||
/* FIXME: We should only draw inside of rect. This code is necessary
|
/* FIXME: We should only draw inside of rect. This code is necessary
|
||||||
* to remove markings of old glyphs. These would not be removed
|
* to remove markings of old glyphs. These would not be removed
|
||||||
* by the following call to the layout manager because that only
|
* by the following call to the layout manager because that only
|
||||||
|
@ -5740,6 +5781,195 @@ or add guards
|
||||||
type: NSStringPboardType];
|
type: NSStringPboardType];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Text checking
|
||||||
|
*/
|
||||||
|
|
||||||
|
- (void) _checkTextInRange: (NSRange)aRange
|
||||||
|
{
|
||||||
|
NSRange longestRange;
|
||||||
|
id value = [_layoutManager temporaryAttribute: @"NSTextChecked"
|
||||||
|
atCharacterIndex: aRange.location
|
||||||
|
longestEffectiveRange: &longestRange
|
||||||
|
inRange: aRange];
|
||||||
|
longestRange = NSIntersectionRange(longestRange, aRange);
|
||||||
|
|
||||||
|
if ([value boolValue] && NSEqualRanges(longestRange, aRange))
|
||||||
|
{
|
||||||
|
//NSLog(@"No need to check in range %@", NSStringFromRange(aRange));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check all of aRange
|
||||||
|
|
||||||
|
[_layoutManager removeTemporaryAttribute: NSSpellingStateAttributeName
|
||||||
|
forCharacterRange: aRange];
|
||||||
|
|
||||||
|
{
|
||||||
|
NSSpellChecker *sp = [NSSpellChecker sharedSpellChecker];
|
||||||
|
NSInteger start = 0;
|
||||||
|
int count;
|
||||||
|
// FIXME: doing the spellcheck on this substring could create false-positives
|
||||||
|
// if a word is split in half.. so the range should be expanded to the
|
||||||
|
// nearest word boundary
|
||||||
|
NSString *substring = [[self string] substringWithRange: aRange];
|
||||||
|
|
||||||
|
do {
|
||||||
|
NSRange errorRange = [sp checkSpellingOfString: substring
|
||||||
|
startingAt: start
|
||||||
|
language: [sp language]
|
||||||
|
wrap: YES
|
||||||
|
inSpellDocumentWithTag: [self spellCheckerDocumentTag]
|
||||||
|
wordCount: &count];
|
||||||
|
|
||||||
|
if (errorRange.location < start)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorRange.length > 0)
|
||||||
|
{
|
||||||
|
start = NSMaxRange(errorRange);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
errorRange = NSMakeRange(aRange.location + errorRange.location, errorRange.length);
|
||||||
|
|
||||||
|
//NSLog(@"highlighting mistake: %@", [[self string] substringWithRange: errorRange]);
|
||||||
|
|
||||||
|
[_layoutManager addTemporaryAttribute: NSSpellingStateAttributeName
|
||||||
|
value: [NSNumber numberWithInteger: NSSpellingStateSpellingFlag]
|
||||||
|
forCharacterRange: errorRange];
|
||||||
|
|
||||||
|
} while (1);
|
||||||
|
|
||||||
|
[_layoutManager addTemporaryAttribute: @"NSTextChecked"
|
||||||
|
value: [NSNumber numberWithBool: YES]
|
||||||
|
forCharacterRange: aRange];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) _textCheckingTimerFired: (NSTimer *)t
|
||||||
|
{
|
||||||
|
_textCheckingTimer = nil;
|
||||||
|
|
||||||
|
if (nil == _layoutManager)
|
||||||
|
return;
|
||||||
|
|
||||||
|
{
|
||||||
|
const NSRect visibleRect = [self visibleRect];
|
||||||
|
|
||||||
|
NSRange visibleGlyphRange = [_layoutManager glyphRangeForBoundingRect: visibleRect
|
||||||
|
inTextContainer: _textContainer];
|
||||||
|
|
||||||
|
NSRange visibleRange = [_layoutManager characterRangeForGlyphRange: visibleGlyphRange
|
||||||
|
actualGlyphRange: NULL];
|
||||||
|
|
||||||
|
[self _checkTextInRange: visibleRange];
|
||||||
|
|
||||||
|
_lastCheckedRect = visibleRect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) _scheduleTextCheckingTimer
|
||||||
|
{
|
||||||
|
[_textCheckingTimer invalidate];
|
||||||
|
_textCheckingTimer = [NSTimer scheduledTimerWithTimeInterval: 0.5
|
||||||
|
target: self
|
||||||
|
selector: @selector(_textCheckingTimerFired:)
|
||||||
|
userInfo: [NSValue valueWithRect: [self visibleRect]]
|
||||||
|
repeats: NO];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) _scheduleTextCheckingInVisibleRectIfNeeded
|
||||||
|
{
|
||||||
|
if (_tf.continuous_spell_checking)
|
||||||
|
{
|
||||||
|
const NSRect visibleRect = [self visibleRect];
|
||||||
|
if (!NSEqualRects(visibleRect, _lastCheckedRect))
|
||||||
|
{
|
||||||
|
if (NSEqualRects(visibleRect, [[_textCheckingTimer userInfo] rectValue]))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
[self _scheduleTextCheckingTimer];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If selected has a nonzero length, return it unmodified.
|
||||||
|
* If selected is touching a word, expand it to cover the entire word
|
||||||
|
* Otherwise return selected unmodified
|
||||||
|
*/
|
||||||
|
- (NSRange) _rangeToInvalidateSpellingForSelectionRange: (NSRange)selected
|
||||||
|
{
|
||||||
|
if (selected.length > 0)
|
||||||
|
{
|
||||||
|
return selected;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NSCharacterSet *boundary = [[NSCharacterSet letterCharacterSet] invertedSet];
|
||||||
|
|
||||||
|
if (selected.location == [[self string] length] && selected.location > 0)
|
||||||
|
{
|
||||||
|
selected.location--;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSRange prevNonCharacter = [[self string] rangeOfCharacterFromSet: boundary options: NSBackwardsSearch range: NSMakeRange(0, selected.location)];
|
||||||
|
NSRange nextNonCharacter = [[self string] rangeOfCharacterFromSet: boundary options: 0 range: NSMakeRange(NSMaxRange(selected), [[self string] length] - NSMaxRange(selected))];
|
||||||
|
NSRange range;
|
||||||
|
|
||||||
|
if (prevNonCharacter.length == 0)
|
||||||
|
{
|
||||||
|
range.location = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
range.location = prevNonCharacter.location + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextNonCharacter.length == 0)
|
||||||
|
{
|
||||||
|
range.length = [[self string] length] - range.location;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
range.length = nextNonCharacter.location - range.location;
|
||||||
|
}
|
||||||
|
|
||||||
|
return range;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) _textDidChange: (NSNotification*)notif
|
||||||
|
{
|
||||||
|
if (_tf.continuous_spell_checking)
|
||||||
|
{
|
||||||
|
// FIXME: This uses the caret position to guess what change caused
|
||||||
|
// the NSTextDidChangeNotification and mark that range of the text as requiring re-checking.
|
||||||
|
//
|
||||||
|
// It would be better to use accurate information to decide what range
|
||||||
|
// need its spelling state invalidated.
|
||||||
|
|
||||||
|
NSRange range = [self _rangeToInvalidateSpellingForSelectionRange: [self selectedRange]];
|
||||||
|
if (range.length > 0)
|
||||||
|
{
|
||||||
|
[_layoutManager removeTemporaryAttribute: @"NSTextChecked"
|
||||||
|
forCharacterRange: range];
|
||||||
|
[_layoutManager removeTemporaryAttribute: NSSpellingStateAttributeName
|
||||||
|
forCharacterRange: range];
|
||||||
|
}
|
||||||
|
|
||||||
|
[self _scheduleTextCheckingTimer];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation NSTextViewUndoObject
|
@implementation NSTextViewUndoObject
|
||||||
|
|
|
@ -547,6 +547,11 @@ NSString *NSModificationTimeDocumentAttribute = @"ModificationTime";
|
||||||
|
|
||||||
const unsigned NSUnderlineByWordMask = 0x01;
|
const unsigned NSUnderlineByWordMask = 0x01;
|
||||||
|
|
||||||
|
NSString *NSSpellingStateAttributeName = @"NSSpellingState";
|
||||||
|
const unsigned NSSpellingStateSpellingFlag = 1;
|
||||||
|
const unsigned NSSpellingStateGrammarFlag = 2;
|
||||||
|
|
||||||
|
|
||||||
NSString *NSPlainTextDocumentType = @"PlainText";
|
NSString *NSPlainTextDocumentType = @"PlainText";
|
||||||
NSString *NSRTFTextDocumentType = @"RTF";
|
NSString *NSRTFTextDocumentType = @"RTF";
|
||||||
NSString *NSRTFDTextDocumentType = @"RTFD";
|
NSString *NSRTFDTextDocumentType = @"RTFD";
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue