<commit_before>/*
 
 Document.swift
 
 CotEditor
 https://coteditor.com
 
 Created by nakamuxu on 2004-12-08.
 
 ------------------------------------------------------------------------------
 
 © 2004-2007 nakamuxu
 © 2014-2016 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
 
 http://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

extension Notification.Name {
    
    static let DocumentDidChangeEncoding = Notification.Name("DocumentDidChangeEncoding")
    static let DocumentDidChangeLineEnding = Notification.Name("DocumentDidChangeLineEnding")
    static let DocumentDidChangeSyntaxStyle = Notification.Name("DocumentDidChangeSyntaxStyle")
}


// constants

private let UniqueFileIDLength = 8

/// Maximal length to scan encoding declaration
private let MaxEncodingScanLength = 2000

private enum SerializationKey {
    static let readingEncoding = "readingEncoding"
    static let syntaxStyle = "syntaxStyle"
    static let autosaveIdentifier = "autosaveIdentifier"
}

// file extended attributes
private let NSFileExtendedAttributes = FileAttributeKey("NSFileExtendedAttributes")
private enum FileExtendedAttributeName {
    static let Encoding = "com.apple.TextEncoding"
    static let VerticalText = "com.coteditor.VerticalText"
}



// MARK:

final class Document: NSDocument, AdditionalDocumentPreparing, EncodingHolder {
    
    // MARK: Readonly Properties
    
    let textStorage = NSTextStorage()
    private(set) var encoding: String.Encoding
    private(set) var hasUTF8BOM = false
    private(set) var lineEnding: LineEnding
    private(set) var fileAttributes: [FileAttributeKey: Any]?
    private(set) var syntaxStyle: SyntaxStyle
    
    private(set) lazy var selection: TextSelection = TextSelection(document: self)
    private(set) lazy var analyzer: DocumentAnalyzer = DocumentAnalyzer(document: self)
    private(set) lazy var incompatibleCharacterScanner: IncompatibleCharacterScanner = IncompatibleCharacterScanner(document: self)
    
    
    // MARK: Private Properties
    
    private lazy var printPanelAccessoryController: PrintPanelAccessoryController = PrintPanelAccessoryController()
    @IBOutlet private weak var savePanelAccessoryView: NSView?
    
    private var readingEncoding: String.Encoding  // encoding to read document file
    private var needsShowUpdateAlertWithBecomeKey = false
    private var isExternalUpdateAlertShown = false
    private var fileHash: Data?  // MD5
    private var isVerticalText = false
    private var odbEventSender: ODBEventSender?
    private var shouldSaveXattr = true
    private var autosaveIdentifier: String
    private var suppressesIANACharsetConflictAlert = false
    private dynamic var isExecutable = false  // bind in save panel accessory view
    
    
    
    // MARK:
    // MARK: Lifecycle
    
    override init() {
        
        // [caution] This method may be called from a background thread due to concurrent-opening.
        
        let uuid = UUID().uuidString
        self.autosaveIdentifier = uuid.substring(to: uuid.index(uuid.startIndex, offsetBy: UniqueFileIDLength))
        
        self.encoding = String.Encoding(rawValue: Defaults[.encodingInNew])
        if self.encoding == .utf8 {
            self.hasUTF8BOM = Defaults[.saveUTF8BOM]
        }
        self.lineEnding = LineEnding(index: Defaults[.lineEndCharCode]) ?? .LF
        self.syntaxStyle = SyntaxManager.shared.style(name: Defaults[.syntaxStyle]) ?? SyntaxStyle()
        
        // set encoding to read file
        // -> The value is either user setting or selection of open panel.
        // -> This must be set before `readFromData:ofType:error:` is called.
        self.readingEncoding = (DocumentController.shared() as! DocumentController).accessorySelectedEncoding
        
        super.init()
        
        self.hasUndoManager = true
        
        // observe sytnax style update
        NotificationCenter.default.addObserver(self, selector: #selector(syntaxDidUpdate), name: .StyntaxDidUpdate, object: nil)
    }
    
    
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
    
    
    /// store internal document state
    override func encodeRestorableState(with coder: NSCoder) {
        
        coder.encode(Int(self.encoding.rawValue), forKey: SerializationKey.readingEncoding)
        coder.encode(self.autosaveIdentifier, forKey: SerializationKey.autosaveIdentifier)
        coder.encode(self.syntaxStyle.styleName, forKey: SerializationKey.syntaxStyle)
        
        super.encodeRestorableState(with: coder)
    }
    
    
    /// resume UI state
    override func restoreState(with coder: NSCoder) {
        
        super.restoreState(with: coder)
        
        if coder.containsValue(forKey: SerializationKey.readingEncoding) {
            self.readingEncoding = String.Encoding(rawValue: UInt(coder.decodeInteger(forKey: SerializationKey.readingEncoding)))
        }
        if let identifier = coder.decodeObject(forKey: SerializationKey.autosaveIdentifier) as? String {
            self.autosaveIdentifier = identifier
        }
        if let styleName = coder.decodeObject(forKey: SerializationKey.syntaxStyle) as? String {
            self.setSyntaxStyle(name: styleName)
        }
    }
    
    
    
    // MARK: Document Methods
    
    /// enable Autosave in Place
    override class func autosavesInPlace() -> Bool {
        
        return self._autosavesInPlace
    }<commit_msg>Add safetynet for encoding restoration<commit_after>/*
 
 Document.swift
 
 CotEditor
 https://coteditor.com
 
 Created by nakamuxu on 2004-12-08.
 
 ------------------------------------------------------------------------------
 
 © 2004-2007 nakamuxu
 © 2014-2016 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
 
 http://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

extension Notification.Name {
    
    static let DocumentDidChangeEncoding = Notification.Name("DocumentDidChangeEncoding")
    static let DocumentDidChangeLineEnding = Notification.Name("DocumentDidChangeLineEnding")
    static let DocumentDidChangeSyntaxStyle = Notification.Name("DocumentDidChangeSyntaxStyle")
}


// constants

private let UniqueFileIDLength = 8

/// Maximal length to scan encoding declaration
private let MaxEncodingScanLength = 2000

private enum SerializationKey {
    static let readingEncoding = "readingEncoding"
    static let syntaxStyle = "syntaxStyle"
    static let autosaveIdentifier = "autosaveIdentifier"
}

// file extended attributes
private let NSFileExtendedAttributes = FileAttributeKey("NSFileExtendedAttributes")
private enum FileExtendedAttributeName {
    static let Encoding = "com.apple.TextEncoding"
    static let VerticalText = "com.coteditor.VerticalText"
}



// MARK:

final class Document: NSDocument, AdditionalDocumentPreparing, EncodingHolder {
    
    // MARK: Readonly Properties
    
    let textStorage = NSTextStorage()
    private(set) var encoding: String.Encoding
    private(set) var hasUTF8BOM = false
    private(set) var lineEnding: LineEnding
    private(set) var fileAttributes: [FileAttributeKey: Any]?
    private(set) var syntaxStyle: SyntaxStyle
    
    private(set) lazy var selection: TextSelection = TextSelection(document: self)
    private(set) lazy var analyzer: DocumentAnalyzer = DocumentAnalyzer(document: self)
    private(set) lazy var incompatibleCharacterScanner: IncompatibleCharacterScanner = IncompatibleCharacterScanner(document: self)
    
    
    // MARK: Private Properties
    
    private lazy var printPanelAccessoryController: PrintPanelAccessoryController = PrintPanelAccessoryController()
    @IBOutlet private weak var savePanelAccessoryView: NSView?
    
    private var readingEncoding: String.Encoding  // encoding to read document file
    private var needsShowUpdateAlertWithBecomeKey = false
    private var isExternalUpdateAlertShown = false
    private var fileHash: Data?  // MD5
    private var isVerticalText = false
    private var odbEventSender: ODBEventSender?
    private var shouldSaveXattr = true
    private var autosaveIdentifier: String
    private var suppressesIANACharsetConflictAlert = false
    private dynamic var isExecutable = false  // bind in save panel accessory view
    
    
    
    // MARK:
    // MARK: Lifecycle
    
    override init() {
        
        // [caution] This method may be called from a background thread due to concurrent-opening.
        
        let uuid = UUID().uuidString
        self.autosaveIdentifier = uuid.substring(to: uuid.index(uuid.startIndex, offsetBy: UniqueFileIDLength))
        
        self.encoding = String.Encoding(rawValue: Defaults[.encodingInNew])
        if self.encoding == .utf8 {
            self.hasUTF8BOM = Defaults[.saveUTF8BOM]
        }
        self.lineEnding = LineEnding(index: Defaults[.lineEndCharCode]) ?? .LF
        self.syntaxStyle = SyntaxManager.shared.style(name: Defaults[.syntaxStyle]) ?? SyntaxStyle()
        
        // set encoding to read file
        // -> The value is either user setting or selection of open panel.
        // -> This must be set before `readFromData:ofType:error:` is called.
        self.readingEncoding = (DocumentController.shared() as! DocumentController).accessorySelectedEncoding
        
        super.init()
        
        self.hasUndoManager = true
        
        // observe sytnax style update
        NotificationCenter.default.addObserver(self, selector: #selector(syntaxDidUpdate), name: .StyntaxDidUpdate, object: nil)
    }
    
    
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
    
    
    /// store internal document state
    override func encodeRestorableState(with coder: NSCoder) {
        
        coder.encode(Int(self.encoding.rawValue), forKey: SerializationKey.readingEncoding)
        coder.encode(self.autosaveIdentifier, forKey: SerializationKey.autosaveIdentifier)
        coder.encode(self.syntaxStyle.styleName, forKey: SerializationKey.syntaxStyle)
        
        super.encodeRestorableState(with: coder)
    }
    
    
    /// resume UI state
    override func restoreState(with coder: NSCoder) {
        
        super.restoreState(with: coder)
        
        if coder.containsValue(forKey: SerializationKey.readingEncoding) {
            let encoding = String.Encoding(rawValue: UInt(coder.decodeInteger(forKey: SerializationKey.readingEncoding)))
            if String.availableStringEncodings.contains(encoding) {
                self.readingEncoding = encoding
            }
        }
        if let identifier = coder.decodeObject(forKey: SerializationKey.autosaveIdentifier) as? String {
            self.autosaveIdentifier = identifier
        }
        if let styleName = coder.decodeObject(forKey: SerializationKey.syntaxStyle) as? String {
            self.setSyntaxStyle(name: styleName)
        }
    }
    
    
    
    // MARK: Document Methods
    
    /// enable Autosave in Place
    override class func autosavesInPlace() -> Bool {
        
        return self._autosavesInPlace
    }
{'fraction_non_alphanumeric': 0.08401038612949158, 'fraction_numerical': 0.007035765139458916, 'mean_word_length': 2.973377703826955, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 9, 'https://': 2, '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': '2208173', 'n_tokens_mistral': 3248, 'n_tokens_neox': 3051, 'n_words': 879}