<commit_before>//
//  ActiveLabel.swift
//  ActiveLabel
//
//  Created by <NAME> on 9/4/15.
//  Copyright © 2015 Optonaut. All rights reserved.
//

import Foundation
import UIKit

public class ActiveLabel: UILabel {
    
    // MARK: - public properties
    public var mentionEnabled: Bool = true {
        didSet {
            updateTextStorage()
        }
    }
    public var hashtagEnabled: Bool = true {
        didSet {
            updateTextStorage()
        }
    }
    public var URLEnabled: Bool = true {
        didSet {
            updateTextStorage()
        }
    }
    public var mentionColor: UIColor = .blueColor() {
        didSet {
            updateTextStorage()
        }
    }
    public var mentionSelectedColor: UIColor? {
        didSet {
            updateTextStorage()
        }
    }
    public var hashtagColor: UIColor = .blueColor() {
        didSet {
            updateTextStorage()
        }
    }
    public var hashtagSelectedColor: UIColor? {
        didSet {
            updateTextStorage()
        }
    }
    public var URLColor: UIColor = .blueColor() {
        didSet {
            updateTextStorage()
        }
    }
    public var URLSelectedColor: UIColor? {
        didSet {
            updateTextStorage()
        }
    }
    public var lineSpacing: Float? {
        didSet {
            updateTextStorage()
        }
    }
    
    // MARK: - public methods
    public func handleMentionTap(handler: (String) -> ()) {
        mentionTapHandler = handler
    }
    
    public func handleHashtagTap(handler: (String) -> ()) {
        hashtagTapHandler = handler
    }
    
    public func handleURLTap(handler: (NSURL) -> ()) {
        urlTapHandler = handler
    }
    
    // MARK: - override UILabel properties
    override public var text: String? {
        didSet {
            updateTextStorage()
        }
    }
    
    override public var attributedText: NSAttributedString? {
        didSet {
            updateTextStorage()
        }
    }
    
    override public var font: UIFont! {
        didSet {
            updateTextStorage()
        }
    }
    
    override public var textColor: UIColor! {
        didSet {
            updateTextStorage()
        }
    }
    
    // MARK: - init functions
    override public init(frame: CGRect) {
        super.init(frame: frame)
        
        setupLabel()
    }
    
    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        
        setupLabel()
    }
    
    public override func layoutSubviews() {
        super.layoutSubviews()
        
        textContainer.size = bounds.size
    }
    
    // MARK: - override touch events
    public override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
        let location = touches.first!.locationInView(self)
        
        selectedElement = elementAtLocation(location)
        updateAttributesWhenSelected(true)
    }
    
    public override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
        guard let selectedElement = selectedElement else {
            return
        }
        
        switch selectedElement.element {
        case .Mention(let userHandle): mentionTapHandler?(userHandle)
        case .Hashtag(let hashtag): hashtagTapHandler?(hashtag)
        case .URL(let url): urlTapHandler?(url)
        case .None: ()
        }
        
        let when = dispatch_time(DISPATCH_TIME_NOW, Int64(0.25 * Double(NSEC_PER_SEC)))
        dispatch_after(when, dispatch_get_main_queue()) {
            self.updateAttributesWhenSelected(false)
            self.selectedElement = nil
        }
    }
    
    public override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
        let location = touches.first!.locationInView(self)
        
        if let element = elementAtLocation(location) {
            if element.range.location != selectedElement?.range.location || element.range.length != selectedElement?.range.length {
                updateAttributesWhenSelected(false)
                selectedElement = element
                updateAttributesWhenSelected(true)
            }
        } else {
            updateAttributesWhenSelected(false)
            selectedElement = nil
        }
    }
    
    public override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
        updateAttributesWhenSelected(false)
        selectedElement = nil
    }
    
    public override func drawTextInRect(rect: CGRect) {
        let range = NSRange(location: 0, length: textStorage.length)
        
        layoutManager.drawBackgroundForGlyphRange(range, atPoint: CGPointZero)
        layoutManager.drawGlyphsForGlyphRange(range, atPoint: CGPointZero)
    }
    
    // MARK: - private properties
    private var mentionTapHandler: ((String) -> ())?
    private var hashtagTapHandler: ((String) -> ())?
    private var urlTapHandler: ((NSURL) -> ())?
    
    private var selectedElement: (range: NSRange, element: ActiveElement)?
    private lazy var textStorage = NSTextStorage()
    private lazy var layoutManager = NSLayoutManager()
    private lazy var textContainer = NSTextContainer()
    private lazy var activeElements: [ActiveType: [(range: NSRange, element: ActiveElement)]] = [
        .Mention: [],
        .Hashtag: [],
        .URL: [],
    ]
    
    // MARK: - helper functions
    private func setupLabel() {
        textStorage.addLayoutManager(layoutManager)
        layoutManager.addTextContainer(textContainer)
        textContainer.lineFragmentPadding = 0
        
        userInteractionEnabled = true
    }
    
    private func updateTextStorage() {
        guard let attributedText = attributedText else {
            return
        }
        
        // clean up previous active elements
        for (type, _) in activeElements {
            activeElements[type]?.removeAll()
        }
        
        guard attributedText.length > 0 else {
            return
        }
        <commit_msg>rewritten touch handling<commit_after>//
//  ActiveLabel.swift
//  ActiveLabel
//
//  Created by <NAME> on 9/4/15.
//  Copyright © 2015 Optonaut. All rights reserved.
//

import Foundation
import UIKit

public class ActiveLabel: UILabel {
    
    // MARK: - public properties
    public var mentionEnabled: Bool = true {
        didSet {
            updateTextStorage()
        }
    }
    public var hashtagEnabled: Bool = true {
        didSet {
            updateTextStorage()
        }
    }
    public var URLEnabled: Bool = true {
        didSet {
            updateTextStorage()
        }
    }
    public var mentionColor: UIColor = .blueColor() {
        didSet {
            updateTextStorage()
        }
    }
    public var mentionSelectedColor: UIColor? {
        didSet {
            updateTextStorage()
        }
    }
    public var hashtagColor: UIColor = .blueColor() {
        didSet {
            updateTextStorage()
        }
    }
    public var hashtagSelectedColor: UIColor? {
        didSet {
            updateTextStorage()
        }
    }
    public var URLColor: UIColor = .blueColor() {
        didSet {
            updateTextStorage()
        }
    }
    public var URLSelectedColor: UIColor? {
        didSet {
            updateTextStorage()
        }
    }
    public var lineSpacing: Float? {
        didSet {
            updateTextStorage()
        }
    }
    
    // MARK: - public methods
    public func handleMentionTap(handler: (String) -> ()) {
        mentionTapHandler = handler
    }
    
    public func handleHashtagTap(handler: (String) -> ()) {
        hashtagTapHandler = handler
    }
    
    public func handleURLTap(handler: (NSURL) -> ()) {
        urlTapHandler = handler
    }
    
    // MARK: - override UILabel properties
    override public var text: String? {
        didSet {
            updateTextStorage()
        }
    }
    
    override public var attributedText: NSAttributedString? {
        didSet {
            updateTextStorage()
        }
    }
    
    override public var font: UIFont! {
        didSet {
            updateTextStorage()
        }
    }
    
    override public var textColor: UIColor! {
        didSet {
            updateTextStorage()
        }
    }
    
    // MARK: - init functions
    override public init(frame: CGRect) {
        super.init(frame: frame)
        
        setupLabel()
    }
    
    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        
        setupLabel()
    }
    
    // MARK: - layout functions
    public override func layoutSubviews() {
        super.layoutSubviews()
        
        textContainer.size = bounds.size
    }
    
    public override func drawTextInRect(rect: CGRect) {
        let range = NSRange(location: 0, length: textStorage.length)
        
        layoutManager.drawBackgroundForGlyphRange(range, atPoint: CGPointZero)
        layoutManager.drawGlyphsForGlyphRange(range, atPoint: CGPointZero)
    }
    
    // MARK: - touch events
    func onTouch(gesture: UILongPressGestureRecognizer) {
        let location = gesture.locationInView(self)
        
        switch gesture.state {
        case .Began, .Changed:
            if let element = elementAtLocation(location) {
                if element.range.location != selectedElement?.range.location || element.range.length != selectedElement?.range.length {
                    updateAttributesWhenSelected(false)
                    selectedElement = element
                    updateAttributesWhenSelected(true)
                }
            } else {
                updateAttributesWhenSelected(false)
                selectedElement = nil
            }
        case .Cancelled, .Ended:
            guard let selectedElement = selectedElement else {
                return
            }
            
            switch selectedElement.element {
            case .Mention(let userHandle): mentionTapHandler?(userHandle)
            case .Hashtag(let hashtag): hashtagTapHandler?(hashtag)
            case .URL(let url): urlTapHandler?(url)
            case .None: ()
            }
            
            let when = dispatch_time(DISPATCH_TIME_NOW, Int64(0.25 * Double(NSEC_PER_SEC)))
            dispatch_after(when, dispatch_get_main_queue()) {
                self.updateAttributesWhenSelected(false)
                self.selectedElement = nil
            }
        default: ()
        }
    }
    
    // MARK: - private properties
    private var mentionTapHandler: ((String) -> ())?
    private var hashtagTapHandler: ((String) -> ())?
    private var urlTapHandler: ((NSURL) -> ())?
    
    private var selectedElement: (range: NSRange, element: ActiveElement)?
    private lazy var textStorage = NSTextStorage()
    private lazy var layoutManager = NSLayoutManager()
    private lazy var textContainer = NSTextContainer()
    private lazy var activeElements: [ActiveType: [(range: NSRange, element: ActiveElement)]] = [
        .Mention: [],
        .Hashtag: [],
        .URL: [],
    ]
    
    // MARK: - helper functions
    private func setupLabel() {
        textStorage.addLayoutManager(layoutManager)
        layoutManager.addTextContainer(textContainer)
        textContainer.lineFragmentPadding = 0
        
        let touchRecognizer = UILongPressGestureRecognizer(target: self, action: "onTouch:")
        touchRecognizer.minimumPressDuration = 0.00001
        addGestureRecognizer(touchRecognizer)
        
        userInteractionEnabled = true
    }
    
    private func updateTextStorage() {
        guard let attributedText = attributedText else {
            return
        }
        
        // clean up previous active elements
        for (type, _) in activeElements {
            activeElements[type]?.removeAll()
        }
        
        guard attributedText.length > 0 else {
            return
        }
        
{'fraction_non_alphanumeric': 0.08667898538551991, 'fraction_numerical': 0.003191668066521082, 'mean_word_length': 1.850610485994733, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6932997', 'n_tokens_mistral': 3276, 'n_tokens_neox': 3182, 'n_words': 722}