Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
255 changes: 131 additions & 124 deletions Gas Mask.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

52 changes: 30 additions & 22 deletions Source/HostsDownloader.m
Original file line number Diff line number Diff line change
Expand Up @@ -133,40 +133,48 @@ @implementation HostsDownloader (Private)

- (void)notifyDelegateHostsUpToDate
{
SEL selector = @selector(hostsUpToDate:);
if (delegate && [delegate respondsToSelector:selector]) {
SuppressPerformSelectorLeakWarning(
[delegate performSelector:selector withObject:self]);
}
dispatch_async(dispatch_get_main_queue(), ^{
SEL selector = @selector(hostsUpToDate:);
if (delegate && [delegate respondsToSelector:selector]) {
SuppressPerformSelectorLeakWarning(
[delegate performSelector:selector withObject:self]);
}
});
}

- (void)notifyDelegateDownloadingStarted
{
SEL selector = @selector(hostsDownloadingStarted:);
if (delegate && [delegate respondsToSelector:selector]) {
SuppressPerformSelectorLeakWarning(
[delegate performSelector:selector withObject:self]);
}
dispatch_async(dispatch_get_main_queue(), ^{
SEL selector = @selector(hostsDownloadingStarted:);
if (delegate && [delegate respondsToSelector:selector]) {
SuppressPerformSelectorLeakWarning(
[delegate performSelector:selector withObject:self]);
}
});
}

- (void)notifyDelegateDownloaded
{
logDebug(@"Downloading complete: %@", url);

SEL selector = @selector(hostsDownloaded:);
if (delegate && [delegate respondsToSelector:selector]) {
SuppressPerformSelectorLeakWarning(
[delegate performSelector:selector withObject:self]);
}
dispatch_async(dispatch_get_main_queue(), ^{
logDebug(@"Downloading complete: %@", url);

SEL selector = @selector(hostsDownloaded:);
if (delegate && [delegate respondsToSelector:selector]) {
SuppressPerformSelectorLeakWarning(
[delegate performSelector:selector withObject:self]);
}
});
}

- (void)notifyDelegateDownloadFailed
{
SEL selector = @selector(hostsDownloadFailed:);
if (delegate && [delegate respondsToSelector:selector]) {
SuppressPerformSelectorLeakWarning(
[delegate performSelector:selector withObject:self]);
}
dispatch_async(dispatch_get_main_queue(), ^{
SEL selector = @selector(hostsDownloadFailed:);
if (delegate && [delegate respondsToSelector:selector]) {
SuppressPerformSelectorLeakWarning(
[delegate performSelector:selector withObject:self]);
}
});
}

#pragma mark -
Expand Down
10 changes: 9 additions & 1 deletion Source/HostsTextView.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
***************************************************************************/


NS_ASSUME_NONNULL_BEGIN

@interface HostsTextView : NSTextView<NSTextStorageDelegate> {
@private
BOOL syntaxHighlighting;
Expand All @@ -27,11 +29,17 @@
NSColor *textColor;
NSColor *commentColor;
NSCharacterSet *nameCharacterSet;
NSUInteger _highlightGeneration;
BOOL _replacingContent;
}

- (void)setSyntaxHighlighting:(BOOL)value;
- (BOOL)syntaxHighlighting;
- (void)cancelPendingHighlighting;
- (void)replaceContentWith:(NSString *)newContent;

+ (instancetype)createForProgrammaticUse;
+ (nullable instancetype)createForProgrammaticUse;

@end

NS_ASSUME_NONNULL_END
151 changes: 122 additions & 29 deletions Source/HostsTextView.m
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,12 @@
#import "ExtendedNSString.h"
#import "IP.h"

#define kAsyncHighlightThreshold 50000
#define kHighlightChunkSize 100000

@interface HostsTextView (HighLight)
-(void)colorText: (NSTextStorage*)textStorage;
-(void)colorTextInRange:(NSRange)range;
-(void)highlightAsyncFrom:(NSUInteger)start generation:(NSUInteger)generation;
-(void)removeColors;
-(void)removeMarks: (NSTextStorage*)textStorage range: (NSRange)range;
-(void)markComment: (NSTextStorage*)textStorage range:(NSRange)range;
Expand Down Expand Up @@ -118,9 +122,19 @@ -(void)setSyntaxHighlighting:(BOOL)value
{
syntaxHighlighting = value;
if (syntaxHighlighting) {
[self colorText:[self textStorage]];
NSUInteger length = [[[self textStorage] string] length];
if (length > kAsyncHighlightThreshold) {
_highlightGeneration++;
[self highlightAsyncFrom:0 generation:_highlightGeneration];
} else if (length > 0) {
NSTextStorage *ts = [self textStorage];
[ts beginEditing];
[self colorTextInRange:NSMakeRange(0, length)];
[ts endEditing];
}
}
else {
_highlightGeneration++;
[self removeColors];
}
}
Expand All @@ -129,45 +143,74 @@ -(BOOL)syntaxHighlighting
return syntaxHighlighting;
}

-(void)cancelPendingHighlighting
{
_highlightGeneration++;
}

-(void)replaceContentWith:(NSString *)newContent
{
// Cancel any pending async highlighting from a previous call
_highlightGeneration++;
_replacingContent = YES;
[self setString:newContent];
_replacingContent = NO;

if (syntaxHighlighting) {
NSUInteger length = [[self string] length];
if (length > kAsyncHighlightThreshold) {
_highlightGeneration++;
NSUInteger generation = _highlightGeneration;
dispatch_async(dispatch_get_main_queue(), ^{
[self highlightAsyncFrom:0 generation:generation];
});
} else if (length > 0) {
NSTextStorage *ts = [self textStorage];
[ts beginEditing];
[self colorTextInRange:NSMakeRange(0, length)];
[ts endEditing];
}
}
}

@end

@implementation HostsTextView (HighLight)

-(void)colorText: (NSTextStorage*)textStorage
-(void)colorTextInRange:(NSRange)range
{

NSRange range = [self changedLinesRange: textStorage];
NSTextStorage *textStorage = [self textStorage];
NSString *contents = [[textStorage string] substringWithRange:range];

if ([contents length] == 0) {
return;
}

[self removeMarks: textStorage range: range];
NSArray *array = [contents componentsSeparatedByString: @"\n"]; // 37ms

NSArray *array = [contents componentsSeparatedByString: @"\n"];

int pos = 0;
IP *ip = [[IP alloc] initWithString:contents];

for (NSString *line in array) {
NSRange ipRange = NSMakeRange(NSNotFound, NSNotFound);
NSRange namesRange = NSMakeRange(NSNotFound, NSNotFound);

int i;
for (i=0; i<[line length]; i++) {
unichar character = [line characterAtIndex:i];
// Start of comment
if (character == '#') {

// End of names
if (namesRange.location != NSNotFound) {
namesRange.length = pos-namesRange.location;
}

NSRange range2 = NSMakeRange(range.location+pos, [line length]-i);
[self markComment:textStorage range:range2];

pos += [line length]-i;
break;
}
Expand All @@ -185,16 +228,16 @@ -(void)colorText: (NSTextStorage*)textStorage
}
pos++;
}

if (ipRange.location != NSNotFound && ipRange.length == NSNotFound) {
ipRange.length = pos-ipRange.location;
}

if (ipRange.location != NSNotFound && ipRange.length != NSNotFound) {
[ip setRange:ipRange];

ipRange.location += range.location;

if ([ip isValid]) {
if ([ip isVersion4]) {
[self markIPv4:textStorage range:ipRange];
Expand All @@ -206,27 +249,27 @@ -(void)colorText: (NSTextStorage*)textStorage
else {
NSRange badIPRange = [ip invalidRange];
badIPRange.location += ipRange.location;

[self markInvalid: textStorage range:badIPRange];
}
}

if (namesRange.length == NSNotFound) {
namesRange.length = pos-namesRange.location;

if ([line hasSuffix:@"\r"]) {
namesRange.length--;
}
}
// Color names
if (namesRange.location != NSNotFound) {

NSRange nameRange = NSMakeRange(namesRange.location, NSNotFound);

int end = NSMaxRange(namesRange);
for (int i=namesRange.location; i<end; i++) {
unichar character = [contents characterAtIndex:i];

if (nameRange.length == NSNotFound) {
if (character == ' ' || character == '\t') {
nameRange.length = i-nameRange.location;
Expand All @@ -235,7 +278,7 @@ -(void)colorText: (NSTextStorage*)textStorage
else if (i == end-1) {
nameRange.length = i-nameRange.location+1;
}

if (nameRange.length != NSNotFound && nameRange.length > 0) {
if (![self validName:contents range:nameRange]) {
[self markInvalid:textStorage range:NSMakeRange(range.location+nameRange.location, nameRange.length)];
Expand All @@ -248,12 +291,45 @@ -(void)colorText: (NSTextStorage*)textStorage
}
}
}

// Move over newline
pos++;
}
}

-(void)highlightAsyncFrom:(NSUInteger)start generation:(NSUInteger)generation
{
if (generation != _highlightGeneration) return;
if (!syntaxHighlighting) return;

NSTextStorage *textStorage = [self textStorage];
NSString *string = [textStorage string];
NSUInteger totalLength = [string length];

if (start >= totalLength) return;

NSUInteger end = MIN(start + kHighlightChunkSize, totalLength);

// Extend to line boundary
if (end < totalLength) {
NSRange lineRange = [string lineRangeForRange:NSMakeRange(end, 0)];
end = NSMaxRange(lineRange);
}

NSRange range = NSMakeRange(start, end - start);

[textStorage beginEditing];
[self colorTextInRange:range];
[textStorage endEditing];

if (end < totalLength && generation == _highlightGeneration) {
NSUInteger nextStart = end;
dispatch_async(dispatch_get_main_queue(), ^{
[self highlightAsyncFrom:nextStart generation:generation];
});
}
}

-(void)removeColors
{
NSTextStorage *textStorage = [self textStorage];
Expand Down Expand Up @@ -340,9 +416,26 @@ -(BOOL)validName:(NSString*)contents range:(NSRange)nameRange

- (void)textStorageDidProcessEditing:(NSNotification *)notification
{
if (syntaxHighlighting && [[self textStorage] editedMask] != NSTextStorageEditedAttributes) {
[self colorText:[notification object]];
if (_replacingContent) return;
if (!syntaxHighlighting) return;
if ([[self textStorage] editedMask] == NSTextStorageEditedAttributes) return;

// Cancel any pending async highlighting (text has changed)
_highlightGeneration++;

NSTextStorage *textStorage = [notification object];
NSRange range = [self changedLinesRange:textStorage];

// For large edits (bulk string replacement), use async chunked highlighting
if (range.length > kAsyncHighlightThreshold) {
NSUInteger generation = _highlightGeneration;
dispatch_async(dispatch_get_main_queue(), ^{
[self highlightAsyncFrom:0 generation:generation];
});
return;
}

[self colorTextInRange:range];
}

@end
Expand Down
Loading