GitHub PullRequestReviewCommentEvent at 2014-07-19 02:07:57
Repository: cappuccino
Actor: primalmotion
Payload: {"action":"created","comment":{"url":"https://api.github.com/repos/cappuccino/cappuccino/pulls/comments/15141338","id":15141338,"diff_hunk":"@@ -0,0 +1,1828 @@\n+/*\n+ *  CPTextView.j\n+ *  AppKit\n+ *\n+ *  Created by Daniel Boehringer on 27/12/2013.\n+ *  All modifications copyright Daniel Boehringer 2013.\n+ *  Based on original work by\n+ *  Created by Emmanuel Maillard on 27/02/2010.\n+ *  Copyright Emmanuel Maillard 2010.\n+ *\n+ * This library is free software; you can redistribute it and/or\n+ * modify it under the terms of the GNU Lesser General Public\n+ * License as published by the Free Software Foundation; either\n+ * version 2.1 of the License, or (at your option) any later version.\n+ *\n+ * This library is distributed in the hope that it will be useful,\n+ * but WITHOUT ANY WARRANTY; without even the implied warranty of\n+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n+ * Lesser General Public License for more details.\n+ *\n+ * You should have received a copy of the GNU Lesser General Public\n+ * License along with this library; if not, write to the Free Software\n+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n+ */\n+\n+@import \"CPText.j\"\n+@import \"CPPasteboard.j\"\n+@import \"CPColorPanel.j\"\n+@import \"CPFontManager.j\"\n+@import \"CPTextStorage.j\"\n+@import \"CPTextContainer.j\"\n+@import \"CPLayoutManager.j\"\n+\n+@class _CPRTFProducer;\n+@class _CPRTFParser;\n+\n+\n+@protocol CPTextViewDelegate <CPTextDelegate>\n+\n+- (BOOL)textView:(CPTextView)aTextView doCommandBySelector:(SEL)aSelector;\n+- (BOOL)textView:(CPTextView)aTextView shouldChangeTextInRange:(CPRange)affectedCharRange replacementString:(CPString)replacementString;\n+- (CPDictionary)textView:(CPTextView)textView shouldChangeTypingAttributes:(CPDictionary)oldTypingAttributes toAttributes:(CPDictionary)newTypingAttributes;\n+- (CPRange)textView:(CPTextView)aTextView willChangeSelectionFromCharacterRange:(CPRange)oldSelectedCharRange toCharacterRange:(CPRange)newSelectedCharRange;\n+- (void)textViewDidChangeSelection:(CPNotification)aNotification;\n+- (void)textViewDidChangeTypingAttributes:(CPNotification)aNotification;\n+\n+@end\n+\n+\n+_MakeRangeFromAbs = function(a1, a2)\n+{\n+    return (a1 < a2) ? CPMakeRange(a1, a2 - a1) : CPMakeRange(a2, a1 - a2);\n+};\n+\n+_MidRange = function(a1)\n+{\n+    return Math.floor((CPMaxRange(a1) + a1.location) / 2);\n+};\n+\n+/*\n+    CPSelectionGranularity\n+*/\n+CPSelectByCharacter = 0;\n+CPSelectByWord      = 1;\n+CPSelectByParagraph = 2;\n+\n+\n+var kDelegateRespondsTo_textShouldBeginEditing                                          = 0x0001,\n+    kDelegateRespondsTo_textView_doCommandBySelector                                    = 0x0002,\n+    kDelegateRespondsTo_textView_willChangeSelectionFromCharacterRange_toCharacterRange = 0x0004,\n+    kDelegateRespondsTo_textView_shouldChangeTextInRange_replacementString              = 0x0008,\n+    kDelegateRespondsTo_textView_shouldChangeTypingAttributes_toAttributes              = 0x0010;\n+\n+/*!\n+    @ingroup appkit\n+    @class CPTextView\n+*/\n+@implementation CPTextView : CPText\n+{\n+    BOOL                    _allowsUndo             @accessors(property=allowsUndo);\n+    BOOL                    _usesFontPanel          @accessors(property=usesFontPanel);\n+    CPColor                 _insertionPointColor    @accessors(property=insertionPointColor);\n+    CPSelectionGranularity  _selectionGranularity   @accessors(property=selectionGranularity);\n+\n+    CPTextStorage               _textStorage;\n+    CPTextContainer             _textContainer;\n+    CPLayoutManager             _layoutManager;\n+\n+    id <CPTextViewDelegate>     _delegate;\n+    unsigned                    _delegateRespondsToSelectorMask;\n+\n+    CGSize          _textContainerInset;\n+    CGPoint         _textContainerOrigin;\n+\n+    int             _startTrackingLocation;\n+    CPRange         _selectionRange;\n+    CPDictionary    _selectedTextAttributes;\n+\n+    CPDictionary    _typingAttributes;\n+\n+    BOOL            _isFirstResponder;\n+\n+    BOOL            _drawCaret;\n+    CPTimer         _caretTimer;\n+    CPTimer         _scollingTimer;\n+    CGRect          _caretRect;\n+\n+    CPFont          _font;\n+    CPColor         _textColor;\n+\n+    CGSize          _minSize;\n+    CGSize          _maxSize;\n+\n+    BOOL            _scrollingDownward;\n+\n+    BOOL            _isRichText;\n+    BOOL            _isHorizontallyResizable;\n+    BOOL            _isVerticallyResizable;\n+    BOOL            _isEditable;\n+    BOOL            _isSelectable;\n+\n+    var             _caretDOM;\n+    int             _stickyXLocation;\n+}\n+\n+\n+#pragma mark -\n+#pragma mark Class methods\n+\n+/* <!> FIXME\n+    just a testing characterSet\n+    all of this depend of the current language.\n+    Need some CPLocale support and maybe even a FSM...\n+ */\n++ (CPArray)_wordBoundaryCharacterArray\n+{\n+    return ['\\n','\\r', ' ', '\\t', ',', ';', '.', '!', '?', '\\'', '\"', '-', ':'];\n+}\n+\n++ (CPArray)_paragraphBoundaryCharacterArray\n+{\n+    return ['\\n','\\r'];\n+}\n+\n+\n+#pragma mark -\n+#pragma mark Init methodes\n+\n+- (id)initWithFrame:(CGRect)aFrame textContainer:(CPTextContainer)aContainer\n+{\n+    self = [super initWithFrame:aFrame];\n+\n+    if (self)\n+    {\n+#if PLATFORM(DOM)\n+        self._DOMElement.style.cursor = \"text\";\n+#endif\n+        _textContainerInset = CGSizeMake(2,0);\n+        _textContainerOrigin = CGPointMake(_bounds.origin.x, _bounds.origin.y);\n+        [aContainer setTextView:self];\n+        _isEditable = YES;\n+        _isSelectable = YES;\n+\n+        _isFirstResponder = NO;\n+        _delegate = nil;\n+        _delegateRespondsToSelectorMask = 0;\n+        _selectionRange = CPMakeRange(0, 0);\n+\n+        _selectionGranularity = CPSelectByCharacter;\n+        _selectedTextAttributes = [CPDictionary dictionaryWithObject:[CPColor selectedTextBackgroundColor]\n+                                                forKey:CPBackgroundColorAttributeName];\n+\n+        _insertionPointColor = [CPColor blackColor];\n+        _textColor = [CPColor blackColor];\n+        _font = [CPFont systemFontOfSize:12.0];\n+        [self setFont:_font];\n+        [self setBackgroundColor:[CPColor whiteColor]];\n+\n+\n+        _typingAttributes = [[CPDictionary alloc] initWithObjects:[_font, _textColor] forKeys:[CPFontAttributeName, CPForegroundColorAttributeName]];\n+\n+        _minSize = CGSizeCreateCopy(aFrame.size);\n+        _maxSize = CGSizeMake(aFrame.size.width, 1e7);\n+\n+        _isRichText = NO;\n+        _usesFontPanel = YES;\n+        _allowsUndo = YES;\n+        _isVerticallyResizable = YES;\n+        _isHorizontallyResizable = NO;\n+\n+        _caretRect = CGRectMake(0, 0, 1, 11);\n+    }\n+\n+    [self registerForDraggedTypes:[CPColorDragType]];\n+\n+    return self;\n+}\n+\n+- (id)initWithFrame:(CGRect)aFrame\n+{\n+    var layoutManager = [[CPLayoutManager alloc] init],\n+        textStorage = [[CPTextStorage alloc] init],\n+        container = [[CPTextContainer alloc] initWithContainerSize:CGSizeMake(aFrame.size.width, 1e7)];\n+\n+    [textStorage addLayoutManager:layoutManager];\n+    [layoutManager addTextContainer:container];\n+\n+    return [self initWithFrame:aFrame textContainer:container];\n+}\n+\n+\n+#pragma mark -\n+#pragma mark Responders method\n+\n+- (BOOL)acceptsFirstResponder\n+{\n+    if (_isSelectable)\n+        return YES;\n+\n+    return NO;\n+}\n+\n+- (BOOL)becomeFirstResponder\n+{\n+    _isFirstResponder = YES;\n+    [self updateInsertionPointStateAndRestartTimer:YES];\n+    [[CPFontManager sharedFontManager] setSelectedFont:[self font] isMultiple:NO];\n+    [self setNeedsDisplay:YES];\n+\n+    return YES;\n+}\n+\n+- (BOOL)resignFirstResponder\n+{\n+    [_caretTimer invalidate];\n+    _caretTimer = nil;\n+    _isFirstResponder = NO;\n+    [self setNeedsDisplay:YES];\n+\n+    return YES;\n+}\n+\n+\n+#pragma mark -\n+#pragma mark Delegate methods\n+\n+/*!\n+    Returns the delegate object for the text view.\n+*/\n+- (id)delegate\n+{\n+    return _delegate;\n+}\n+\n+/*!\n+    TODO : documentation\n+*/\n+- (void)setDelegate:(id <CPTextViewDelegate>)aDelegate\n+{\n+    if (aDelegate === _delegate)\n+        return;\n+\n+    _delegateRespondsToSelectorMask = 0;\n+\n+    if (_delegate)\n+    {\n+        [[CPNotificationCenter defaultCenter] removeObserver:_delegate name:CPTextDidChangeNotification object:self];\n+        [[CPNotificationCenter defaultCenter] removeObserver:_delegate name:CPTextViewDidChangeSelectionNotification object:self];\n+        [[CPNotificationCenter defaultCenter] removeObserver:_delegate name:CPTextViewDidChangeTypingAttributesNotification object:self];\n+    }\n+\n+    _delegate = aDelegate;\n+\n+    if (_delegate)\n+    {\n+        if ([_delegate respondsToSelector:@selector(textDidChange:)])\n+            [[CPNotificationCenter defaultCenter] addObserver:_delegate selector:@selector(textDidChange:) name:CPTextDidChangeNotification object:self];\n+\n+        if ([_delegate respondsToSelector:@selector(textViewDidChangeSelection:)])\n+            [[CPNotificationCenter defaultCenter] addObserver:_delegate selector:@selector(textViewDidChangeSelection:) name:CPTextViewDidChangeSelectionNotification object:self];\n+\n+        if ([_delegate respondsToSelector:@selector(textViewDidChangeTypingAttributes:)])\n+            [[CPNotificationCenter defaultCenter] addObserver:_delegate selector:@selector(textViewDidChangeTypingAttributes:) name:CPTextViewDidChangeTypingAttributesNotification object:self];\n+\n+        if ([_delegate respondsToSelector:@selector(textView:doCommandBySelector:)])\n+            _delegateRespondsToSelectorMask |= kDelegateRespondsTo_textView_doCommandBySelector;\n+\n+        if ([_delegate respondsToSelector:@selector(textShouldBeginEditing:)])\n+            _delegateRespondsToSelectorMask |= kDelegateRespondsTo_textShouldBeginEditing;\n+\n+        if ([_delegate respondsToSelector:@selector(textView:willChangeSelectionFromCharacterRange:toCharacterRange:)])\n+            _delegateRespondsToSelectorMask |= kDelegateRespondsTo_textView_willChangeSelectionFromCharacterRange_toCharacterRange;\n+\n+        if ([_delegate respondsToSelector:@selector(textView:shouldChangeTextInRange:replacementString:)])\n+            _delegateRespondsToSelectorMask |= kDelegateRespondsTo_textView_shouldChangeTextInRange_replacementString;\n+\n+        if ([_delegate respondsToSelector:@selector(textView:shouldChangeTypingAttributes:toAttributes:)])\n+            _delegateRespondsToSelectorMask |= kDelegateRespondsTo_textView_shouldChangeTypingAttributes_toAttributes;\n+    }\n+}\n+\n+\n+#pragma mark -\n+#pragma mark Key window methods\n+\n+- (void)becomeKeyWindow\n+{\n+    [self setNeedsDisplay:YES];\n+}\n+\n+/*!\n+    @ignore\n+*/\n+- (void)resignKeyWindow\n+{\n+    [self setNeedsDisplay:YES];\n+}\n+\n+- (BOOL)_isFocused\n+{\n+   return [[self window] isKeyWindow] && _isFirstResponder;\n+}\n+\n+\n+#pragma mark -\n+#pragma mark Undo redo methods\n+\n+- (void)undo:(id)sender\n+{\n+    if (_allowsUndo)\n+        [[[self window] undoManager] undo];\n+}\n+\n+- (void)redo:(id)sender\n+{\n+    if (_allowsUndo)\n+        [[[self window] undoManager] redo];\n+}\n+\n+\n+#pragma mark -\n+#pragma mark Accessors\n+\n+- (void)setString:(CPString)aString\n+{\n+    [_textStorage replaceCharactersInRange:CPMakeRange(0, [_layoutManager numberOfCharacters]) withString:aString];\n+    [self didChangeText];\n+    [_layoutManager _validateLayoutAndGlyphs];\n+    [self sizeToFit];\n+    [self setNeedsDisplay:YES];\n+}\n+\n+- (CPString)string\n+{\n+    return [_textStorage string];\n+}\n+\n+// KVO support\n+- (void)setValue:(CPString)aValue\n+{\n+    [self setString:[aValue description]]\n+}\n+\n+- (id)value\n+{\n+    [self string]\n+}\n+\n+- (void)setTextContainer:(CPTextContainer)aContainer\n+{\n+    _textContainer = aContainer;\n+    _layoutManager = [_textContainer layoutManager];\n+    _textStorage = [_layoutManager textStorage];\n+    [_textStorage setFont:_font];\n+    [_textStorage setForegroundColor:_textColor];\n+\n+    [self invalidateTextContainerOrigin];\n+}\n+\n+- (CPTextContainer)textContainer\n+{\n+    return _textContainer;\n+}\n+\n+- (CPTextStorage)textStorage\n+{\n+    return _textStorage;\n+}\n+\n+- (CPLayoutManager)layoutManager\n+{\n+    return _layoutManager;\n+}\n+\n+- (void)setTextContainerInset:(CGSize)aSize\n+{\n+    _textContainerInset = aSize;\n+    [self invalidateTextContainerOrigin];\n+}\n+\n+- (CGSize)textContainerInset\n+{\n+    return _textContainerInset;\n+}\n+\n+- (CGPoint)textContainerOrigin\n+{\n+    return _textContainerOrigin;\n+}\n+\n+- (void)invalidateTextContainerOrigin\n+{\n+    _textContainerOrigin.x = _bounds.origin.x;\n+    _textContainerOrigin.x += _textContainerInset.width;\n+\n+    _textContainerOrigin.y = _bounds.origin.y;\n+    _textContainerOrigin.y += _textContainerInset.height;\n+}\n+\n+- (BOOL)isEditable\n+{\n+    return _isEditable;\n+}\n+\n+- (void)setEditable:(BOOL)flag\n+{\n+    _isEditable = flag;\n+\n+    if (flag)\n+        _isSelectable = flag;\n+}\n+\n+- (BOOL)isSelectable\n+{\n+    return _isSelectable;\n+}\n+\n+- (void)setSelectable:(BOOL)flag\n+{\n+    _isSelectable = flag;\n+\n+    if (flag)\n+        _isEditable = flag;\n+}\n+\n+- (void)doCommandBySelector:(SEL)aSelector\n+{\n+    if (![self _sendDelegateDoCommandBySelector:aSelector])\n+        [super doCommandBySelector:aSelector];\n+}\n+\n+- (void)didChangeText\n+{\n+    [[CPNotificationCenter defaultCenter] postNotificationName:CPTextDidChangeNotification object:self];\n+}\n+\n+- (BOOL)shouldChangeTextInRange:(CPRange)aRange replacementString:(CPString)aString\n+{\n+    if (!_isEditable)\n+        return NO;\n+\n+    return [self _sendDelegateTextShouldBeginEditing] && [self _sendDelegateShouldChangeTextInRange:aRange replacementString:aString];\n+}\n+\n+\n+#pragma mark -\n+#pragma mark Insert characters methods\n+\n+- (void)_replaceCharactersInRange:aRange withAttributedString:(CPString)aString\n+{\n+    [_textStorage replaceCharactersInRange:aRange withAttributedString:aString];\n+    [self setSelectedRange:CPMakeRange(aRange.location, [aString length])];\n+    [_layoutManager _validateLayoutAndGlyphs];\n+    [self sizeToFit];\n+    [self scrollRangeToVisible:_selectionRange];\n+    [self setNeedsDisplay:YES];\n+\n+}\n+- (void)_replaceCharactersInRange:(CPRange)aRange withString:(CPString)aString\n+{\n+    [_textStorage replaceCharactersInRange:CPMakeRangeCopy(aRange) withString:aString];\n+    [self setSelectedRange:CPMakeRange(aRange.location, aString.length)];\n+    [_layoutManager _validateLayoutAndGlyphs];\n+    [self sizeToFit];\n+    [self scrollRangeToVisible:_selectionRange];\n+    [self setNeedsDisplay:YES];\n+}\n+\n+- (void)insertText:(CPString)aString\n+{\n+    var isAttributed = [aString isKindOfClass:CPAttributedString],\n+        string = isAttributed ? [aString string]:aString;\n+\n+    if (![self shouldChangeTextInRange:CPMakeRangeCopy(_selectionRange) replacementString:string])\n+        return;\n+\n+    if (isAttributed)\n+    {\n+        [[[[self window] undoManager] prepareWithInvocationTarget:self]\n+                _replaceCharactersInRange:CPMakeRange(_selectionRange.location, [aString length])\n+                withAttributedString:[_textStorage attributedSubstringFromRange:CPMakeRangeCopy(_selectionRange)]];\n+        [[[self window] undoManager] setActionName:@\"Replace rich text\"];\n+\n+        [_textStorage replaceCharactersInRange:CPMakeRangeCopy(_selectionRange) withAttributedString:aString];\n+    }\n+    else\n+    {\n+        [[[self window] undoManager] setActionName:@\"Replace plain text\"];\n+\n+        if (_isRichText)\n+        {\n+            aString = [[CPAttributedString alloc] initWithString:aString attributes:_typingAttributes];\n+            [[[[self window] undoManager] prepareWithInvocationTarget:self]\n+                _replaceCharactersInRange:CPMakeRange(_selectionRange.location, [aString length])\n+                withAttributedString:[_textStorage attributedSubstringFromRange:CPMakeRangeCopy(_selectionRange)]];\n+            [_textStorage replaceCharactersInRange:CPMakeRangeCopy(_selectionRange) withAttributedString:aString];\n+        }\n+        else\n+        {\n+            [[[[self window] undoManager] prepareWithInvocationTarget:self] _replaceCharactersInRange:CPMakeRange(_selectionRange.location, [aString length]) withString:[[self string] substringWithRange:CPMakeRangeCopy(_selectionRange)]];\n+            [_textStorage replaceCharactersInRange:CPMakeRangeCopy(_selectionRange) withString:aString];\n+        }\n+    }\n+\n+    [self setSelectedRange:CPMakeRange(_selectionRange.location + [string length], 0)];\n+\n+    [self didChangeText];\n+    [_layoutManager _validateLayoutAndGlyphs];\n+    [self sizeToFit];\n+    [self scrollRangeToVisible:_selectionRange];\n+    _stickyXLocation = _caretRect.origin.x;\n+}\n+\n+- (void)_blinkCaret:(CPTimer)aTimer\n+{\n+    _drawCaret = !_drawCaret;\n+    [self setNeedsDisplayInRect:_caretRect];\n+}\n+\n+\n+#pragma mark -\n+#pragma mark Drawing methods\n+\n+- (void)drawInsertionPointInRect:(CGRect)aRect color:(CPColor)aColor turnedOn:(BOOL)flag\n+{\n+#if PLATFORM(DOM)\n+    var style;\n+\n+    if (!_caretDOM)\n+    {\n+        _caretDOM = document.createElement(\"span\");\n+        style = _caretDOM.style;\n+        style.position = \"absolute\";\n+        style.visibility = \"visible\";\n+        style.padding = \"0px\";\n+        style.margin = \"0px\";\n+        style.whiteSpace = \"pre\";\n+        style.backgroundColor = \"black\";\n+        _caretDOM.style.width = \"1px\";\n+        self._DOMElement.appendChild(_caretDOM);\n+    }\n+\n+    _caretDOM.style.left = (aRect.origin.x) + \"px\";\n+    _caretDOM.style.top = (aRect.origin.y) + \"px\";\n+    _caretDOM.style.height = (aRect.size.height) + \"px\";\n+    _caretDOM.style.visibility = flag ? \"visible\" : \"hidden\";\n+#endif\n+}\n+\n+- (void)drawRect:(CGRect)aRect\n+{\n+    var ctx = [[CPGraphicsContext currentContext] graphicsPort],\n+        range = [_layoutManager glyphRangeForBoundingRect:aRect inTextContainer:_textContainer];\n+\n+    if (_selectionRange.length)\n+    {\n+        var rects = [_layoutManager rectArrayForCharacterRange:_selectionRange\n+                                    withinSelectedCharacterRange:_selectionRange\n+                                    inTextContainer:_textContainer\n+                                    rectCount:nil];\n+\n+        CGContextSaveGState(ctx);\n+        var effectiveSelectionColor = [self _isFocused]? [_selectedTextAttributes objectForKey:CPBackgroundColorAttributeName] : [CPColor selectedTextBackgroundColorUnfocussed];\n+\n+        CGContextSetFillColor(ctx, effectiveSelectionColor);\n+\n+        for (var i = 0; i < rects.length; i++)\n+        {\n+            rects[i].origin.x += _textContainerOrigin.x;\n+            rects[i].origin.y += _textContainerOrigin.y;\n+\n+            CGContextFillRect(ctx, rects[i]);\n+        }\n+\n+        CGContextRestoreGState(ctx);\n+    }\n+\n+    if (range.length)\n+        [_layoutManager drawGlyphsForGlyphRange:range atPoint:_textContainerOrigin];\n+\n+    if ([self shouldDrawInsertionPoint])\n+    {\n+        [self updateInsertionPointStateAndRestartTimer:NO];\n+        [self drawInsertionPointInRect:_caretRect color:_insertionPointColor turnedOn:_drawCaret];\n+    }\n+    else // <!> FIXME: breaks DOM abstraction, but i did get it working otherwise\n+    {\n+        if (_caretDOM)\n+            _caretDOM.style.visibility = \"hidden\";\n+    }\n+}\n+\n+\n+#pragma mark -\n+#pragma mark Select methods\n+\n+- (void)selectAll:(id)sender\n+{\n+    if (_isSelectable)\n+    {\n+        if (_caretTimer)\n+        {\n+            [_caretTimer invalidate];\n+            _caretTimer = nil;\n+        }\n+\n+        [self setSelectedRange:CPMakeRange(0, [_layoutManager numberOfCharacters])];\n+    }\n+}\n+\n+- (void)setSelectedRange:(CPRange)range\n+{\n+    [self setSelectedRange:range affinity:0 stillSelecting:NO];\n+    [self setTypingAttributes:[_textStorage attributesAtIndex:MAX(0, range.location -1) effectiveRange:nil]];\n+}\n+\n+- (void)setSelectedRange:(CPRange)range affinity:(CPSelectionAffinity /* unused */ )affinity stillSelecting:(BOOL)selecting\n+{\n+    var maxRange = CPMakeRange(0, [_layoutManager numberOfCharacters]);\n+    range = CPIntersectionRange(maxRange, range);\n+\n+    if (!selecting && [self _delegateRespondsToWillChangeSelectionFromCharacterRangeToCharacterRange])\n+    {\n+        _selectionRange = [self _sendDelegateWillChangeSelectionFromCharacterRange:_selectionRange toCharacterRange:range];\n+    }\n+    else\n+    {\n+        _selectionRange = CPMakeRangeCopy(range);\n+        _selectionRange = [self selectionRangeForProposedRange:_selectionRange granularity:[self selectionGranularity]];\n+    }\n+\n+    if (_selectionRange.length)\n+        [_layoutManager invalidateDisplayForGlyphRange:_selectionRange];\n+    else\n+        [self setNeedsDisplay:YES];\n+\n+    if (!selecting)\n+    {\n+        if (_isFirstResponder)\n+            [self updateInsertionPointStateAndRestartTimer:((_selectionRange.length === 0) && ![_caretTimer isValid])];\n+\n+        [self setTypingAttributes:[_textStorage attributesAtIndex:MAX(0, range.location -1) effectiveRange:nil]];\n+\n+        [[CPNotificationCenter defaultCenter] postNotificationName:CPTextViewDidChangeSelectionNotification object:self];\n+    }\n+}\n+\n+- (CPArray)selectedRanges\n+{\n+    return [_selectionRange];\n+}\n+\n+- (CPRange)selectionRangeForProposedRange:(CPRange)proposedRange granularity:(CPSelectionGranularity)granularity\n+{\n+    var textStorageLength = [_layoutManager numberOfCharacters];\n+\n+    if (textStorageLength == 0)\n+        return CPMakeRange(0, 0);\n+\n+    if (proposedRange.location >= textStorageLength)\n+        return CPMakeRange(textStorageLength, 0);\n+\n+    if (CPMaxRange(proposedRange) > textStorageLength)\n+        proposedRange.length = textStorageLength - proposedRange.location;\n+\n+    var string = [_textStorage string];\n+\n+    switch (granularity)\n+    {\n+        case CPSelectByWord:\n+            var wordRange = [self _characterRangeForUnitAtIndex:proposedRange.location asDefinedByCharArray:[[self class] _wordBoundaryCharacterArray] skip:YES];\n+\n+            if (proposedRange.length)\n+                wordRange = CPUnionRange(wordRange, [self _characterRangeForUnitAtIndex:CPMaxRange(proposedRange) asDefinedByCharArray:[[self class] _wordBoundaryCharacterArray] skip:NO]);\n+\n+            return wordRange;\n+\n+        case CPSelectByParagraph:\n+            var parRange = [self _characterRangeForUnitAtIndex:proposedRange.location asDefinedByCharArray:[[self class] _paragraphBoundaryCharacterArray] skip:NO];\n+\n+            if (proposedRange.length)\n+                parRange = CPUnionRange(parRange, [self _characterRangeForUnitAtIndex:CPMaxRange(proposedRange) asDefinedByCharArray: [[self class] _paragraphBoundaryCharacterArray] skip:NO]);\n+\n+            return parRange;\n+\n+        default:\n+            return proposedRange;\n+    }\n+}\n+\n+\n+#pragma mark -\n+#pragma mark Keyboard events\n+\n+- (void)keyDown:(CPEvent)event\n+{\n+    [self interpretKeyEvents:[event]];\n+}\n+\n+\n+#pragma mark -\n+#pragma mark Mouse Events\n+\n+- (void)mouseDown:(CPEvent)event\n+{\n+    var fraction = [],\n+        point = [self convertPoint:[event locationInWindow] fromView:nil];\n+\n+    /* stop _caretTimer */\n+    [_caretTimer invalidate];\n+    _caretTimer = nil;\n+    [self _hideCaret];\n+\n+    // convert to container coordinate\n+    point.x -= _textContainerOrigin.x;\n+    point.y -= _textContainerOrigin.y;\n+\n+    _startTrackingLocation = [_layoutManager glyphIndexForPoint:point inTextContainer:_textContainer fractionOfDistanceThroughGlyph:fraction];\n+\n+    if (_startTrackingLocation === CPNotFound)\n+        _startTrackingLocation = [_layoutManager numberOfCharacters];\n+\n+    var granularities = [-1, CPSelectByCharacter, CPSelectByWord, CPSelectByParagraph];\n+    [self setSelectionGranularity:granularities[[event clickCount]]];\n+\n+    var setRange = CPMakeRange(_startTrackingLocation, 0);\n+\n+    if ([event modifierFlags] & CPShiftKeyMask)\n+    {\n+        setRange = _MakeRangeFromAbs(_startTrackingLocation < _MidRange(_selectionRange)?\n+                                     CPMaxRange(_selectionRange) : _selectionRange.location,\n+                                     _startTrackingLocation);\n+    }\n+\n+    [self setSelectedRange:setRange affinity:0 stillSelecting:YES];\n+}\n+\n+- (void)_clearRange:(var)range\n+{\n+    var rects = [_layoutManager rectArrayForCharacterRange:nil withinSelectedCharacterRange:range\n+                                 inTextContainer:_textContainer\n+                                 rectCount:nil],\n+        l = rects.length;\n+\n+    for (var i = 0; i < l ; i++)\n+    {\n+        rects[i].origin.x += _textContainerOrigin.x;\n+        rects[i].origin.y += _textContainerOrigin.y;\n+        [self setNeedsDisplayInRect:rects[i]];\n+    }\n+}\n+\n+- (void)mouseDragged:(CPEvent)event\n+{\n+    var fraction = [],\n+        point = [self convertPoint:[event locationInWindow] fromView:nil];\n+\n+    // convert to container coordinate\n+    point.x -= _textContainerOrigin.x;\n+    point.y -= _textContainerOrigin.y;\n+\n+    var oldRange = [self selectedRange],\n+        index = [_layoutManager glyphIndexForPoint:point\n+                                inTextContainer:_textContainer\n+                                fractionOfDistanceThroughGlyph:fraction];\n+\n+    if (index == CPNotFound)\n+        index = _scrollingDownward ? CPMaxRange(oldRange) : oldRange.location;\n+\n+    if (index > oldRange.location)\n+    {\n+        [self _clearRange:_MakeRangeFromAbs(oldRange.location,index)];\n+        _scrollingDownward = YES;\n+    }\n+\n+    if (index < CPMaxRange(oldRange))\n+    {\n+        [self _clearRange:_MakeRangeFromAbs(index, CPMaxRange(oldRange))];\n+        _scrollingDownward = NO;\n+    }\n+\n+    if (index < _startTrackingLocation)\n+        [self setSelectedRange:CPMakeRange(index, _startTrackingLocation - index)\n+              affinity:0\n+              stillSelecting:YES];\n+    else\n+        [self setSelectedRange:CPMakeRange(_startTrackingLocation, index - _startTrackingLocation)\n+              affinity:0\n+              stillSelecting:YES];\n+\n+    [self scrollRangeToVisible:CPMakeRange(index, 0)];\n+}\n+\n+// handle all the other methods from CPKeyBinding.j\n+\n+- (void)mouseUp:(CPEvent)event\n+{\n+    /* will post CPTextViewDidChangeSelectionNotification */\n+    _previousSelectionGranularity = [self selectionGranularity];\n+    [self setSelectionGranularity:CPSelectByCharacter];\n+    [self setSelectedRange:[self selectedRange] affinity:0 stillSelecting:NO];\n+\n+    var point = [_layoutManager locationForGlyphAtIndex:[self selectedRange].location];\n+    _stickyXLocation= point.x;\n+    _startTrackingLocation = _selectionRange.location;\n+}\n+\n+- (void)moveDown:(id)sender\n+{\n+    if (_isSelectable)\n+    {\n+        var fraction = [],\n+            nglyphs= [_layoutManager numberOfCharacters],\n+            sindex = CPMaxRange([self selectedRange]),\n+            rectSource = [_layoutManager boundingRectForGlyphRange:CPMakeRange(sindex, 1) inTextContainer:_textContainer],\n+            rectEnd = nglyphs ? [_layoutManager boundingRectForGlyphRange:CPMakeRange(nglyphs - 1, 1) inTextContainer:_textContainer] : rectSource,\n+            point = rectSource.origin;\n+\n+        if (point.y >= rectEnd.origin.y)\n+            return;\n+\n+        if (_stickyXLocation)\n+            point.x = _stickyXLocation;\n+\n+        // <!> FIXME: Define constants for this magic number\n+        point.y += 2 + rectSource.size.height;\n+        point.x += 2;\n+\n+        var dindex= [_layoutManager glyphIndexForPoint:point inTextContainer:_textContainer fractionOfDistanceThroughGlyph:fraction],\n+            oldStickyLoc = _stickyXLocation;\n+        [self _establishSelection:CPMakeRange(dindex,0) byExtending:NO];\n+        _stickyXLocation = oldStickyLoc;\n+        [self scrollRangeToVisible:CPMakeRange(dindex, 0)]\n+    }\n+}\n+\n+- (void)moveDownAndModifySelection:(id)sender\n+{\n+    if (_isSelectable)\n+    {\n+        var oldStartTrackingLocation = _startTrackingLocation;\n+        [self _performSelectionFixupForRange:CPMakeRange(_selectionRange.location < _startTrackingLocation? _selectionRange.location : CPMaxRange(_selectionRange), 0)];\n+        [self moveDown:sender];\n+        _startTrackingLocation = oldStartTrackingLocation;\n+        [self _performSelectionFixupForRange:_MakeRangeFromAbs(_startTrackingLocation, (_selectionRange.location < _startTrackingLocation? _selectionRange.location : CPMaxRange(_selectionRange)))];\n+    }\n+}\n+\n+- (void)moveUp:(id)sender\n+{\n+    if (_isSelectable)\n+    {\n+        var fraction = [],\n+            sindex = [self selectedRange].location,\n+            rectSource = [_layoutManager boundingRectForGlyphRange:CPMakeRange(sindex, 1) inTextContainer:_textContainer],\n+            point = rectSource.origin;\n+\n+        if (point.y <= 0)\n+            return;\n+\n+        if (_stickyXLocation)\n+            point.x = _stickyXLocation;\n+\n+        point.y -= 2;    // FIXME <!> these should not be constants\n+        point.x += 2;\n+\n+        var dindex= [_layoutManager glyphIndexForPoint:point inTextContainer:_textContainer fractionOfDistanceThroughGlyph:fraction],\n+            oldStickyLoc = _stickyXLocation;\n+        [self _establishSelection:CPMakeRange(dindex,0) byExtending:NO];\n+        _stickyXLocation = oldStickyLoc;\n+        [self scrollRangeToVisible:CPMakeRange(dindex, 0)]\n+    }\n+}\n+\n+- (void)moveUpAndModifySelection:(id)sender\n+{\n+    if (_isSelectable)\n+    {\n+        var oldStartTrackingLocation = _startTrackingLocation;\n+        [self _performSelectionFixupForRange:CPMakeRange(_selectionRange.location < _startTrackingLocation? _selectionRange.location : CPMaxRange(_selectionRange), 0)];\n+        [self moveUp:sender];\n+        _startTrackingLocation = oldStartTrackingLocation;\n+        [self _performSelectionFixupForRange:_MakeRangeFromAbs(_startTrackingLocation, (_selectionRange.location < _startTrackingLocation? _selectionRange.location : CPMaxRange(_selectionRange)))];\n+    }\n+}\n+\n+- (void)_performSelectionFixupForRange:(CPRange)aSel\n+{\n+    aSel.location = MAX(0, aSel.location);\n+\n+    if (CPMaxRange(aSel) > [_layoutManager numberOfCharacters])\n+        aSel = CPMakeRange([_layoutManager numberOfCharacters], 0);\n+\n+    [self setSelectedRange:aSel];\n+    var point = [_layoutManager locationForGlyphAtIndex:aSel.location];\n+    _stickyXLocation = point.x;\n+}\n+\n+- (void)_establishSelection:(CPSelection)aSel byExtending:(BOOL)flag\n+{\n+    if (flag)\n+        aSel = CPUnionRange(aSel, _selectionRange);\n+\n+    [self _performSelectionFixupForRange:aSel];\n+    _startTrackingLocation = _selectionRange.location;\n+}\n+\n+- (unsigned)_calculateMoveSelectionFromRange:(CPRange)aRange intoDirection:(integer)move granularity:(CPSelectionGranularity)granularity\n+{\n+    var inWord = ![self _isCharacterAtIndex:(move > 0 ? CPMaxRange(aRange) : aRange.location) + move granularity:granularity],\n+        aSel = [self selectionRangeForProposedRange:CPMakeRange((move > 0 ? CPMaxRange(aRange) : aRange.location) + move, 0) granularity:granularity],\n+        bSel = [self selectionRangeForProposedRange:CPMakeRange((move > 0 ? CPMaxRange(aSel) : aSel.location) + move, 0) granularity:granularity];\n+\n+    return move > 0 ? CPMaxRange(inWord? aSel:bSel) : (inWord? aSel:bSel).location;\n+}\n+\n+- (void)_moveSelectionIntoDirection:(integer)move granularity:(CPSelectionGranularity)granularity\n+{\n+    var pos = [self _calculateMoveSelectionFromRange:_selectionRange intoDirection:move granularity:granularity];\n+    [self _performSelectionFixupForRange:CPMakeRange(pos, 0)];\n+    _startTrackingLocation = _selectionRange.location;\n+}\n+\n+- (void)_extendSelectionIntoDirection:(integer)move granularity:(CPSelectionGranularity)granularity\n+{\n+    var aSel = CPMakeRangeCopy(_selectionRange);\n+\n+    if (granularity !== CPSelectByCharacter)\n+    {   var pos = [self _calculateMoveSelectionFromRange:CPMakeRange(aSel.location < _startTrackingLocation? aSel.location : CPMaxRange(aSel), 0)\n+                                           intoDirection:move granularity:granularity];\n+        aSel = CPMakeRange(pos, 0);\n+    }\n+\n+    else\n+    {\n+        aSel = CPMakeRange((aSel.location < _startTrackingLocation? aSel.location : CPMaxRange(aSel)) + move, 0);\n+    }\n+\n+    aSel = _MakeRangeFromAbs(_startTrackingLocation, aSel.location);\n+    [self _performSelectionFixupForRange:aSel];\n+}\n+\n+- (void)moveLeftAndModifySelection:(id)sender\n+{\n+    if (_isSelectable)\n+       [self _extendSelectionIntoDirection:-1 granularity:CPSelectByCharacter];\n+}\n+\n+- (void)moveBackward:(id)sender\n+{\n+    [self moveLeft:sender];\n+}\n+\n+- (void)moveBackwardAndModifySelection:(id)sender\n+{\n+    [self moveLeftAndModifySelection:sender];\n+}\n+\n+- (void)moveRightAndModifySelection:(id)sender\n+{\n+    if (_isSelectable)\n+       [self _extendSelectionIntoDirection:+1 granularity:CPSelectByCharacter];\n+}\n+\n+- (void)moveLeft:(id)sender\n+{\n+    if (_isSelectable)\n+        [self _establishSelection:CPMakeRange(_selectionRange.location - 1, 0) byExtending:NO];\n+}\n+\n+- (void)moveToEndOfParagraph:(id)sender\n+{\n+    if (_isSelectable)\n+       [self _moveSelectionIntoDirection:+1 granularity:CPSelectByParagraph];\n+}\n+\n+- (void)moveToEndOfParagraphAndModifySelection:(id)sender\n+{\n+    if (_isSelectable)\n+       [self _extendSelectionIntoDirection:+1 granularity:CPSelectByParagraph];\n+}\n+\n+- (void)moveParagraphForwardAndModifySelection:(id)sender\n+{\n+    if (_isSelectable)\n+       [self _extendSelectionIntoDirection:+1 granularity:CPSelectByParagraph];\n+}\n+\n+- (void)moveParagraphForward:(id)sender\n+{\n+    if (_isSelectable)\n+       [self _moveSelectionIntoDirection:+1 granularity:CPSelectByParagraph]\n+}\n+\n+- (void)moveWordBackwardAndModifySelection:(id)sender\n+{\n+    [self moveWordLeftAndModifySelection:sender];\n+}\n+\n+- (void)moveWordBackward:(id)sender\n+{\n+    [self moveWordLeft:sender];\n+}\n+\n+- (void)moveWordForwardAndModifySelection:(id)sender\n+{\n+    [self moveWordRightAndModifySelection:sender];\n+}\n+\n+- (void)moveWordForward:(id)sender\n+{\n+    [self moveWordRight:sender];\n+}\n+\n+- (void)moveToBeginningOfDocument:(id)sender\n+{\n+    if (_isSelectable)\n+         [self _establishSelection:CPMakeRange(0, 0) byExtending:NO];\n+}\n+\n+- (void)moveToBeginningOfDocumentAndModifySelection:(id)sender\n+{\n+    if (_isSelectable)\n+         [self _establishSelection:CPMakeRange(0, 0) byExtending:YES];\n+}\n+\n+- (void)moveToEndOfDocument:(id)sender\n+{\n+    if (_isSelectable)\n+         [self _establishSelection:CPMakeRange([_layoutManager numberOfCharacters], 0) byExtending:NO];\n+}\n+\n+- (void)moveToEndOfDocumentAndModifySelection:(id)sender\n+{\n+    if (_isSelectable)\n+         [self _establishSelection:CPMakeRange([_layoutManager numberOfCharacters], 0) byExtending:YES];\n+}\n+\n+- (void)moveWordRight:(id)sender\n+{\n+    if (_isSelectable)\n+        [self _moveSelectionIntoDirection:+1 granularity:CPSelectByWord]\n+}\n+\n+- (void)moveToBeginningOfParagraph:(id)sender\n+{\n+    if (_isSelectable)\n+        [self _moveSelectionIntoDirection:-1 granularity:CPSelectByParagraph]\n+}\n+\n+- (void)moveToBeginningOfParagraphAndModifySelection:(id)sender\n+{\n+    if (_isSelectable)\n+       [self _extendSelectionIntoDirection:-1 granularity:CPSelectByParagraph];\n+}\n+\n+- (void)moveParagraphBackward:(id)sender\n+{\n+    if (_isSelectable)\n+        [self _moveSelectionIntoDirection:-1 granularity:CPSelectByParagraph]\n+}\n+\n+- (void)moveParagraphBackwardAndModifySelection:(id)sender\n+{\n+    if (_isSelectable)\n+       [self _extendSelectionIntoDirection:-1 granularity:CPSelectByParagraph];\n+}\n+\n+- (void)moveWordRightAndModifySelection:(id)sender\n+{\n+    if (_isSelectable)\n+         [self _extendSelectionIntoDirection:+1 granularity:CPSelectByWord];\n+}\n+\n+- (void)deleteToEndOfParagraph:(id)sender\n+{\n+    if (_isSelectable && _isEditable)\n+    {\n+        [self moveToEndOfParagraphAndModifySelection:self];\n+        [self delete:self];\n+    }\n+}\n+\n+- (void)deleteToBeginningOfParagraph:(id)sender\n+{\n+    if (_isSelectable && _isEditable)\n+    {\n+        [self moveToBeginningOfParagraphAndModifySelection:self];\n+        [self delete:self];\n+    }\n+}\n+\n+- (void)deleteToBeginningOfLine:(id)sender\n+{\n+    if (_isSelectable && _isEditable)\n+    {\n+        [self moveToLeftEndOfLineAndModifySelection:self];\n+        [self delete:self];\n+    }\n+}\n+\n+- (void)deleteToEndOfLine:(id)sender\n+{\n+    if (_isSelectable && _isEditable)\n+    {\n+        [self moveToRightEndOfLineAndModifySelection:self];\n+        [self delete:self];\n+    }\n+}\n+\n+- (void)deleteWordBackward:(id)sender\n+{\n+    if (_isSelectable && _isEditable)\n+    {\n+        [self moveWordLeftAndModifySelection:self];\n+        [self delete:self];\n+    }\n+}\n+\n+- (void)deleteWordForward:(id)sender\n+{\n+    if (_isSelectable && _isEditable)\n+    {\n+        [self moveWordRightAndModifySelection:self];\n+        [self delete:self];\n+    }\n+}\n+\n+- (void)moveToLeftEndOfLine:(id)sender byExtending:(BOOL)flag\n+{\n+    if (_isSelectable)\n+    {\n+        var fragment = [_layoutManager _lineFragmentForLocation:_selectionRange.location];\n+\n+        if (!fragment && _selectionRange.location > 0)\n+            fragment = [_layoutManager _lineFragmentForLocation:_selectionRange.location - 1];\n+        if (fragment)\n+            [self _establishSelection:CPMakeRange(fragment._range.location, 0) byExtending:flag];\n+    }\n+}\n+\n+- (void)moveToLeftEndOfLine:(id)sender\n+{\n+    [self moveToLeftEndOfLine:sender byExtending:NO];\n+}\n+\n+- (void)moveToLeftEndOfLineAndModifySelection:(id)sender\n+{\n+    [self moveToLeftEndOfLine:sender byExtending:YES];\n+}\n+\n+- (void)moveToRightEndOfLine:(id)sender byExtending:(BOOL)flag\n+{\n+    if (_isSelectable)\n+    {\n+        var fragment = [_layoutManager _lineFragmentForLocation:_selectionRange.location];\n+\n+        if (fragment)\n+        {\n+            var loc = CPMaxRange(fragment._range);\n+\n+            if (loc > 0 && loc < [_layoutManager numberOfCharacters])\n+                loc = MAX(0, loc - 1);\n+\n+            [self _establishSelection:CPMakeRange(loc, 0) byExtending:flag];\n+        }\n+    }\n+}\n+\n+- (void)moveToRightEndOfLine:(id)sender\n+{\n+    [self moveToRightEndOfLine:sender byExtending:NO];\n+}\n+\n+- (void)moveToRightEndOfLineAndModifySelection:(id)sender\n+{\n+    [self moveToRightEndOfLine:sender byExtending:YES];\n+}\n+\n+- (void)moveWordLeftAndModifySelection:(id)sender\n+{\n+    if (_isSelectable)\n+        [self _extendSelectionIntoDirection:-1 granularity:CPSelectByWord];\n+}\n+\n+- (void)moveWordLeft:(id)sender\n+{\n+    if (_isSelectable)\n+        [self _moveSelectionIntoDirection:-1 granularity:CPSelectByWord]\n+}\n+\n+- (void)moveRight:(id)sender\n+{\n+    if (_isSelectable)\n+        [self _establishSelection:CPMakeRange(CPMaxRange(_selectionRange) + 1, 0) byExtending:NO];\n+}\n+\n+- (void)_deleteForRange:(CPRange)changedRange\n+{\n+    if (![self shouldChangeTextInRange:changedRange replacementString:@\"\"])\n+        return;\n+\n+    [[[[self window] undoManager] prepareWithInvocationTarget:self] _replaceCharactersInRange:CPMakeRange(_selectionRange.location, 0) withAttributedString:[_textStorage attributedSubstringFromRange:CPMakeRangeCopy(changedRange)]];\n+    [_textStorage deleteCharactersInRange:CPMakeRangeCopy(changedRange)];\n+\n+    [self setSelectedRange:CPMakeRange(changedRange.location, 0)];\n+    [self didChangeText];\n+    [_layoutManager _validateLayoutAndGlyphs];\n+    [self sizeToFit];\n+    _stickyXLocation = _caretRect.origin.x;\n+}\n+\n+- (void)deleteBackward:(id)sender\n+{\n+    var changedRange;\n+\n+    if (CPEmptyRange(_selectionRange) && _selectionRange.location > 0)\n+         changedRange = CPMakeRange(_selectionRange.location - 1, 1);\n+    else\n+        changedRange = _selectionRange;\n+\n+    if (_previousSelectionGranularity > 0 &&\n+        changedRange.location > 0 && [self _isCharacterAtIndex:(changedRange.location - 1) granularity:_previousSelectionGranularity] &&\n+        changedRange.location < [[self string] length] && [self _isCharacterAtIndex:CPMaxRange(changedRange) granularity:_previousSelectionGranularity])\n+        changedRange.length++;\n+\n+    [self _deleteForRange:changedRange];\n+}\n+\n+- (void)deleteForward:(id)sender\n+{\n+    var changedRange = nil;\n+\n+    if (CPEmptyRange(_selectionRange) && _selectionRange.location < [_layoutManager numberOfCharacters])\n+         changedRange = CPMakeRange(_selectionRange.location, 1);\n+    else\n+        changedRange = _selectionRange;\n+\n+    [self _deleteForRange:changedRange];\n+}\n+\n+- (void)cut:(id)sender\n+{\n+    var selectedRange = [self selectedRange];\n+\n+    if (selectedRange.length < 1)\n+            return;\n+\n+    [self copy:sender];\n+    [self deleteBackward:sender]\n+}\n+\n+- (void)insertLineBreak:(id)sender\n+{\n+    [self insertText:@\"\\n\"];\n+}\n+\n+- (void)insertTab:(id)sender\n+{\n+    [self insertText:@\"\\t\"];\n+}\n+\n+- (void)insertTabIgnoringFieldEditor:(id)sender\n+{\n+    [self insertTab:sender];\n+}\n+\n+- (void)insertNewlineIgnoringFieldEditor:(id)sender\n+{\n+    [self insertLineBreak:sender];\n+}\n+\n+- (void)insertNewline:(id)sender\n+{\n+    [self insertLineBreak:sender];\n+}\n+\n+- (void)setTypingAttributes:(CPDictionary)attributes\n+{\n+    if (!attributes)\n+        attributes = [CPDictionary dictionary];\n+\n+    if ([self _delegateRespondsToShouldChangeTypingAttributesToAttributes])\n+    {\n+        _typingAttributes = [self _sendDelegateShouldChangeTypingAttributes:_typingAttributes toAttributes:attributes];\n+    }\n+    else\n+    {\n+        _typingAttributes = [attributes copy];\n+        /* check that new attributes contains essentials one's */\n+        if (![_typingAttributes containsKey:CPFontAttributeName])\n+            [_typingAttributes setObject:[self font] forKey:CPFontAttributeName];\n+\n+        if (![_typingAttributes containsKey:CPForegroundColorAttributeName])\n+            [_typingAttributes setObject:[self textColor] forKey:CPForegroundColorAttributeName];\n+    }\n+\n+    [[CPNotificationCenter defaultCenter] postNotificationName:CPTextViewDidChangeTypingAttributesNotification\n+                                          object:self];\n+}\n+\n+- (CPDictionary)typingAttributes\n+{\n+    return _typingAttributes;\n+}\n+\n+- (void)setSelectedTextAttributes:(CPDictionary)attributes\n+{\n+    _selectedTextAttributes = attributes;\n+}\n+\n+- (CPDictionary)selectedTextAttributes\n+{\n+    return _selectedTextAttributes;\n+}\n+\n+- (void)delete:(id)sender\n+{\n+    [self deleteBackward:sender];\n+}\n+\n+- (CPString)stringValue\n+{\n+    return _textStorage._string;\n+}\n+\n+// fixme: rich text should return attributed string, shouldn't it?\n+- (CPString)objectValue\n+{\n+    return [self stringValue];\n+}\n+\n+- (void)setFont:(CPFont)font\n+{\n+    _font = font;\n+\n+    var length = [_layoutManager numberOfCharacters];\n+\n+    if (length)\n+    {\n+        [_textStorage addAttribute:CPFontAttributeName value:_font range:CPMakeRange(0, length)];\n+        [_textStorage setFont:_font];\n+        [self scrollRangeToVisible:CPMakeRange(length, 0)];\n+    }\n+}\n+\n+- (void)setFont:(CPFont)font range:(CPRange)range\n+{\n+    if (!_isRichText)\n+    {\n+        _font = font;\n+        [_textStorage setFont:_font];\n+    }\n+\n+    [_textStorage addAttribute:CPFontAttributeName value:font range:CPMakeRangeCopy(range)];\n+    [_layoutManager _validateLayoutAndGlyphs];\n+    [self scrollRangeToVisible:CPMakeRange(CPMaxRange(range), 0)];\n+}\n+\n+- (CPFont)font\n+{\n+    return _font;\n+}\n+\n+- (void)changeColor:(id)sender\n+{\n+    [self setTextColor:[sender color] range:_selectionRange];\n+}\n+\n+- (void)changeFont:(id)sender\n+{\n+    var currRange = CPMakeRange(_selectionRange.location, 0),\n+        oldFont,\n+        attributes,\n+        scrollRange = CPMakeRange(CPMaxRange(_selectionRange), 0);\n+\n+    if (_isRichText)\n+    {\n+        if (!CPEmptyRange(_selectionRange))\n+        {\n+            while (CPMaxRange(currRange) < CPMaxRange(_selectionRange))  // iterate all \"runs\"\n+            {\n+                attributes = [_textStorage attributesAtIndex:CPMaxRange(currRange)\n+                                       longestEffectiveRange:currRange\n+                                                     inRange:_selectionRange];\n+                oldFont = [attributes objectForKey:CPFontAttributeName] || [self font];\n+                [self setFont:[sender convertFont:oldFont] range:currRange];\n+            }\n+        }\n+        else\n+        {\n+            [_typingAttributes setObject:[sender selectedFont] forKey:CPFontAttributeName];\n+        }\n+    }\n+    else\n+    {\n+        oldFont = [self font];\n+\n+        var length = [_textStorage length];\n+\n+        [self setFont:[sender convertFont:oldFont] range:CPMakeRange(0,length)];\n+        scrollRange = CPMakeRange(length, 0);\n+    }\n+\n+    [_layoutManager _validateLayoutAndGlyphs];\n+    [self sizeToFit];\n+    [self setNeedsDisplay:YES];\n+    [self scrollRangeToVisible:scrollRange];\n+}\n+\n+- (void)underline:(id)sender\n+{\n+    if (![self shouldChangeTextInRange:_selectionRange replacementString:nil])\n+        return;\n+\n+    if (!CPEmptyRange(_selectionRange))\n+    {\n+        var attrib = [_textStorage attributesAtIndex:_selectionRange.location effectiveRange:nil];\n+\n+        if ([attrib containsKey:CPUnderlineStyleAttributeName] && [[attrib objectForKey:CPUnderlineStyleAttributeName] intValue])\n+            [_textStorage removeAttribute:CPUnderlineStyleAttributeName range:_selectionRange];\n+        else\n+            [_textStorage addAttribute:CPUnderlineStyleAttributeName value:[CPNumber numberWithInt:1] range:CPMakeRangeCopy(_selectionRange)];\n+    }\n+    else\n+    {\n+        if ([_typingAttributes containsKey:CPUnderlineStyleAttributeName] && [[_typingAttributes  objectForKey:CPUnderlineStyleAttributeName] intValue])\n+            [_typingAttributes setObject:[CPNumber numberWithInt:0] forKey:CPUnderlineStyleAttributeName];\n+        else\n+            [_typingAttributes setObject:[CPNumber numberWithInt:1] forKey:CPUnderlineStyleAttributeName];\n+    }\n+}\n+\n+- (CPSelectionAffinity)selectionAffinity\n+{\n+    return 0;\n+}\n+\n+- (void)setTextColor:(CPColor)aColor\n+{\n+    _textColor = aColor;\n+\n+    if (_textColor)\n+        [_textStorage addAttribute:CPForegroundColorAttributeName value:_textColor range:CPMakeRange(0, [_layoutManager numberOfCharacters])];\n+    else\n+        [_textStorage removeAttribute:CPForegroundColorAttributeName range:CPMakeRange(0, [_layoutManager numberOfCharacters])];\n+\n+    [_layoutManager _validateLayoutAndGlyphs];\n+    [self scrollRangeToVisible:CPMakeRange([_layoutManager numberOfCharacters], 0)];\n+}\n+\n+- (void)setTextColor:(CPColor)aColor range:(CPRange)range\n+{\n+    if (!_isRichText)  // FIXME\n+        return;\n+\n+    if (!CPEmptyRange(_selectionRange))\n+    {\n+        if (aColor)\n+            [_textStorage addAttribute:CPForegroundColorAttributeName value:aColor range:CPMakeRangeCopy(range)];\n+        else\n+            [_textStorage removeAttribute:CPForegroundColorAttributeName range:CPMakeRangeCopy(range)];\n+    }\n+    else\n+    {\n+        [_typingAttributes setObject:aColor forKey:CPForegroundColorAttributeName];\n+    }\n+\n+    [_layoutManager _validateLayoutAndGlyphs];\n+    [self setNeedsDisplay:YES];\n+    [self scrollRangeToVisible:CPMakeRange(CPMaxRange(range), 0)];\n+}\n+\n+- (CPColor)textColor\n+{\n+    return _textColor;\n+}\n+\n+- (BOOL)isRichText\n+{\n+    return _isRichText;\n+}\n+\n+- (BOOL)isRulerVisible\n+{\n+    return NO;\n+}\n+\n+- (CPRange)selectedRange\n+{\n+    return _selectionRange;\n+}\n+\n+- (void)replaceCharactersInRange:(CPRange)aRange withString:(CPString)aString\n+{\n+    [_textStorage replaceCharactersInRange:aRange withString:aString];\n+}\n+\n+- (CPString)string\n+{\n+    return [_textStorage string];\n+}\n+\n+- (BOOL)isHorizontallyResizable\n+{\n+    return _isHorizontallyResizable;\n+}\n+\n+- (void)setHorizontallyResizable:(BOOL)flag\n+{\n+    _isHorizontallyResizable = flag;\n+}\n+\n+- (BOOL)isVerticallyResizable\n+{\n+    return _isVerticallyResizable;\n+}\n+\n+- (void)setVerticallyResizable:(BOOL)flag\n+{\n+    _isVerticallyResizable = flag;\n+}\n+\n+- (CGSize)maxSize\n+{\n+    return _maxSize;\n+}\n+\n+- (CGSize)minSize\n+{\n+    return _minSize;\n+}\n+\n+- (void)setMaxSize:(CGSize)aSize\n+{\n+    _maxSize = aSize;\n+}\n+\n+- (void)setMinSize:(CGSize)aSize\n+{\n+    _minSize = aSize;\n+}\n+\n+- (void)setConstrainedFrameSize:(CGSize)desiredSize\n+{\n+    [self setFrameSize:desiredSize];\n+}\n+\n+- (void)sizeToFit\n+{\n+    [self setFrameSize:[self frameSize]]\n+","path":"AppKit/CPTextView/CPTextView.j","position":1572,"original_position":1572,"commit_id":"0b2b29b7cfba704ec57871f7e2cb2e61c3d9b386","original_commit_id":"0b2b29b7cfba704ec57871f7e2cb2e61c3d9b386","user":{"login":"primalmotion","id":208711,"avatar_url":"https://avatars.githubusercontent.com/u/208711?","gravatar_id":"3acffc83399de56ab6297f9ccd030916","url":"https://api.github.com/users/primalmotion","html_url":"https://github.com/primalmotion","followers_url":"https://api.github.com/users/primalmotion/followers","following_url":"https://api.github.com/users/primalmotion/following{/other_user}","gists_url":"https://api.github.com/users/primalmotion/gists{/gist_id}","starred_url":"https://api.github.com/users/primalmotion/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/primalmotion/subscriptions","organizations_url":"https://api.github.com/users/primalmotion/orgs","repos_url":"https://api.github.com/users/primalmotion/repos","events_url":"https://api.github.com/users/primalmotion/events{/privacy}","received_events_url":"https://api.github.com/users/primalmotion/received_events","type":"User","site_admin":false},"body":"useless line, missing `;`","created_at":"2014-07-19T02:07:57Z","updated_at":"2014-07-19T02:07:57Z","html_url":"https://github.com/cappuccino/cappuccino/pull/2068#discussion_r15141338","pull_request_url":"https://api.github.com/repos/cappuccino/cappuccino/pulls/2068","_links":{"self":{"href":"https://api.github.com/repos/cappuccino/cappuccino/pulls/comments/15141338"},"html":{"href":"https://github.com/cappuccino/cappuccino/pull/2068#discussion_r15141338"},"pull_request":{"href":"https://api.github.com/repos/cappuccino/cappuccino/pulls/2068"}}},"pull_request":{"url":"https://api.github.com/repos/cappuccino/cappuccino/pulls/2068","id":12388140,"html_url":"https://github.com/cappuccino/cappuccino/pull/2068","diff_url":"https://github.com/cappuccino/cappuccino/pull/2068.diff","patch_url":"https://github.com/cappuccino/cappuccino/pull/2068.patch","issue_url":"https://api.github.com/repos/cappuccino/cappuccino/issues/2068","number":2068,"state":"open","title":"New: CPTextView, CPTextStorage, CPTextContainer, CPLayoutManager and CPSimpleTypesetter + CPFontPanel + RTF parser/producer [+1]","user":{"login":"daboe01","id":146335,"avatar_url":"https://avatars.githubusercontent.com/u/146335?","gravatar_id":"c8058b8db73e4702f0ab2141beae5d7a","url":"https://api.github.com/users/daboe01","html_url":"https://github.com/daboe01","followers_url":"https://api.github.com/users/daboe01/followers","following_url":"https://api.github.com/users/daboe01/following{/other_user}","gists_url":"https://api.github.com/users/daboe01/gists{/gist_id}","starred_url":"https://api.github.com/users/daboe01/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/daboe01/subscriptions","organizations_url":"https://api.github.com/users/daboe01/orgs","repos_url":"https://api.github.com/users/daboe01/repos","events_url":"https://api.github.com/users/daboe01/events{/privacy}","received_events_url":"https://api.github.com/users/daboe01/received_events","type":"User","site_admin":false},"body":"This is an implementation of the cocoa text system for cappuccino. It is based on the (long-dead) cappuccino-fork from http://github.com/emaillard/cappuccino. I eventually re-wrote most of the code and i.e. replaced canvas-drawing with DOM-spans to address the immanent performance and rendering-quality issues. In my testing the code is currently fully functional. However, Cappuccino still needs two critical mods to make it work:\r\n\r\n* Merge in PR #2045 (CPTextView will crash without it)\r\n* Pasteboard support is broken in Safari. This has to be worked around somehow to make it useful here.\r\n\r\nfixes issue: #1432","created_at":"2014-02-10T20:16:38Z","updated_at":"2014-07-19T02:07:57Z","closed_at":null,"merged_at":null,"merge_commit_sha":"7143f9da4b9232b447c686097fdab02b6ec5ee94","assignee":null,"milestone":{"url":"https://api.github.com/repos/cappuccino/cappuccino/milestones/6","labels_url":"https://api.github.com/repos/cappuccino/cappuccino/milestones/6/labels","id":508942,"number":6,"title":"0.9.8","description":"","creator":{"login":"aljungberg","id":154423,"avatar_url":"https://avatars.githubusercontent.com/u/154423?","gravatar_id":"c84a878bf7946b8903f83595142540e1","url":"https://api.github.com/users/aljungberg","html_url":"https://github.com/aljungberg","followers_url":"https://api.github.com/users/aljungberg/followers","following_url":"https://api.github.com/users/aljungberg/following{/other_user}","gists_url":"https://api.github.com/users/aljungberg/gists{/gist_id}","starred_url":"https://api.github.com/users/aljungberg/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aljungberg/subscriptions","organizations_url":"https://api.github.com/users/aljungberg/orgs","repos_url":"https://api.github.com/users/aljungberg/repos","events_url":"https://api.github.com/users/aljungberg/events{/privacy}","received_events_url":"https://api.github.com/users/aljungberg/received_events","type":"User","site_admin":false},"open_issues":2,"closed_issues":20,"state":"open","created_at":"2013-12-09T16:23:41Z","updated_at":"2014-07-19T01:42:49Z","due_on":"2014-05-31T07:00:00Z"},"commits_url":"https://api.github.com/repos/cappuccino/cappuccino/pulls/2068/commits","review_comments_url":"https://api.github.com/repos/cappuccino/cappuccino/pulls/2068/comments","review_comment_url":"https://api.github.com/repos/cappuccino/cappuccino/pulls/comments/{number}","comments_url":"https://api.github.com/repos/cappuccino/cappuccino/issues/2068/comments","statuses_url":"https://api.github.com/repos/cappuccino/cappuccino/statuses/0b2b29b7cfba704ec57871f7e2cb2e61c3d9b386","head":{"label":"daboe01:NEW--CPTextView","ref":"NEW--CPTextView","sha":"0b2b29b7cfba704ec57871f7e2cb2e61c3d9b386","user":{"login":"daboe01","id":146335,"avatar_url":"https://avatars.githubusercontent.com/u/146335?","gravatar_id":"c8058b8db73e4702f0ab2141beae5d7a","url":"https://api.github.com/users/daboe01","html_url":"https://github.com/daboe01","followers_url":"https://api.github.com/users/daboe01/followers","following_url":"https://api.github.com/users/daboe01/following{/other_user}","gists_url":"https://api.github.com/users/daboe01/gists{/gist_id}","starred_url":"https://api.github.com/users/daboe01/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/daboe01/subscriptions","organizations_url":"https://api.github.com/users/daboe01/orgs","repos_url":"https://api.github.com/users/daboe01/repos","events_url":"https://api.github.com/users/daboe01/events{/privacy}","received_events_url":"https://api.github.com/users/daboe01/received_events","type":"User","site_admin":false},"repo":{"id":7672771,"name":"cappuccino","full_name":"daboe01/cappuccino","owner":{"login":"daboe01","id":146335,"avatar_url":"https://avatars.githubusercontent.com/u/146335?","gravatar_id":"c8058b8db73e4702f0ab2141beae5d7a","url":"https://api.github.com/users/daboe01","html_url":"https://github.com/daboe01","followers_url":"https://api.github.com/users/daboe01/followers","following_url":"https://api.github.com/users/daboe01/following{/other_user}","gists_url":"https://api.github.com/users/daboe01/gists{/gist_id}","starred_url":"https://api.github.com/users/daboe01/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/daboe01/subscriptions","organizations_url":"https://api.github.com/users/daboe01/orgs","repos_url":"https://api.github.com/users/daboe01/repos","events_url":"https://api.github.com/users/daboe01/events{/privacy}","received_events_url":"https://api.github.com/users/daboe01/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/daboe01/cappuccino","description":"Web Application Framework in JavaScript and Objective-J","fork":true,"url":"https://api.github.com/repos/daboe01/cappuccino","forks_url":"https://api.github.com/repos/daboe01/cappuccino/forks","keys_url":"https://api.github.com/repos/daboe01/cappuccino/keys{/key_id}","collaborators_url":"https://api.github.com/repos/daboe01/cappuccino/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/daboe01/cappuccino/teams","hooks_url":"https://api.github.com/repos/daboe01/cappuccino/hooks","issue_events_url":"https://api.github.com/repos/daboe01/cappuccino/issues/events{/number}","events_url":"https://api.github.com/repos/daboe01/cappuccino/events","assignees_url":"https://api.github.com/repos/daboe01/cappuccino/assignees{/user}","branches_url":"https://api.github.com/repos/daboe01/cappuccino/branches{/branch}","tags_url":"https://api.github.com/repos/daboe01/cappuccino/tags","blobs_url":"https://api.github.com/repos/daboe01/cappuccino/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/daboe01/cappuccino/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/daboe01/cappuccino/git/refs{/sha}","trees_url":"https://api.github.com/repos/daboe01/cappuccino/git/trees{/sha}","statuses_url":"https://api.github.com/repos/daboe01/cappuccino/statuses/{sha}","languages_url":"https://api.github.com/repos/daboe01/cappuccino/languages","stargazers_url":"https://api.github.com/repos/daboe01/cappuccino/stargazers","contributors_url":"https://api.github.com/repos/daboe01/cappuccino/contributors","subscribers_url":"https://api.github.com/repos/daboe01/cappuccino/subscribers","subscription_url":"https://api.github.com/repos/daboe01/cappuccino/subscription","commits_url":"https://api.github.com/repos/daboe01/cappuccino/commits{/sha}","git_commits_url":"https://api.github.com/repos/daboe01/cappuccino/git/commits{/sha}","comments_url":"https://api.github.com/repos/daboe01/cappuccino/comments{/number}","issue_comment_url":"https://api.github.com/repos/daboe01/cappuccino/issues/comments/{number}","contents_url":"https://api.github.com/repos/daboe01/cappuccino/contents/{+path}","compare_url":"https://api.github.com/repos/daboe01/cappuccino/compare/{base}...{head}","merges_url":"https://api.github.com/repos/daboe01/cappuccino/merges","archive_url":"https://api.github.com/repos/daboe01/cappuccino/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/daboe01/cappuccino/downloads","issues_url":"https://api.github.com/repos/daboe01/cappuccino/issues{/number}","pulls_url":"https://api.github.com/repos/daboe01/cappuccino/pulls{/number}","milestones_url":"https://api.github.com/repos/daboe01/cappuccino/milestones{/number}","notifications_url":"https://api.github.com/repos/daboe01/cappuccino/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/daboe01/cappuccino/labels{/name}","releases_url":"https://api.github.com/repos/daboe01/cappuccino/releases{/id}","created_at":"2013-01-17T19:36:42Z","updated_at":"2014-07-12T12:11:58Z","pushed_at":"2014-07-13T18:12:55Z","git_url":"git://github.com/daboe01/cappuccino.git","ssh_url":"git@github.com:daboe01/cappuccino.git","clone_url":"https://github.com/daboe01/cappuccino.git","svn_url":"https://github.com/daboe01/cappuccino","homepage":"http://cappuccino-project.org","size":44332,"stargazers_count":1,"watchers_count":1,"language":"Objective-J","has_issues":false,"has_downloads":true,"has_wiki":true,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":1,"default_branch":"master"}},"base":{"label":"cappuccino:master","ref":"master","sha":"1848f96efae81711fec453ab2b1b39a51662d5f1","user":{"login":"cappuccino","id":16595,"avatar_url":"https://avatars.githubusercontent.com/u/16595?","gravatar_id":"f9e1c6f80c05348751a7a0879f0090c5","url":"https://api.github.com/users/cappuccino","html_url":"https://github.com/cappuccino","followers_url":"https://api.github.com/users/cappuccino/followers","following_url":"https://api.github.com/users/cappuccino/following{/other_user}","gists_url":"https://api.github.com/users/cappuccino/gists{/gist_id}","starred_url":"https://api.github.com/users/cappuccino/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cappuccino/subscriptions","organizations_url":"https://api.github.com/users/cappuccino/orgs","repos_url":"https://api.github.com/users/cappuccino/repos","events_url":"https://api.github.com/users/cappuccino/events{/privacy}","received_events_url":"https://api.github.com/users/cappuccino/received_events","type":"Organization","site_admin":false},"repo":{"id":49107,"name":"cappuccino","full_name":"cappuccino/cappuccino","owner":{"login":"cappuccino","id":16595,"avatar_url":"https://avatars.githubusercontent.com/u/16595?","gravatar_id":"f9e1c6f80c05348751a7a0879f0090c5","url":"https://api.github.com/users/cappuccino","html_url":"https://github.com/cappuccino","followers_url":"https://api.github.com/users/cappuccino/followers","following_url":"https://api.github.com/users/cappuccino/following{/other_user}","gists_url":"https://api.github.com/users/cappuccino/gists{/gist_id}","starred_url":"https://api.github.com/users/cappuccino/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cappuccino/subscriptions","organizations_url":"https://api.github.com/users/cappuccino/orgs","repos_url":"https://api.github.com/users/cappuccino/repos","events_url":"https://api.github.com/users/cappuccino/events{/privacy}","received_events_url":"https://api.github.com/users/cappuccino/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/cappuccino/cappuccino","description":"Web Application Framework in JavaScript and Objective-J","fork":false,"url":"https://api.github.com/repos/cappuccino/cappuccino","forks_url":"https://api.github.com/repos/cappuccino/cappuccino/forks","keys_url":"https://api.github.com/repos/cappuccino/cappuccino/keys{/key_id}","collaborators_url":"https://api.github.com/repos/cappuccino/cappuccino/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/cappuccino/cappuccino/teams","hooks_url":"https://api.github.com/repos/cappuccino/cappuccino/hooks","issue_events_url":"https://api.github.com/repos/cappuccino/cappuccino/issues/events{/number}","events_url":"https://api.github.com/repos/cappuccino/cappuccino/events","assignees_url":"https://api.github.com/repos/cappuccino/cappuccino/assignees{/user}","branches_url":"https://api.github.com/repos/cappuccino/cappuccino/branches{/branch}","tags_url":"https://api.github.com/repos/cappuccino/cappuccino/tags","blobs_url":"https://api.github.com/repos/cappuccino/cappuccino/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/cappuccino/cappuccino/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/cappuccino/cappuccino/git/refs{/sha}","trees_url":"https://api.github.com/repos/cappuccino/cappuccino/git/trees{/sha}","statuses_url":"https://api.github.com/repos/cappuccino/cappuccino/statuses/{sha}","languages_url":"https://api.github.com/repos/cappuccino/cappuccino/languages","stargazers_url":"https://api.github.com/repos/cappuccino/cappuccino/stargazers","contributors_url":"https://api.github.com/repos/cappuccino/cappuccino/contributors","subscribers_url":"https://api.github.com/repos/cappuccino/cappuccino/subscribers","subscription_url":"https://api.github.com/repos/cappuccino/cappuccino/subscription","commits_url":"https://api.github.com/repos/cappuccino/cappuccino/commits{/sha}","git_commits_url":"https://api.github.com/repos/cappuccino/cappuccino/git/commits{/sha}","comments_url":"https://api.github.com/repos/cappuccino/cappuccino/comments{/number}","issue_comment_url":"https://api.github.com/repos/cappuccino/cappuccino/issues/comments/{number}","contents_url":"https://api.github.com/repos/cappuccino/cappuccino/contents/{+path}","compare_url":"https://api.github.com/repos/cappuccino/cappuccino/compare/{base}...{head}","merges_url":"https://api.github.com/repos/cappuccino/cappuccino/merges","archive_url":"https://api.github.com/repos/cappuccino/cappuccino/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/cappuccino/cappuccino/downloads","issues_url":"https://api.github.com/repos/cappuccino/cappuccino/issues{/number}","pulls_url":"https://api.github.com/repos/cappuccino/cappuccino/pulls{/number}","milestones_url":"https://api.github.com/repos/cappuccino/cappuccino/milestones{/number}","notifications_url":"https://api.github.com/repos/cappuccino/cappuccino/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/cappuccino/cappuccino/labels{/name}","releases_url":"https://api.github.com/repos/cappuccino/cappuccino/releases{/id}","created_at":"2008-09-04T10:43:35Z","updated_at":"2014-07-18T14:30:28Z","pushed_at":"2014-07-19T01:42:50Z","git_url":"git://github.com/cappuccino/cappuccino.git","ssh_url":"git@github.com:cappuccino/cappuccino.git","clone_url":"https://github.com/cappuccino/cappuccino.git","svn_url":"https://github.com/cappuccino/cappuccino","homepage":"http://cappuccino-project.org","size":66766,"stargazers_count":2013,"watchers_count":2013,"language":"Objective-J","has_issues":true,"has_downloads":true,"has_wiki":true,"forks_count":328,"mirror_url":null,"open_issues_count":142,"forks":328,"open_issues":142,"watchers":2013,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/cappuccino/cappuccino/pulls/2068"},"html":{"href":"https://github.com/cappuccino/cappuccino/pull/2068"},"issue":{"href":"https://api.github.com/repos/cappuccino/cappuccino/issues/2068"},"comments":{"href":"https://api.github.com/repos/cappuccino/cappuccino/issues/2068/comments"},"review_comments":{"href":"https://api.github.com/repos/cappuccino/cappuccino/pulls/2068/comments"},"review_comment":{"href":"https://api.github.com/repos/cappuccino/cappuccino/pulls/comments/{number}"},"commits":{"href":"https://api.github.com/repos/cappuccino/cappuccino/pulls/2068/commits"},"statuses":{"href":"https://api.github.com/repos/cappuccino/cappuccino/statuses/0b2b29b7cfba704ec57871f7e2cb2e61c3d9b386"}}}}