diff --git a/.swiftlint.yml b/.swiftlint.yml index 29e050973..46f0ec7a4 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -68,6 +68,7 @@ disabled_rules: - force_cast - force_try - opening_brace + - static_over_final_class - switch_case_alignment - trailing_comma - unneeded_override diff --git a/CHANGELOG.md b/CHANGELOG.md index e00cb93f6..5f2804652 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,10 @@ 4.8.6 (unreleased) -------------------------- +### Fixes + +- Fix a trivial memory leak in the line ending menu (thanks to Yoshimasa Niwa). + 4.8.5 (653) diff --git a/CotEditor/Sources/DispatchQueue.swift b/CotEditor/Sources/DispatchQueue.swift index 559d49723..d89ef6c51 100644 --- a/CotEditor/Sources/DispatchQueue.swift +++ b/CotEditor/Sources/DispatchQueue.swift @@ -8,7 +8,7 @@ // // --------------------------------------------------------------------------- // -// © 2016-2023 1024jp +// © 2016-2024 1024jp // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -31,7 +31,7 @@ extension DispatchQueue { /// Synchronously but thread-safely invokes passed-in block on main thread to avoid deadlock. /// /// - Parameter block: The block that contains the work to perform. - final class func syncOnMain(execute block: () -> Void) { + static func syncOnMain(execute block: () -> Void) { if Thread.isMainThread { block() @@ -46,7 +46,7 @@ extension DispatchQueue { /// /// - Parameter work: The work item containing the work to perform. /// - Returns: The return value of the item in the work parameter. - final class func syncOnMain(execute work: () throws -> T) rethrows -> T { + static func syncOnMain(execute work: () throws -> T) rethrows -> T { if Thread.isMainThread { try work() diff --git a/CotEditor/Sources/DocumentViewController.swift b/CotEditor/Sources/DocumentViewController.swift index 3ef799cae..69af64d8b 100644 --- a/CotEditor/Sources/DocumentViewController.swift +++ b/CotEditor/Sources/DocumentViewController.swift @@ -398,6 +398,8 @@ final class DocumentViewController: NSSplitViewController, ThemeChanging, NSTool /// Invoked when the text was edited (invoked right **before** notifying layout managers). override func textStorageDidProcessEditing(_ notification: Notification) { + assert(Thread.isMainThread) + let textStorage = notification.object as! NSTextStorage guard diff --git a/CotEditor/Sources/KeyBindingManager.swift b/CotEditor/Sources/KeyBindingManager.swift index 3750e0274..05a2adbd2 100644 --- a/CotEditor/Sources/KeyBindingManager.swift +++ b/CotEditor/Sources/KeyBindingManager.swift @@ -274,7 +274,7 @@ final class KeyBindingManager { /// /// - Parameter menu: The menu where to collect key bindings. /// - Returns: An array of KeyBindings. - private class func scanKeyBindings(in menu: NSMenu) -> [KeyBinding] { + private static func scanKeyBindings(in menu: NSMenu) -> [KeyBinding] { menu.items .filter(Self.allowsModifying) diff --git a/CotEditor/Sources/OptionalMenu.swift b/CotEditor/Sources/OptionalMenu.swift index c19a7510e..7c1a6980c 100644 --- a/CotEditor/Sources/OptionalMenu.swift +++ b/CotEditor/Sources/OptionalMenu.swift @@ -8,7 +8,7 @@ // // --------------------------------------------------------------------------- // -// © 2022-2023 1024jp +// © 2022-2024 1024jp // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -59,9 +59,11 @@ final class OptionalMenu: NSMenu, NSMenuDelegate { func menuWillOpen(_ menu: NSMenu) { self.update() // UI validation is performed here - self.validateKeyEvent(force: true) + self.validateKeyEvent(forcibly: true) - let timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(validateKeyEvent), userInfo: nil, repeats: true) + let timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] _ in + self?.validateKeyEvent() + } RunLoop.current.add(timer, forMode: .eventTracking) self.trackingTimer = timer } @@ -78,12 +80,12 @@ final class OptionalMenu: NSMenu, NSMenuDelegate { /// Checks the state of the modifier key press and update the item visibility. /// - /// - Parameter force: Whether forcing to update the item visibility. - @objc private func validateKeyEvent(force: Bool = false) { + /// - Parameter forcibly: Whether forcing to update the item visibility. + @objc private func validateKeyEvent(forcibly: Bool = false) { let shows = NSEvent.modifierFlags.contains(.option) - guard force || shows != self.isShowingOptionalItems else { return } + guard forcibly || shows != self.isShowingOptionalItems else { return } self.updateOptionalItems(shows: shows) } diff --git a/CotEditor/Sources/PrintPanelAccessoryController.swift b/CotEditor/Sources/PrintPanelAccessoryController.swift index dc6234fc7..b6bea991a 100644 --- a/CotEditor/Sources/PrintPanelAccessoryController.swift +++ b/CotEditor/Sources/PrintPanelAccessoryController.swift @@ -480,7 +480,7 @@ private extension PrintInfoType { let item = NSMenuItem() item.title = self.label - item.tag = Self.allCases.enumerated().first { $0.element == self }?.offset ?? 0 + item.tag = Self.allCases.firstIndex(of: self) ?? 0 return item } } diff --git a/CotEditor/Sources/String+Normalization.swift b/CotEditor/Sources/String+Normalization.swift index 06347dc93..10627d23f 100644 --- a/CotEditor/Sources/String+Normalization.swift +++ b/CotEditor/Sources/String+Normalization.swift @@ -121,7 +121,7 @@ enum UnicodeNormalizationForm: String, CaseIterable { /// Unique identifier for menu item. var tag: Int { - Self.allCases.enumerated().first { $0.element == self }!.offset + Self.allCases.firstIndex(of: self)! } } diff --git a/CotEditor/Sources/SyntaxEditView.swift b/CotEditor/Sources/SyntaxEditView.swift index b3a7614b9..bf41e5c9e 100644 --- a/CotEditor/Sources/SyntaxEditView.swift +++ b/CotEditor/Sources/SyntaxEditView.swift @@ -64,7 +64,7 @@ struct SyntaxEditView: View { weak var parent: NSHostingController? - @MainActor private static var viewSize = CGSize(width: 680, height: 500) + @MainActor private static var viewSize = CGSize(width: 680, height: 525) @State private var name: String = "" @State private var message: String? @@ -184,7 +184,7 @@ struct SyntaxEditView: View { } } } - .frame(idealWidth: Self.viewSize.width, minHeight: 500, idealHeight: Self.viewSize.height) + .frame(idealWidth: Self.viewSize.width, minHeight: 525, idealHeight: Self.viewSize.height) } diff --git a/CotEditor/Sources/UserUnixTask.swift b/CotEditor/Sources/UserUnixTask.swift index 8a45e354f..c2e4ca8ad 100644 --- a/CotEditor/Sources/UserUnixTask.swift +++ b/CotEditor/Sources/UserUnixTask.swift @@ -98,7 +98,7 @@ actor UserUnixTask { async let data = buffer.reduce(into: Data()) { $0 += $1 } - return String(data: await data, encoding: .utf8) + return String(decoding: await data, as: UTF8.self) } } @@ -106,13 +106,11 @@ actor UserUnixTask { /// The standard error. var error: String? { - guard - let data = try? self.errorPipe.fileHandleForReading.readToEnd(), - let string = String(data: data, encoding: .utf8), - !string.isEmpty - else { return nil } + guard let data = try? self.errorPipe.fileHandleForReading.readToEnd() else { return nil } - return string + let string = String(decoding: data, as: UTF8.self) + + return string.isEmpty ? nil : string } } diff --git a/Tests/EncodingDetectionTests.swift b/Tests/EncodingDetectionTests.swift index 0dc463a33..7c6e80b0a 100644 --- a/Tests/EncodingDetectionTests.swift +++ b/Tests/EncodingDetectionTests.swift @@ -37,7 +37,7 @@ final class EncodingDetectionTests: XCTestCase { // -> String(data:encoding:) preserves BOM since Swift 5 (2019-03) // cf. https://bugs.swift.org/browse/SR-10173 let data = try self.dataForFileName("UTF-8 BOM") - XCTAssertEqual(String(data: data, encoding: .utf8), "\u{FEFF}0") + XCTAssertEqual(String(decoding: data, as: UTF8.self), "\u{FEFF}0") XCTAssertEqual(String(bomCapableData: data, encoding: .utf8), "0") var encoding: String.Encoding?