<commit_before>/*
 
 FindPanelFieldViewController.swift
 
 CotEditor
 https://coteditor.com
 
 Created by 1024jp on 2016-06-26.
 
 ------------------------------------------------------------------------------
 
 © 2014-2018 1024jp
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
 
 https://www.apache.org/licenses/LICENSE-2.0
 
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 
 */

import Cocoa

final class FindPanelFieldViewController: NSViewController, NSTextViewDelegate {
    
    // MARK: Private Properties
    
    @objc private dynamic let textFinder = TextFinder.shared
    
    private weak var currentResultMessageTarget: NSLayoutManager?  // grab layoutManager instead of NSTextView to use weak reference
    
    private lazy var regexReferenceViewController = DetachablePopoverViewController(nibName: NSNib.Name("RegexReferenceView"), bundle: nil)
    private lazy var preferencesViewController = NSViewController(nibName: NSNib.Name("FindPreferencesView"), bundle: nil)
    
    @IBOutlet private var findTextView: NSTextView?  // NSTextView cannot be weak
    @IBOutlet private var replacementTextView: NSTextView?  // NSTextView cannot be weak
    @IBOutlet private weak var findHistoryMenu: NSMenu?
    @IBOutlet private weak var replaceHistoryMenu: NSMenu?
    @IBOutlet private weak var findResultField: NSTextField?
    @IBOutlet private weak var replacementResultField: NSTextField?
    
    
    
    // MARK: -
    // MARK: Lifecycle
    
    deinit {
        UserDefaults.standard.removeObserver(self, forKeyPath: DefaultKeys.findHistory.rawValue)
        UserDefaults.standard.removeObserver(self, forKeyPath: DefaultKeys.replaceHistory.rawValue)
    }
    
    
    
    // MARK: View Controller Methods
    
    /// setup UI
    override func viewDidLoad() {
        
        super.viewDidLoad()
        
        self.updateFindHistoryMenu()
        self.updateReplaceHistoryMenu()
        
        // observe default change for the history menus
        UserDefaults.standard.addObserver(self, forKeyPath: DefaultKeys.findHistory.rawValue, context: nil)
        UserDefaults.standard.addObserver(self, forKeyPath: DefaultKeys.replaceHistory.rawValue, context: nil)
    }
    
    
    /// make find field initial first responder
    override func viewWillAppear() {
        
        super.viewWillAppear()
        
        // make find text view the initial first responder to focus it on showWindow(_:)
        self.view.window?.initialFirstResponder = self.findTextView
    }
    
    
    /// observed user defaults are changed
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
        
        guard let keyPath = keyPath else { return }
        
        switch keyPath {
        case DefaultKeys.findHistory.rawValue:
            self.updateFindHistoryMenu()
        case DefaultKeys.replaceHistory.rawValue:
            self.updateReplaceHistoryMenu()
        default: break
        }
    }
    
    
    
    // MARK: Text View Delegate
    
    /// find string did change
    func textDidChange(_ notification: Notification) {
        
        guard let textView = notification.object as? NSTextView else { return }
        
        switch textView {
        case self.findTextView!:
            self.clearNumberOfReplaced()
            self.clearNumberOfFound()
        case self.replacementTextView!:
            self.clearNumberOfReplaced()
        default:
            break
        }
    }
    
    
    /// selected range did update
    func textViewDidChangeSelection(_ notification: Notification) {
        
        guard let textView = notification.object as? NSTextView else { return }
        
        // highlight matching brace
        if UserDefaults.standard[.findUsesRegularExpression] {
            let pairs = [BracePair(begin: "(", end: ")"),
                         BracePair(begin: "[", end: "]")]
            <commit_msg>Highlight matching brace only in find string field (#807)<commit_after>/*
 
 FindPanelFieldViewController.swift
 
 CotEditor
 https://coteditor.com
 
 Created by 1024jp on 2016-06-26.
 
 ------------------------------------------------------------------------------
 
 © 2014-2018 1024jp
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
 
 https://www.apache.org/licenses/LICENSE-2.0
 
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 
 */

import Cocoa

final class FindPanelFieldViewController: NSViewController, NSTextViewDelegate {
    
    // MARK: Private Properties
    
    @objc private dynamic let textFinder = TextFinder.shared
    
    private weak var currentResultMessageTarget: NSLayoutManager?  // grab layoutManager instead of NSTextView to use weak reference
    
    private lazy var regexReferenceViewController = DetachablePopoverViewController(nibName: NSNib.Name("RegexReferenceView"), bundle: nil)
    private lazy var preferencesViewController = NSViewController(nibName: NSNib.Name("FindPreferencesView"), bundle: nil)
    
    @IBOutlet private var findTextView: NSTextView?  // NSTextView cannot be weak
    @IBOutlet private var replacementTextView: NSTextView?  // NSTextView cannot be weak
    @IBOutlet private weak var findHistoryMenu: NSMenu?
    @IBOutlet private weak var replaceHistoryMenu: NSMenu?
    @IBOutlet private weak var findResultField: NSTextField?
    @IBOutlet private weak var replacementResultField: NSTextField?
    
    
    
    // MARK: -
    // MARK: Lifecycle
    
    deinit {
        UserDefaults.standard.removeObserver(self, forKeyPath: DefaultKeys.findHistory.rawValue)
        UserDefaults.standard.removeObserver(self, forKeyPath: DefaultKeys.replaceHistory.rawValue)
    }
    
    
    
    // MARK: View Controller Methods
    
    /// setup UI
    override func viewDidLoad() {
        
        super.viewDidLoad()
        
        self.updateFindHistoryMenu()
        self.updateReplaceHistoryMenu()
        
        // observe default change for the history menus
        UserDefaults.standard.addObserver(self, forKeyPath: DefaultKeys.findHistory.rawValue, context: nil)
        UserDefaults.standard.addObserver(self, forKeyPath: DefaultKeys.replaceHistory.rawValue, context: nil)
    }
    
    
    /// make find field initial first responder
    override func viewWillAppear() {
        
        super.viewWillAppear()
        
        // make find text view the initial first responder to focus it on showWindow(_:)
        self.view.window?.initialFirstResponder = self.findTextView
    }
    
    
    /// observed user defaults are changed
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
        
        guard let keyPath = keyPath else { return }
        
        switch keyPath {
        case DefaultKeys.findHistory.rawValue:
            self.updateFindHistoryMenu()
        case DefaultKeys.replaceHistory.rawValue:
            self.updateReplaceHistoryMenu()
        default: break
        }
    }
    
    
    
    // MARK: Text View Delegate
    
    /// find string did change
    func textDidChange(_ notification: Notification) {
        
        guard let textView = notification.object as? NSTextView else { return }
        
        switch textView {
        case self.findTextView!:
            self.clearNumberOfReplaced()
            self.clearNumberOfFound()
        case self.replacementTextView!:
            self.clearNumberOfReplaced()
        default:
            break
        }
    }
    
    
    /// selected range did update
    func textViewDidChangeSelection(_ notification: Notification) {
        
        guard
            let textView = notification.object as? NSTextView,
            textView == self.findTextView
            else { return }
        
        // highlight matching brace
        if UserDefaults.standard[.findUsesRegularExpression] {
            let pairs = [BracePair(begin: "(", end: ")"),
                         BracePair(begin: "[", end: "]")]
            
{'fraction_non_alphanumeric': 0.08680437972683147, 'fraction_numerical': 0.006659893893215939, 'mean_word_length': 2.411628802464382, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 4, 'lorem ipsum': 0, 'www.': 2, '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': '1456189', 'n_tokens_mistral': 2339, 'n_tokens_neox': 2191, 'n_words': 682}