@@ -94,6 +94,11 @@ final class CodeFileDocument: NSDocument, ObservableObject {
9494 isDocumentEditedSubject. eraseToAnyPublisher ( )
9595 }
9696
97+ /// A lock that ensures autosave scheduling happens correctly.
98+ private var autosaveTimerLock : NSLock = NSLock ( )
99+ /// Timer used to schedule autosave intervals.
100+ private var autosaveTimer : Timer ?
101+
97102 // MARK: - NSDocument
98103
99104 override static var autosavesInPlace : Bool {
@@ -130,6 +135,8 @@ final class CodeFileDocument: NSDocument, ObservableObject {
130135 }
131136 }
132137
138+ // MARK: - Data
139+
133140 override func data( ofType _: String ) throws -> Data {
134141 guard let sourceEncoding, let data = ( content? . string as NSString ? ) ? . data ( using: sourceEncoding. nsValue) else {
135142 Self . logger. error ( " Failed to encode contents to \( self . sourceEncoding. debugDescription) " )
@@ -138,6 +145,8 @@ final class CodeFileDocument: NSDocument, ObservableObject {
138145 return data
139146 }
140147
148+ // MARK: - Read
149+
141150 /// This function is used for decoding files.
142151 /// It should not throw error as unsupported files can still be opened by QLPreviewView.
143152 override func read( from data: Data , ofType _: String ) throws {
@@ -161,6 +170,8 @@ final class CodeFileDocument: NSDocument, ObservableObject {
161170 NotificationCenter . default. post ( name: Self . didOpenNotification, object: self )
162171 }
163172
173+ // MARK: - Autosave
174+
164175 /// Triggered when change occurred
165176 override func updateChangeCount( _ change: NSDocument . ChangeType ) {
166177 super. updateChangeCount ( change)
@@ -183,6 +194,31 @@ final class CodeFileDocument: NSDocument, ObservableObject {
183194 self . isDocumentEditedSubject. send ( self . isDocumentEdited)
184195 }
185196
197+ /// If ``hasUnautosavedChanges`` is `true` and an autosave has not already been scheduled, schedules a new autosave.
198+ /// If ``hasUnautosavedChanges`` is `false`, cancels any scheduled timers and returns.
199+ ///
200+ /// All operations are done with the ``autosaveTimerLock`` acquired (including the scheduled autosave) to ensure
201+ /// correct timing when scheduling or cancelling timers.
202+ override func scheduleAutosaving( ) {
203+ autosaveTimerLock. withLock {
204+ if self . hasUnautosavedChanges {
205+ guard autosaveTimer == nil else { return }
206+ autosaveTimer = Timer . scheduledTimer ( withTimeInterval: 2.0 , repeats: false ) { [ weak self] timer in
207+ self ? . autosaveTimerLock. withLock {
208+ guard timer. isValid else { return }
209+ self ? . autosaveTimer = nil
210+ self ? . autosave ( withDelegate: nil , didAutosave: nil , contextInfo: nil )
211+ }
212+ }
213+ } else {
214+ autosaveTimer? . invalidate ( )
215+ autosaveTimer = nil
216+ }
217+ }
218+ }
219+
220+ // MARK: - Close
221+
186222 override func close( ) {
187223 super. close ( )
188224 NotificationCenter . default. post ( name: Self . didCloseNotification, object: fileURL)
@@ -199,7 +235,7 @@ final class CodeFileDocument: NSDocument, ObservableObject {
199235 let directory = fileURL. deletingLastPathComponent ( )
200236 try FileManager . default. createDirectory ( at: directory, withIntermediateDirectories: true , attributes: nil )
201237
202- try data ( ofType : fileType ?? " " ) . write ( to : fileURL , options : . atomic )
238+ super . save ( sender )
203239 } catch {
204240 presentError ( error)
205241 }
0 commit comments