diff --git a/Chromatism/Chromatism.xcodeproj/project.pbxproj b/Chromatism/Chromatism.xcodeproj/project.pbxproj index f65a503..22d803c 100644 --- a/Chromatism/Chromatism.xcodeproj/project.pbxproj +++ b/Chromatism/Chromatism.xcodeproj/project.pbxproj @@ -41,6 +41,8 @@ 8FFEB27E183AD6740032C9FB /* JLObjectiveCTokenizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FFEB27C183AD6740032C9FB /* JLObjectiveCTokenizer.m */; }; 8FFEB283183AD8B70032C9FB /* JLTokenizer+Additions.h in Headers */ = {isa = PBXBuildFile; fileRef = 8FFEB281183AD8B70032C9FB /* JLTokenizer+Additions.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8FFEB284183AD8B70032C9FB /* JLTokenizer+Additions.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FFEB282183AD8B70032C9FB /* JLTokenizer+Additions.m */; }; + 9E5EC9B719A735F900E12723 /* CYRLayoutManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 9E5EC9B519A735F900E12723 /* CYRLayoutManager.h */; }; + 9E5EC9B819A735F900E12723 /* CYRLayoutManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 9E5EC9B619A735F900E12723 /* CYRLayoutManager.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -91,6 +93,8 @@ 8FFEB27C183AD6740032C9FB /* JLObjectiveCTokenizer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JLObjectiveCTokenizer.m; sourceTree = ""; }; 8FFEB281183AD8B70032C9FB /* JLTokenizer+Additions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "JLTokenizer+Additions.h"; sourceTree = ""; }; 8FFEB282183AD8B70032C9FB /* JLTokenizer+Additions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "JLTokenizer+Additions.m"; sourceTree = ""; }; + 9E5EC9B519A735F900E12723 /* CYRLayoutManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CYRLayoutManager.h; sourceTree = ""; }; + 9E5EC9B619A735F900E12723 /* CYRLayoutManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CYRLayoutManager.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -159,6 +163,7 @@ 8F3AB9501781C1250023348A /* JLScope.m */, 8F3AB9571781C1250023348A /* JLTokenPattern.h */, 8F3AB9581781C1250023348A /* JLTokenPattern.m */, + 9E5EC9B919A7360700E12723 /* CYRLayoutManager */, 8F3AB91E1781C0BA0023348A /* Supporting Files */, ); path = Chromatism; @@ -223,6 +228,15 @@ name = General; sourceTree = ""; }; + 9E5EC9B919A7360700E12723 /* CYRLayoutManager */ = { + isa = PBXGroup; + children = ( + 9E5EC9B519A735F900E12723 /* CYRLayoutManager.h */, + 9E5EC9B619A735F900E12723 /* CYRLayoutManager.m */, + ); + name = CYRLayoutManager; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -235,6 +249,7 @@ 8FA0CB0F1795F41B00FF8EB1 /* JLTextView.h in Headers */, 8F54EF0E1781C776008C1609 /* Chromatism.h in Headers */, 8F54EF101781C788008C1609 /* Helpers.h in Headers */, + 9E5EC9B719A735F900E12723 /* CYRLayoutManager.h in Headers */, 8FE2796517C3842900DD933A /* UIColor+Chromatism.h in Headers */, 8F54EF111781C788008C1609 /* JLScope.h in Headers */, 8FFEB27D183AD6740032C9FB /* JLObjectiveCTokenizer.h in Headers */, @@ -328,6 +343,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 9E5EC9B819A735F900E12723 /* CYRLayoutManager.m in Sources */, 8F54EF241781CD87008C1609 /* JLTokenPattern.m in Sources */, 8FFEB27E183AD6740032C9FB /* JLObjectiveCTokenizer.m in Sources */, 8F9F6BC91822F09D00DE15A8 /* JLDiffTokenizer.m in Sources */, diff --git a/Chromatism/Chromatism/CYRLayoutManager.h b/Chromatism/Chromatism/CYRLayoutManager.h new file mode 100755 index 0000000..48d7fd9 --- /dev/null +++ b/Chromatism/Chromatism/CYRLayoutManager.h @@ -0,0 +1,49 @@ +// +// CYRLayoutManager.h +// +// Version 0.4.0 +// +// Created by Illya Busigin on 01/05/2014. +// Copyright (c) 2014 Cyrillian, Inc. +// +// Distributed under MIT license. +// Get the latest version from here: +// +// https://github.com/illyabusigin/CYRTextView +// Original implementation taken from: https://github.com/alldritt/TextKit_LineNumbers +// +// The MIT License (MIT) +// +// Copyright (c) 2014 Cyrillian, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#import + +@interface CYRLayoutManager : NSLayoutManager + +@property (nonatomic, strong) UIFont *lineNumberFont; +@property (nonatomic, strong) UIColor *lineNumberColor; +@property (nonatomic, strong) UIColor *selectedLineNumberColor; + +@property (nonatomic, readonly) CGFloat gutterWidth; +@property (nonatomic, assign) NSRange selectedRange; + +- (CGRect)paragraphRectForRange:(NSRange)range; + +@end diff --git a/Chromatism/Chromatism/CYRLayoutManager.m b/Chromatism/Chromatism/CYRLayoutManager.m new file mode 100755 index 0000000..dd82bc0 --- /dev/null +++ b/Chromatism/Chromatism/CYRLayoutManager.m @@ -0,0 +1,227 @@ +// +// CYRLayoutManager.h +// +// Version 0.4.0 +// +// Created by Illya Busigin on 01/05/2014. +// Copyright (c) 2014 Cyrillian, Inc. +// +// Distributed under MIT license. +// Get the latest version from here: +// +// https://github.com/illyabusigin/CYRTextView +// Original implementation taken from: https://github.com/alldritt/TextKit_LineNumbers +// +// The MIT License (MIT) +// +// Copyright (c) 2014 Cyrillian, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#import "CYRLayoutManager.h" + +static CGFloat kMinimumGutterWidth = 30.f; + +@interface CYRLayoutManager () + +@property (nonatomic, assign) CGFloat gutterWidth; +@property (nonatomic, assign) UIEdgeInsets lineAreaInset; + +@property (nonatomic) NSUInteger lastParaLocation; +@property (nonatomic) NSUInteger lastParaNumber; + +@end + +@implementation CYRLayoutManager + +#pragma mark - Initialization & Setup + +- (instancetype)init +{ + self = [super init]; + + if (self) + { + [self _commonSetup]; + } + + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder +{ + self = [super initWithCoder:aDecoder]; + + if (self) + { + [self _commonSetup]; + } + + return self; +} + +- (void)_commonSetup +{ + self.gutterWidth = kMinimumGutterWidth; + self.selectedRange = NSMakeRange(0, 0); + + self.lineAreaInset = UIEdgeInsetsMake(0, 10, 0, 4); + self.lineNumberColor = [UIColor grayColor]; + self.lineNumberFont = [UIFont systemFontOfSize:10.0f]; + self.selectedLineNumberColor = [UIColor colorWithWhite:0.9 alpha:1]; +} + + +#pragma mark - Convenience + +- (CGRect)paragraphRectForRange:(NSRange)range +{ + range = [self.textStorage.string paragraphRangeForRange:range]; + range = [self glyphRangeForCharacterRange:range actualCharacterRange:NULL]; + + CGRect startRect = [self lineFragmentRectForGlyphAtIndex:range.location effectiveRange:NULL]; + CGRect endRect = [self lineFragmentRectForGlyphAtIndex:range.location + range.length - 1 effectiveRange:NULL]; + + CGRect paragraphRectForRange = CGRectUnion(startRect, endRect); + paragraphRectForRange = CGRectOffset(paragraphRectForRange, _gutterWidth, 8); + + return paragraphRectForRange; +} + +- (NSUInteger) _paraNumberForRange:(NSRange) charRange +{ + // NSString does not provide a means of efficiently determining the paragraph number of a range of text. This code + // attempts to optimize what would normally be a series linear searches by keeping track of the last paragraph number + // found and uses that as the starting point for next paragraph number search. This works (mostly) because we + // are generally asked for continguous increasing sequences of paragraph numbers. Also, this code is called in the + // course of drawing a pagefull of text, and so even when moving back, the number of paragraphs to search for is + // relativly low, even in really long bodies of text. + // + // This all falls down when the user edits the text, and can potentially invalidate the cached paragraph number which + // causes a (potentially lengthy) search from the beginning of the string. + + if (charRange.location == self.lastParaLocation) + return self.lastParaNumber; + else if (charRange.location < self.lastParaLocation) + { + // We need to look backwards from the last known paragraph for the new paragraph range. This generally happens + // when the text in the UITextView scrolls downward, revaling paragraphs before/above the ones previously drawn. + + NSString* s = self.textStorage.string; + __block NSUInteger paraNumber = self.lastParaNumber; + + [s enumerateSubstringsInRange:NSMakeRange(charRange.location, self.lastParaLocation - charRange.location) + options:NSStringEnumerationByParagraphs | + NSStringEnumerationSubstringNotRequired | + NSStringEnumerationReverse + usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop){ + if (enclosingRange.location <= charRange.location) { + *stop = YES; + } + --paraNumber; + }]; + + self.lastParaLocation = charRange.location; + self.lastParaNumber = paraNumber; + + return paraNumber; + } + else + { + // We need to look forward from the last known paragraph for the new paragraph range. This generally happens + // when the text in the UITextView scrolls upwards, revealing paragraphs that follow the ones previously drawn. + + NSString* s = self.textStorage.string; + __block NSUInteger paraNumber = self.lastParaNumber; + + [s enumerateSubstringsInRange:NSMakeRange(self.lastParaLocation, charRange.location - self.lastParaLocation) + options:NSStringEnumerationByParagraphs | NSStringEnumerationSubstringNotRequired + usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop){ + if (enclosingRange.location >= charRange.location) { + *stop = YES; + } + ++paraNumber; + }]; + + self.lastParaLocation = charRange.location; + self.lastParaNumber = paraNumber; + + return paraNumber; + } +} + + +#pragma mark - Layouting + +- (void)processEditingForTextStorage:(NSTextStorage *)textStorage edited:(NSTextStorageEditActions)editMask range:(NSRange)newCharRange changeInLength:(NSInteger)delta invalidatedRange:(NSRange)invalidatedCharRange +{ + [super processEditingForTextStorage:textStorage edited:editMask range:newCharRange changeInLength:delta invalidatedRange:invalidatedCharRange]; + + if (invalidatedCharRange.location < self.lastParaLocation) + { + // When the backing store is edited ahead the cached paragraph location, invalidate the cache and force a complete + // recalculation. We cannot be much smarter than this because we don't know how many paragraphs have been deleted + // since the text has already been removed from the backing store. + self.lastParaLocation = 0; + self.lastParaNumber = 0; + } +} + + +#pragma mark - Drawing + +- (void) drawBackgroundForGlyphRange:(NSRange)glyphsToShow atPoint:(CGPoint)origin +{ + [super drawBackgroundForGlyphRange:glyphsToShow atPoint:origin]; + + // Draw line numbers. Note that the background for line number gutter is drawn by the LineNumberTextView class. + + NSDictionary* atts = @{NSFontAttributeName : _lineNumberFont , + NSForegroundColorAttributeName : _lineNumberColor}; + + [self enumerateLineFragmentsForGlyphRange:glyphsToShow + usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer *textContainer, NSRange glyphRange, BOOL *stop) { + NSRange charRange = [self characterRangeForGlyphRange:glyphRange actualGlyphRange:nil]; + NSRange paraRange = [self.textStorage.string paragraphRangeForRange:charRange]; + + BOOL showCursorRect = NSLocationInRange(_selectedRange.location, paraRange); + + if (showCursorRect) + { + CGContextRef context = UIGraphicsGetCurrentContext(); + CGRect cursorRect = CGRectMake(0, usedRect.origin.y + 8, _gutterWidth, usedRect.size.height); + + CGContextSetFillColorWithColor(context, _selectedLineNumberColor.CGColor); + CGContextFillRect(context, cursorRect); + } + + // Only draw line numbers for the paragraph's first line fragment. Subsequent fragments are wrapped portions of the paragraph and don't get the line number. + if (charRange.location == paraRange.location) { + CGRect gutterRect = CGRectOffset(CGRectMake(0, rect.origin.y, _gutterWidth, rect.size.height), origin.x, origin.y); + NSUInteger paraNumber = [self _paraNumberForRange:charRange]; + NSString* ln = [NSString stringWithFormat:@"%ld", (unsigned long) paraNumber + 1]; + CGSize size = [ln sizeWithAttributes:atts]; + + [ln drawInRect:CGRectOffset(gutterRect, CGRectGetWidth(gutterRect) - _lineAreaInset.right - size.width - _gutterWidth, (CGRectGetHeight(gutterRect) - size.height) / 2.0) + withAttributes:atts]; + } + + }]; +} + +@end diff --git a/Chromatism/Chromatism/Chromatism+Internal.h b/Chromatism/Chromatism/Chromatism+Internal.h index f325da9..3698bd8 100644 --- a/Chromatism/Chromatism/Chromatism+Internal.h +++ b/Chromatism/Chromatism/Chromatism+Internal.h @@ -53,6 +53,8 @@ FOUNDATION_EXPORT NSString *const JLTokenTypeProjectClassNames; FOUNDATION_EXPORT NSString *const JLTokenTypeProjectMethodNames; FOUNDATION_EXPORT NSString *const JLTokenTypeDiffAddition; FOUNDATION_EXPORT NSString *const JLTokenTypeDiffDeletion; +FOUNDATION_EXPORT NSString *const JLGutterBackgroundColor; +FOUNDATION_EXPORT NSString *const JLGutterLineColor; /// Used as attribute in NSAttributedStrings. You may subclass NSLayoutManager to use this attribute. FOUNDATION_EXPORT NSString *const JLDiffColorAttributeName; diff --git a/Chromatism/Chromatism/Chromatism.m b/Chromatism/Chromatism/Chromatism.m index 3bc78ec..50d50f1 100644 --- a/Chromatism/Chromatism/Chromatism.m +++ b/Chromatism/Chromatism/Chromatism.m @@ -44,6 +44,8 @@ NSString *const JLTokenTypeProjectMethodNames = @"project_method_names"; NSString *const JLTokenTypeProjectClassNames = @"project_class_names"; +NSString *const JLGutterBackgroundColor = @"gutterBackgroundColor"; +NSString *const JLGutterLineColor = @"gutterLineColor"; NSString *const JLTokenTypeDiffAddition = @"diff_addition"; NSString *const JLTokenTypeDiffDeletion = @"diff_deletion"; @@ -76,7 +78,10 @@ + (NSDictionary *)colorsForTheme:(JLTokenizerTheme)theme JLTokenTypeProjectMethodNames : [UIColor colorWithRed:38.0/255 green:71.0/255 blue:75.0/255 alpha:1], JLTokenTypeDiffAddition : [UIColor greenColor], - JLTokenTypeDiffDeletion : [UIColor redColor] + JLTokenTypeDiffDeletion : [UIColor redColor], + + JLGutterBackgroundColor : [UIColor colorWithWhite:0.95 alpha:1], + JLGutterLineColor : [UIColor lightGrayColor] }; break; @@ -99,7 +104,10 @@ + (NSDictionary *)colorsForTheme:(JLTokenizerTheme)theme JLTokenTypeProjectClassNames : [UIColor colorWithRed:131.0/255 green:192.0/255 blue:87.0/255 alpha:1], JLTokenTypeDiffAddition : [UIColor greenColor], - JLTokenTypeDiffDeletion : [UIColor redColor] + JLTokenTypeDiffDeletion : [UIColor redColor], + + JLGutterBackgroundColor : [UIColor colorWithRed:19.0/255.0 green:16.0/255.0 blue:26.0/255.0 alpha:1], + JLGutterLineColor : [UIColor lightGrayColor] }; break; diff --git a/Chromatism/Chromatism/JLTextView.h b/Chromatism/Chromatism/JLTextView.h index e11b3cb..2d413de 100644 --- a/Chromatism/Chromatism/JLTextView.h +++ b/Chromatism/Chromatism/JLTextView.h @@ -32,4 +32,8 @@ @property (nonatomic, strong) JLTokenizer *syntaxTokenizer; @property (nonatomic, assign) JLTokenizerTheme theme; + +@property (nonatomic, assign) BOOL drawLineNumbers; +@property (nonatomic, assign) BOOL drawLineCursor; + @end diff --git a/Chromatism/Chromatism/JLTextView.m b/Chromatism/Chromatism/JLTextView.m index fc7025f..d9d32e5 100644 --- a/Chromatism/Chromatism/JLTextView.m +++ b/Chromatism/Chromatism/JLTextView.m @@ -25,45 +25,81 @@ #import "JLTextView.h" #import "Chromatism.h" +#import "CYRLayoutManager.h" + +static void *JLTextViewContext = &JLTextViewContext; + +@interface JLTextView () +{ + CYRLayoutManager *_lineNumberLayoutManager; + + UIColor *_gutterBackgroundColor; + UIColor *_gutterLineColor; +} +@end + + @implementation JLTextView + @synthesize theme = _theme; +@synthesize drawLineNumbers = _drawLineNumbers; +@synthesize drawLineCursor = _drawLineCursor; + #pragma mark - Initialization & Setup - (id)init { - self = [super init]; - if (self) { - [self setup]; - } - return self; + return [self initWithFrame:CGRectMake(0, 0, 0, 0)]; } -- (id)initWithFrame:(CGRect)frame +- (id)initWithFrame:(CGRect)frame textContainer:(NSTextContainer *)textContainer { - self = [super initWithFrame:frame]; - if (self) { - [self setup]; - } - return self; + return [self initWithFrame:frame]; } -- (id)initWithFrame:(CGRect)frame textContainer:(NSTextContainer *)textContainer +- (id)initWithCoder:(NSCoder *)aDecoder { - self = [super initWithFrame:frame textContainer:textContainer]; - if (self) { - [self setup]; - } - return self; + return [self initWithFrame:CGRectMake(0, 0, 0, 0)]; } -- (id)initWithCoder:(NSCoder *)aDecoder +- (id)initWithFrame:(CGRect)frame { - self = [super initWithCoder:aDecoder]; - if (self) { + NSTextStorage *textStorage = [[NSTextStorage alloc] init]; + CYRLayoutManager *layoutManager = [[CYRLayoutManager alloc] init]; + + _lineNumberLayoutManager = layoutManager; + + NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)]; + + // Wrap text to the text view's frame + textContainer.widthTracksTextView = YES; + + [layoutManager addTextContainer:textContainer]; + + [textStorage removeLayoutManager:textStorage.layoutManagers.firstObject]; + [textStorage addLayoutManager:layoutManager]; + + if ((self = [super initWithFrame:frame textContainer:textContainer])) + { + // causes drawRect: to be called on frame resizing and device rotation + self.contentMode = UIViewContentModeRedraw; + + + _drawLineCursor = YES; + _drawLineNumbers = YES; + + // Inset the content to make room for line numbers + self.textContainerInset = UIEdgeInsetsMake(8, _lineNumberLayoutManager.gutterWidth, 8, 0); + + + [self addObserver:self forKeyPath:NSStringFromSelector(@selector(selectedTextRange)) options:NSKeyValueObservingOptionNew context:JLTextViewContext]; + [self addObserver:self forKeyPath:NSStringFromSelector(@selector(selectedRange)) options:NSKeyValueObservingOptionNew context:JLTextViewContext]; + [self setup]; } + return self; } @@ -80,7 +116,12 @@ - (void)setup self.autocorrectionType = UITextAutocorrectionTypeNo; self.autocapitalizationType = UITextAutocapitalizationTypeNone; self.layoutManager.allowsNonContiguousLayout = YES; - +} + +- (void)dealloc +{ + [self removeObserver:self forKeyPath:NSStringFromSelector(@selector(selectedTextRange))]; + [self removeObserver:self forKeyPath:NSStringFromSelector(@selector(selectedRange))]; } - (void)setSyntaxTokenizer:(JLTokenizer *)tokenizer @@ -104,6 +145,10 @@ -(void)setTheme:(JLTokenizerTheme)theme UIColor *backgroundColor = self.syntaxTokenizer.colors[JLTokenTypeBackground]; [self setBackgroundColor:backgroundColor ? backgroundColor : [UIColor whiteColor] ]; + //Set gutter colors + _gutterBackgroundColor = self.syntaxTokenizer.colors[JLGutterBackgroundColor]; + _gutterLineColor = self.syntaxTokenizer.colors[JLGutterLineColor]; + // Refresh Tokenization [self.syntaxTokenizer refreshTokenizationOfTextStorage:self.textStorage]; } @@ -179,4 +224,71 @@ - (void)downArrow:(UIKeyCommand *)keyCommand { } } } + + + +#pragma mark - Line Drawing + +// Implementation sourced from https://github.com/illyabusigin/CYRTextView +// Original implementation sourced from: https://github.com/alldritt/TextKit_LineNumbers +- (void)drawRect:(CGRect)rect +{ + if (_drawLineNumbers) + { + // Drag the line number gutter background. The line numbers them selves are drawn by LineNumberLayoutManager. + CGContextRef context = UIGraphicsGetCurrentContext(); + CGRect bounds = self.bounds; + + CGFloat height = MAX(CGRectGetHeight(bounds), self.contentSize.height) + 200; + + // Set the regular fill + CGContextSetFillColorWithColor(context, _gutterBackgroundColor.CGColor); + CGContextFillRect(context, CGRectMake(bounds.origin.x, bounds.origin.y, _lineNumberLayoutManager.gutterWidth, height)); + + // Draw line + CGContextSetFillColorWithColor(context, _gutterLineColor.CGColor); + CGContextFillRect(context, CGRectMake(_lineNumberLayoutManager.gutterWidth, bounds.origin.y, 0.5, height)); + + if (_drawLineCursor) + { + _lineNumberLayoutManager.selectedRange = self.selectedRange; + + NSRange glyphRange = [_lineNumberLayoutManager.textStorage.string paragraphRangeForRange:self.selectedRange]; + glyphRange = [_lineNumberLayoutManager glyphRangeForCharacterRange:glyphRange actualCharacterRange:NULL]; + _lineNumberLayoutManager.selectedRange = glyphRange; + [_lineNumberLayoutManager invalidateDisplayForGlyphRange:glyphRange]; + } + } + else + _lineNumberLayoutManager.selectedRange = NSMakeRange(-1, 0); + + [super drawRect:rect]; +} + +- (void)setDrawLineNumbers:(BOOL)drawLineNumbers +{ + _drawLineNumbers = drawLineNumbers; + + if (_drawLineNumbers) + self.textContainerInset = UIEdgeInsetsMake(8, _lineNumberLayoutManager.gutterWidth, 8, 0); + else + self.textContainerInset = UIEdgeInsetsMake(0, 0, 0, 0); + + // Redraw to remove the stripe at the left + [self setNeedsDisplay]; +} + + +#pragma mark - KVO + +// Implementation original sourced from https://github.com/illyabusigin/CYRTextView +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context +{ + if (([keyPath isEqualToString:NSStringFromSelector(@selector(selectedTextRange))] || + [keyPath isEqualToString:NSStringFromSelector(@selector(selectedRange))]) && context == JLTextViewContext) + [self setNeedsDisplay]; + else + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; +} + @end diff --git a/Chromatism/Chromatism/JLTextViewController.h b/Chromatism/Chromatism/JLTextViewController.h index 3fc1b82..ef55e3c 100644 --- a/Chromatism/Chromatism/JLTextViewController.h +++ b/Chromatism/Chromatism/JLTextViewController.h @@ -34,4 +34,7 @@ // Convenience property for self.textView.syntaxTokenizer @property (nonatomic, weak, readonly) JLTokenizer *tokenizer; + +- (void)toggleDisplayLineNumbers; + @end diff --git a/Chromatism/Chromatism/JLTextViewController.m b/Chromatism/Chromatism/JLTextViewController.m index f2f6041..71bb8d4 100644 --- a/Chromatism/Chromatism/JLTextViewController.m +++ b/Chromatism/Chromatism/JLTextViewController.m @@ -53,6 +53,7 @@ - (JLTextView *)textView if (!_textView) { JLTextView *textView = [[JLTextView alloc] initWithFrame:CGRectZero]; textView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; + [textView setTheme:JLTokenizerThemeDefault]; if (self.defaultText) { textView.text = self.defaultText; @@ -66,7 +67,12 @@ - (JLTextView *)textView - (JLTokenizer *)tokenizer { - return self.textView.tokenizer; + return (JLTokenizer*)self.textView.tokenizer; +} + +- (void)toggleDisplayLineNumbers +{ + [[self textView] setDrawLineNumbers:![[self textView] drawLineNumbers]]; } - (void)viewDidLoad @@ -107,6 +113,12 @@ - (void)keyboardWasShown:(NSNotification *)notification UIScrollView *scrollView = self.textView; CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size; + // Fix size in landscape + if (UIInterfaceOrientationIsLandscape([[UIDevice currentDevice] orientation])) + { + kbSize = CGSizeMake(kbSize.height, kbSize.width); + } + UIEdgeInsets contentInsets = scrollView.contentInset; contentInsets.bottom = kbSize.height; scrollView.contentInset = contentInsets; diff --git a/ChromatismDemo/ChromatismDemo.xcodeproj/project.pbxproj b/ChromatismDemo/ChromatismDemo.xcodeproj/project.pbxproj index 36000b9..0c25545 100644 --- a/ChromatismDemo/ChromatismDemo.xcodeproj/project.pbxproj +++ b/ChromatismDemo/ChromatismDemo.xcodeproj/project.pbxproj @@ -229,9 +229,6 @@ LastUpgradeCheck = 0500; ORGANIZATIONNAME = Anviking; TargetAttributes = { - 3260A15F17A88BA9008792E6 = { - DevelopmentTeam = XU2559U3TH; - }; 3260A17A17A88BA9008792E6 = { TestTargetID = 3260A15F17A88BA9008792E6; }; diff --git a/ChromatismDemo/ChromatismDemo/AppDelegate.m b/ChromatismDemo/ChromatismDemo/AppDelegate.m index 3c276d4..dbb9997 100644 --- a/ChromatismDemo/ChromatismDemo/AppDelegate.m +++ b/ChromatismDemo/ChromatismDemo/AppDelegate.m @@ -37,11 +37,12 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( JLTextViewController *viewController = [[JLTextViewController alloc] initWithText:string]; viewController.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Resign" style:UIBarButtonItemStylePlain target:viewController.textView action:@selector(resignFirstResponder)]; + viewController.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Toggle Line Numbers" style:UIBarButtonItemStylePlain target:viewController action:@selector(toggleDisplayLineNumbers)]; UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewController]; viewController.title = @"Chromatism"; [navigationController.navigationBar setBarStyle:UIBarStyleBlack]; - viewController.textView.keyboardAppearance = UIKeyboardAppearanceDark; + viewController.textView.keyboardAppearance = UIKeyboardAppearanceDefault; self.window.rootViewController = navigationController; self.window.backgroundColor = [UIColor whiteColor];