From ef44c4b8621b58dc7bbb3930ae765418fc418b1a Mon Sep 17 00:00:00 2001 From: groverlynn Date: Mon, 15 Jul 2024 01:49:25 +0200 Subject: [PATCH] update --- Squirrel.xcodeproj/project.pbxproj | 6 +- .../xcshareddata/xcschemes/Squirrel.xcscheme | 2 +- sources/RimeKeycode.swift | 2 +- sources/SquirrelApplicationDelegate.swift | 16 +- sources/SquirrelConfig.swift | 50 ++-- sources/SquirrelInputController.swift | 110 ++++---- sources/SquirrelInputSource.swift | 16 +- sources/SquirrelPanel.swift | 241 ++++++++---------- 8 files changed, 200 insertions(+), 243 deletions(-) diff --git a/Squirrel.xcodeproj/project.pbxproj b/Squirrel.xcodeproj/project.pbxproj index 192f4fc4b..c2d6ae3f5 100644 --- a/Squirrel.xcodeproj/project.pbxproj +++ b/Squirrel.xcodeproj/project.pbxproj @@ -588,7 +588,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 1540; + LastUpgradeCheck = 1600; TargetAttributes = { 8D1107260486CEB800E47090 = { LastSwiftMigration = 1530; @@ -804,7 +804,6 @@ C01FCF4F08A954540054247B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_APPICON_NAME = RimeIcon; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; @@ -909,7 +908,6 @@ C01FCF5008A954540054247B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_APPICON_NAME = RimeIcon; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; @@ -998,7 +996,7 @@ PRODUCT_MODULE_NAME = "$(PRODUCT_NAME)"; SDKROOT = macosx; STRINGS_FILE_OUTPUT_ENCODING = "UTF-8"; - SWIFT_COMPILATION_MODE = singlefile; + SWIFT_COMPILATION_MODE = wholemodule; SWIFT_INCLUDE_PATHS = /Library/Developer/Toolchains; SWIFT_OBJC_BRIDGING_HEADER = "sources/Squirrel-Bridging-Header.h"; SWIFT_OBJC_INTERFACE_HEADER_NAME = "$(SWIFT_MODULE_NAME)-Swift.h"; diff --git a/Squirrel.xcodeproj/xcshareddata/xcschemes/Squirrel.xcscheme b/Squirrel.xcodeproj/xcshareddata/xcschemes/Squirrel.xcscheme index d8b0e53fd..1f21223f5 100644 --- a/Squirrel.xcodeproj/xcshareddata/xcschemes/Squirrel.xcscheme +++ b/Squirrel.xcodeproj/xcshareddata/xcschemes/Squirrel.xcscheme @@ -1,6 +1,6 @@ Uppercase self.init(rawValue: CInt(keychar) - 0x20)!; return } diff --git a/sources/SquirrelApplicationDelegate.swift b/sources/SquirrelApplicationDelegate.swift index 0ea714bef..ee39b4eb8 100644 --- a/sources/SquirrelApplicationDelegate.swift +++ b/sources/SquirrelApplicationDelegate.swift @@ -131,7 +131,7 @@ final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUSta } func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { - if response.notification.request.identifier == Self.updaterIdentifier && response.actionIdentifier == UNNotificationDefaultActionIdentifier { + if response.notification.request.identifier == Self.updaterIdentifier, response.actionIdentifier == UNNotificationDefaultActionIdentifier { updateController.updater.checkForUpdates() } completionHandler() @@ -232,7 +232,7 @@ final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUSta } RimeApi.set_notification_handler(notificationHandler, bridge(obj: self)) var squirrelTraits: RimeTraits = RimeStructInit() - squirrelTraits.shared_data_dir = Bundle.main.sharedSupportURL!.withUnsafeFileSystemRepresentation(\.unsafelyUnwrapped) + squirrelTraits.shared_data_dir = Bundle.main.sharedSupportURL?.withUnsafeFileSystemRepresentation(\.unsafelyUnwrapped) squirrelTraits.user_data_dir = Self.userDataDir.withUnsafeFileSystemRepresentation(\.unsafelyUnwrapped) squirrelTraits.distribution_code_name = "Squirrel".utf8CString.withUnsafeBufferPointer(\.baseAddress) squirrelTraits.distribution_name = "鼠鬚管".utf8CString.withUnsafeBufferPointer(\.baseAddress) @@ -286,11 +286,11 @@ final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUSta } func loadSchemaSpecificSettings(schemaId: String, withRimeSession sessionId: RimeSessionId) { - guard !schemaId.isEmpty && !schemaId.hasPrefix(".") else { return } + guard !schemaId.isEmpty, !schemaId.hasPrefix(".") else { return } // update the list of switchers that change styles and color-themes let baseConfig = SquirrelConfig(.base) let schema = SquirrelConfig() - if schema.open(withSchemaId: schemaId, baseConfig: baseConfig) && schema.hasSection("style") { + if schema.open(withSchemaId: schemaId, baseConfig: baseConfig), schema.hasSection("style") { panel.optionSwitcher = schema.optionSwitcherForSchema() panel.optionSwitcher.update(withRimeSession: sessionId) panel.loadConfig(schema) @@ -308,7 +308,7 @@ final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUSta panel.loadLabelConfig(defaultConfig, directUpdate: true) } else { let schema = SquirrelConfig() - if schema.open(withSchemaId: schemaId, baseConfig: defaultConfig) && schema.hasSection("menu") { + if schema.open(withSchemaId: schemaId, baseConfig: defaultConfig), schema.hasSection("menu") { panel.loadLabelConfig(schema, directUpdate: false) } else { panel.loadLabelConfig(defaultConfig, directUpdate: false) @@ -356,7 +356,7 @@ final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUSta } func inputSourceChanged(_ notification: Notification) { - let inputSource: TISInputSource = TISCopyCurrentKeyboardInputSource().takeUnretainedValue() + let inputSource: TISInputSource = TISCopyCurrentKeyboardInputSource().takeRetainedValue() if let inputSourceID: CFString = bridge(ptr: TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceID)), !CFStringHasPrefix(inputSourceID, SquirrelApp.bundleId as CFString) { isCurrentInputMethod = false } @@ -372,7 +372,7 @@ private func showNotification(message: String) { } } center.getNotificationSettings { settings in - guard (settings.authorizationStatus == .authorized || settings.authorizationStatus == .provisional) && settings.alertSetting == .enabled else { return } + guard (settings.authorizationStatus == .authorized || settings.authorizationStatus == .provisional), settings.alertSetting == .enabled else { return } let content = UNMutableNotificationContent() content.title = Bundle.main.localizedString(forKey: "Squirrel", value: nil, table: "Notifications") content.subtitle = Bundle.main.localizedString(forKey: message, value: nil, table: "Notifications") @@ -424,7 +424,7 @@ private func notificationHandler(context_object: UnsafeMutableRawPointer?, sessi appDelegate.loadSchemaSpecificLabels(schemaId: schemaId) appDelegate.loadSchemaSpecificSettings(schemaId: schemaId, withRimeSession: session_id) } - if updateScriptVariant && !updateStyleOptions { + if updateScriptVariant, !updateStyleOptions { appDelegate.panel.updateScriptVariant() } guard appDelegate.showNotifications != .never else { break } diff --git a/sources/SquirrelConfig.swift b/sources/SquirrelConfig.swift index ce9dc1a19..dcbbe0347 100644 --- a/sources/SquirrelConfig.swift +++ b/sources/SquirrelConfig.swift @@ -33,7 +33,7 @@ final class SquirrelOptionSwitcher: NSObject { // return whether switcher options has been successfully updated func updateSwitcher(_ switcher: [String : String]) -> Bool { - guard !self.switcher.isEmpty && switcher.count == self.switcher.count else { return false } + guard !self.switcher.isEmpty, switcher.count == self.switcher.count else { return false } let optionNames: Set = Set(switcher.keys) if optionNames == self.optionNames { self.switcher = switcher @@ -57,26 +57,25 @@ final class SquirrelOptionSwitcher: NSObject { return true } - func updateCurrentScriptVariant(_ scriptVariant: String) -> Bool { - guard !scriptVariantOptions.isEmpty else { return false } - guard let scriptVariantCode = scriptVariantOptions[scriptVariant] else { return false } + func updateCurrentScriptVariant(_ scriptVariant: String?) -> Bool { + guard let scriptVariant = scriptVariant, !scriptVariantOptions.isEmpty, let scriptVariantCode = scriptVariantOptions[scriptVariant] else { return false } currentScriptVariant = scriptVariantCode return true } func update(withRimeSession session: RimeSessionId) { - guard !switcher.isEmpty && session != 0 else { return } + guard !switcher.isEmpty, session != 0 else { return } for state in optionStates { var updatedState: String? let optionGroup: [String] = Array(switcher.filter({ $0.value == state }).keys) - _ = optionGroup.first(where: { if RimeApi.get_option(session, $0) { updatedState = $0; return true } else { return false } }) + updatedState = optionGroup.first { RimeApi.get_option(session, $0) } updatedState ?= "!" + optionGroup[0] if updatedState != state { _ = updateGroupState(updatedState!, ofOption: state) } } // update script variant - _ = scriptVariantOptions.first(where: { if $0.key.hasPrefix("!") ? !RimeApi.get_option(session, String($0.key.dropFirst())) : RimeApi.get_option(session, $0.key) { _ = updateCurrentScriptVariant($0.key); return true } else { return false } }) + _ = updateCurrentScriptVariant(scriptVariantOptions.keys.first { $0.hasPrefix("!") ? !RimeApi.get_option(session, String($0.dropFirst())) : RimeApi.get_option(session, $0) }) } } // SquirrelOptionSwitcher @@ -111,12 +110,9 @@ final class SquirrelConfig: NSObject { private var colorSpaceName: String var colorSpace: String { get { colorSpaceName } - set { - let name: String = newValue.replacingOccurrences(of: "_", with: "") - if name != colorSpaceName, let (key, value) = Self.colorSpaceMap.first(where:{ $0.key.caseInsensitiveCompare(name) == .orderedSame }) { - colorSpaceName = key; colorSpaceObject = value - } - } + set { let name: String = newValue.replacingOccurrences(of: "_", with: "") + guard name != colorSpaceName, let (key, value) = Self.colorSpaceMap.first(where: { $0.key.caseInsensitiveCompare(name) == .orderedSame }) else { return } + (colorSpaceName, colorSpaceObject) = (key, value) } } override init() { @@ -194,7 +190,7 @@ final class SquirrelConfig: NSObject { } func close() { - if isOpen && RimeApi.config_close(&config) { + if isOpen, RimeApi.config_close(&config) { isOpen = false } baseConfig = nil @@ -251,11 +247,11 @@ final class SquirrelConfig: NSObject { return cachedValue } var value: Bool = false - if isOpen && RimeApi.config_get_bool(&config, option, &value) { + if isOpen, RimeApi.config_get_bool(&config, option, &value) { cache[option] = value return value } - if let aliasOption = option.replaceLastPathComponent(with: alias), isOpen && RimeApi.config_get_bool(&config, aliasOption, &value) { + if let aliasOption = option.replaceLastPathComponent(with: alias), isOpen, RimeApi.config_get_bool(&config, aliasOption, &value) { cache[option] = value return value } @@ -267,11 +263,11 @@ final class SquirrelConfig: NSObject { return cachedValue } var value: CInt = 0 - if isOpen && RimeApi.config_get_int(&config, option, &value) { + if isOpen, RimeApi.config_get_int(&config, option, &value) { cache[option] = Int(value) return Int(value) } - if let aliasOption = option.replaceLastPathComponent(with: alias), isOpen && RimeApi.config_get_int(&config, aliasOption, &value) { + if let aliasOption = option.replaceLastPathComponent(with: alias), isOpen, RimeApi.config_get_int(&config, aliasOption, &value) { cache[option] = Int(value) return Int(value) } @@ -283,11 +279,11 @@ final class SquirrelConfig: NSObject { return cachedValue } var value: Double = 0 - if isOpen && RimeApi.config_get_double(&config, option, &value) { + if isOpen, RimeApi.config_get_double(&config, option, &value) { cache[option] = value return value } - if let aliasOption = option.replaceLastPathComponent(with: alias), isOpen && RimeApi.config_get_double(&config, aliasOption, &value) { + if let aliasOption = option.replaceLastPathComponent(with: alias), isOpen, RimeApi.config_get_double(&config, aliasOption, &value) { cache[option] = value return value } @@ -394,13 +390,9 @@ final class SquirrelConfig: NSObject { } func optionSwitcherForSchema() -> SquirrelOptionSwitcher { - guard let schemaId = schemaId, !schemaId.isEmpty && schemaId != "." else { - return SquirrelOptionSwitcher() - } + guard let schemaId = schemaId, !schemaId.isEmpty, schemaId != "." else { return SquirrelOptionSwitcher() } var switchIter = RimeConfigIterator() - guard RimeApi.config_begin_list(&switchIter, &config, "switches") else { - return SquirrelOptionSwitcher(schemaId: schemaId) - } + guard RimeApi.config_begin_list(&switchIter, &config, "switches") else { return SquirrelOptionSwitcher(schemaId: schemaId) } var switcher: [String : String] = [:] var optionGroups: [String : Set] = [:] var defaultScriptVariant: String? @@ -412,7 +404,7 @@ final class SquirrelConfig: NSObject { switcher[name] = reset != 0 ? name : "!" + name optionGroups[name] = [name] } - if defaultScriptVariant == nil && (name.caseInsensitiveCompare("simplification") == .orderedSame || name.caseInsensitiveCompare("simplified") == .orderedSame || name.caseInsensitiveCompare("traditional") == .orderedSame) { + if defaultScriptVariant == nil, name.caseInsensitiveCompare("simplification") == .orderedSame || name.caseInsensitiveCompare("simplified") == .orderedSame || name.caseInsensitiveCompare("traditional") == .orderedSame { defaultScriptVariant = reset != 0 ? name : "!" + name scriptVariantOptions[name] = Self.code(scriptVariant: name) scriptVariantOptions["!" + name] = Self.code(scriptVariant: "!" + name) @@ -433,7 +425,7 @@ final class SquirrelConfig: NSObject { if hasStyleSection { optGroup.forEach { switcher[$0] = optGroup[reset]; optionGroups[$0] = Set(optGroup) } } - if defaultScriptVariant == nil && hasScriptVariant { + if defaultScriptVariant == nil, hasScriptVariant { optGroup.forEach { scriptVariantOptions[$0] = Self.code(scriptVariant: $0) } defaultScriptVariant = scriptVariantOptions[optGroup[reset]] } @@ -480,7 +472,7 @@ final class SquirrelConfig: NSObject { guard let hexCode = hexCode, hexCode.count == 8 || hexCode.count == 10, hexCode.hasPrefix("0x") || hexCode.hasPrefix("0X") else { return nil } let hexScanner = Scanner(string: hexCode) var hex: UInt64 = 0x0 - guard hexScanner.scanHexInt64(&hex) && hexScanner.isAtEnd else { return nil } + guard hexScanner.scanHexInt64(&hex), hexScanner.isAtEnd else { return nil } let r = CGFloat(hex % 0x100) let g = CGFloat(hex / 0x100 % 0x100) let b = CGFloat(hex / 0x10000 % 0x100) diff --git a/sources/SquirrelInputController.swift b/sources/SquirrelInputController.swift index 58eb399a9..5d611ab54 100644 --- a/sources/SquirrelInputController.swift +++ b/sources/SquirrelInputController.swift @@ -4,10 +4,9 @@ import IOKit final class SquirrelInputController: IMKInputController { // class variables static private let kNumKeyRollOver: Int = 50 - static weak var currentController: SquirrelInputController? static private var currentApp: String = "" static private var asciiMode: Bool? = nil - static var chordDuration: TimeInterval = 0 + static var chordDuration: TimeInterval = 0.1 // private private var inlineString: NSMutableAttributedString? private var originalString: String? @@ -37,37 +36,19 @@ final class SquirrelInputController: IMKInputController { // public private(set) var candidateTexts: [String] = [] private(set) var candidateComments: [String] = [] - // KVO - @objc dynamic var viewEffectiveAppearance: NSAppearance { - let sel: Selector = NSSelectorFromString("viewEffectiveAppearance") - let sourceAppearance: NSAppearance? = client().perform(sel)?.takeUnretainedValue() as? NSAppearance - return sourceAppearance ?? NSApp.effectiveAppearance - } - private var observation: NSKeyValueObservation? - - static func updateCurrentController(_ controller: SquirrelInputController) { - currentController = controller - NSApp.SquirrelAppDelegate.panel.inputController = controller - NSApp.SquirrelAppDelegate.panel.IbeamRect = .zero - let appearanceName: NSAppearance.Name = controller.viewEffectiveAppearance.bestMatch(from: [.aqua, .darkAqua])! - let style: SquirrelStyle = appearanceName == .darkAqua ? .dark : .light - NSApp.SquirrelAppDelegate.panel.style = style + @available(macOS 10.14, *) private var style: SquirrelStyle { + let clientAppearance = client().perform(NSSelectorFromString("viewEffectiveAppearance"))?.takeUnretainedValue() as? NSAppearance + return (clientAppearance ?? NSApp.effectiveAppearance).bestMatch(from: [.aqua, .darkAqua]) == .darkAqua ? .dark : .light } override init!(server: IMKServer!, delegate: Any!, client inputClient: Any!) { // print("init(server:delegate:client:)") super.init(server: server, delegate: delegate, client: inputClient) - observation = observe(\.viewEffectiveAppearance, options: [.new, .initial]) { object, change in - let appearanceName: NSAppearance.Name = change.newValue!.bestMatch(from: [.aqua, .darkAqua])! - let style: SquirrelStyle = appearanceName == .darkAqua ? .dark : .light - NSApp.SquirrelAppDelegate.panel.style = style - } createSession() } override func activateServer(_ sender: Any!) { // print("activateServer:") - Self.updateCurrentController(self) let baseConfig = SquirrelConfig(.base) if let keyboardLayout: String = baseConfig.string(forOption: "keyboard_layout") { if keyboardLayout.caseInsensitiveCompare("last") == .orderedSame || keyboardLayout.isEmpty { @@ -91,8 +72,11 @@ final class SquirrelInputController: IMKInputController { showInitialStatus() } } + lastModifiers = [] lastEventCount = 0 + NSApp.SquirrelAppDelegate.panel.inputController = self + NSApp.SquirrelAppDelegate.panel.IbeamRect = .zero super.activateServer(sender) } @@ -121,7 +105,7 @@ final class SquirrelInputController: IMKInputController { switch event.type { case .flagsChanged: - if lastModifiers == modifiers { return true } + guard lastModifiers != modifiers else { return true } // print("FLAGSCHANGED client: \(sender!), modifiers: 0x\(modifiers.rawValue)") let rimeKeycode = RimeKeycode(macKeycode: keyCode) let eventCountTypes: [CGEventType] = [.flagsChanged, .keyDown, .leftMouseDown, .rightMouseDown, .otherMouseDown] @@ -149,7 +133,7 @@ final class SquirrelInputController: IMKInputController { if eventCount - lastEventCount != 1 { rimeModifiers.insert(.Ignored) } handled = processKey(rimeKeycode, modifiers: rimeModifiers) case kVK_Option, kVK_RightOption: - if modifiers == .option && NSApp.SquirrelAppDelegate.panel.showToolTip() { + if modifiers == .option, NSApp.SquirrelAppDelegate.panel.showToolTip() { lastEventCount = eventCount return true } @@ -167,7 +151,7 @@ final class SquirrelInputController: IMKInputController { default: return false } - if NSApp.SquirrelAppDelegate.panel.hasStatusMessage || handled { + if NSApp.SquirrelAppDelegate.panel.statusMessage != nil || handled { rimeUpdate() handled = true } @@ -187,10 +171,10 @@ final class SquirrelInputController: IMKInputController { handled = processKey(rime_keycode, modifiers: rimeModifiers) if handled { rimeUpdate() - } else if panellessCommitFix && client().markedRange().length > 0 { + } else if panellessCommitFix, client().markedRange().length > 0 { if rime_keycode == .XK_Delete || .XK_Home ... .XK_KP_Delete ~= rime_keycode || .XK_BackSpace ... .XK_Escape ~= rime_keycode { showPlaceholder("") - } else if modifiers.isDisjoint(with: [.control, .command]) && !event.characters!.isEmpty { + } else if modifiers.isDisjoint(with: [.control, .command]), !event.characters!.isEmpty { showPlaceholder(nil) client().insertText(event.characters, replacementRange: NSRange(location: NSNotFound, length: NSNotFound)) return true @@ -210,7 +194,7 @@ final class SquirrelInputController: IMKInputController { let head = client().attributes(forCharacterIndex: 0, lineHeightRectangle: nil)["IMKBaseline"] as! NSPoint let tail = client().attributes(forCharacterIndex: markedRange.length - 1, lineHeightRectangle: nil)["IMKBaseline"] as! NSPoint if point.x > tail.x.nextUp || index >= markedRange.length { - if inlineCandidate && !inlinePreedit { return false } + if inlineCandidate, !inlinePreedit { return false } perform(action: .Process, onIndex: .EndKey) } else if point.x < head.x.nextDown || index <= 0 { perform(action: .Process, onIndex: .HomeKey) @@ -221,42 +205,41 @@ final class SquirrelInputController: IMKInputController { } private func processKey(_ keycode: RimeKeycode, modifiers: RimeModifiers) -> Bool { - let panel = NSApp.SquirrelAppDelegate.panel + let panel: SquirrelPanel = NSApp.SquirrelAppDelegate.panel // with linear candidate list, arrow keys may behave differently. - let isLinear = panel.isLinear + let isLinear: Bool = panel.isLinear if isLinear != RimeApi.get_option(session, "_linear") { RimeApi.set_option(session, "_linear", isLinear) } // with vertical text, arrow keys may behave differently. - let isVertical = panel.isVertical + let isVertical: Bool = panel.isVertical if isVertical != RimeApi.get_option(session, "_vertical") { RimeApi.set_option(session, "_vertical", isVertical) } - let isNavigatorInTabular = panel.isTabular && modifiers.isEmpty && panel.isVisible && (isVertical ? keycode == .XK_Left || keycode == .XK_KP_Left || keycode == .XK_Right || keycode == .XK_KP_Right : keycode == .XK_Up || keycode == .XK_KP_Up || keycode == .XK_Down || keycode == .XK_KP_Down) + let isNavigatorInTabular: Bool = panel.isTabular && modifiers.isEmpty && panel.isVisible && (isVertical ? keycode == .XK_Left || keycode == .XK_KP_Left || keycode == .XK_Right || keycode == .XK_KP_Right : keycode == .XK_Up || keycode == .XK_KP_Up || keycode == .XK_Down || keycode == .XK_KP_Down) if isNavigatorInTabular { var keycode: RimeKeycode = keycode if .XK_KP_Left ... .XK_KP_Down ~= keycode { keycode = keycode - .XK_KP_Left + .XK_Left } if let newIndex = panel.candidateIndex(onDirection: SquirrelIndex(rawValue: Int(keycode.rawValue))!) { - if !panel.isLocked && !panel.isExpanded && keycode == (isVertical ? .XK_Left : .XK_Down) { + if !panel.isLocked, !panel.isExpanded, keycode == (isVertical ? .XK_Left : .XK_Down) { panel.isExpanded = true } _ = RimeApi.highlight_candidate(session, newIndex) return true - } else if !panel.isLocked && panel.isExpanded && panel.sectionNum == 0 && keycode == (isVertical ? .XK_Right : .XK_Up) { + } else if !panel.isLocked, panel.isExpanded, panel.sectionNum == 0, keycode == (isVertical ? .XK_Right : .XK_Up) { panel.isExpanded = false return true } } - let handled = RimeApi.process_key(session, keycode.rawValue, modifiers.rawValue) + let handled: Bool = RimeApi.process_key(session, keycode.rawValue, modifiers.rawValue) // print("rime_keycode: \(rime_keycode), rime_modifiers: \(rime_modifiers), handled = \(handled)") if !handled { let isVimBackInCommandMode: Bool = keycode == .XK_Escape || (modifiers.contains(.Control) && (keycode == .XK_c || keycode == .XK_C || keycode == .XK_bracketleft)) - if isVimBackInCommandMode && RimeApi.get_option(session, "vim_mode") && - !RimeApi.get_option(session, "ascii_mode") { + if isVimBackInCommandMode, RimeApi.get_option(session, "vim_mode"), !RimeApi.get_option(session, "ascii_mode") { cancelComposition() RimeApi.set_option(session, "ascii_mode", true) // print("turned Chinese mode off in vim-like editor's command mode") @@ -266,8 +249,8 @@ final class SquirrelInputController: IMKInputController { // Simulate key-ups for every interesting key-down for chord-typing. if handled { - let isChordingKey = .XK_space ... .XK_asciitilde ~= keycode || keycode == .XK_Control_L || keycode == .XK_Control_R || keycode == .XK_Alt_L || keycode == .XK_Alt_R || keycode == .XK_Shift_L || keycode == .XK_Shift_R - if isChordingKey && RimeApi.get_option(session, "_chord_typing") { + let isChordingKey: Bool = .XK_space ... .XK_asciitilde ~= keycode || keycode == .XK_Control_L || keycode == .XK_Control_R || keycode == .XK_Alt_L || keycode == .XK_Alt_R || keycode == .XK_Shift_L || keycode == .XK_Shift_R + if isChordingKey, RimeApi.get_option(session, "_chord_typing") { updateChord(keycode, modifiers: modifiers) } else if modifiers.isDisjoint(with: .Release) { // non-chording key pressed @@ -341,7 +324,7 @@ final class SquirrelInputController: IMKInputController { private func onChordTimer() { // chord release triggered by timer var processed_keys: Int = 0 - if !chordKeyCombos.isEmpty && session != 0 { + if !chordKeyCombos.isEmpty, session != 0 { chordKeyCombos.forEach { if RimeApi.process_key(session, $0.keycode.rawValue, $0.modifiers.union(.Release).rawValue) { processed_keys += 1 } } } clearChord() @@ -374,7 +357,7 @@ final class SquirrelInputController: IMKInputController { private func showInitialStatus() { var status: RimeStatus_stdbool = RimeStructInit() - guard session != 0 && RimeApi.get_status(session, &status) else { return } + guard session != 0, RimeApi.get_status(session, &status) else { return } let schemaName = String(cString: status.schema_name ?? status.schema_id) var options: [String] = [] if let asciiMode = getOptionLabel(session: session, option: "ascii_mode", state: status.is_ascii_mode) { @@ -497,9 +480,7 @@ final class SquirrelInputController: IMKInputController { private func showInlineString(_ string: String, withSelRange selRange: Range, caretPos: Int) { // print("showPreeditString: '\(preedit)'") - if caretPos == inlineCaretPos && selRange == inlineSelRange && string == inlineString?.string { - return - } + guard caretPos != inlineCaretPos, selRange != inlineSelRange, string != inlineString?.string else { return } inlineSelRange = selRange inlineCaretPos = caretPos // print("selRange = \(selRange), caretPos = \(caretPos)") @@ -517,7 +498,7 @@ final class SquirrelInputController: IMKInputController { private func getIbeamRect() -> NSRect { var IbeamRect: NSRect = .zero client().attributes(forCharacterIndex: 0, lineHeightRectangle: &IbeamRect) - if IbeamRect.isEmpty && inlineString?.length == 0 { + if IbeamRect.isEmpty, inlineString?.length == 0 { if client().selectedRange().length == 0 { // activate inline session, in e.g. table cells, by fake inputs client().setMarkedText(" ", selectionRange: NSRange(location: 0, length: 0), replacementRange: NSRange(location: NSNotFound, length: NSNotFound)) @@ -568,8 +549,9 @@ final class SquirrelInputController: IMKInputController { let panel: SquirrelPanel = NSApp.SquirrelAppDelegate.panel if panel.IbeamRect == .zero { panel.IbeamRect = getIbeamRect() + if #available(macOS 10.14, *) { panel.style = style } } - if panel.IbeamRect.isEmpty && panel.hasStatusMessage { + if panel.IbeamRect.isEmpty, panel.statusMessage != nil { panel.updateStatus(long: nil, short: nil) } else { panel.showPanel(withPreedit: preedit, selRange: selRange, caretPos: caretPos, candidateIndices: candidateIndices, highlightedCandidate: highlightedCandidate, pageNum: pageNum, isLastPage: isLastPage, didCompose: didCompose) @@ -657,7 +639,7 @@ final class SquirrelInputController: IMKInputController { var ctx: RimeContext_stdbool = RimeStructInit() if RimeApi.get_context(session, &ctx) { - let showingStatus: Bool = panel.hasStatusMessage + let showingStatus: Bool = panel.statusMessage != nil // update preedit text let preedit: UnsafeMutablePointer! = ctx.composition.preedit let preeditText = preedit == nil ? "" : String(cString: preedit) @@ -690,7 +672,7 @@ final class SquirrelInputController: IMKInputController { // selected segment, with locations in terms of raw input var suffixLength = preeditText[start...].replacingOccurrences(of: " ", with: "").length let selLength = preeditText[start ..< end].replacingOccurrences(of: " ", with: "").length - if !inlinePreedit && end ..< length ~= caretPos { // subtract length of soft cursor + if !inlinePreedit, end ..< length ~= caretPos { // subtract length of soft cursor suffixLength -= 1 } let selSegment: Range = self.originalString == nil ? 0 ..< 0 : (self.originalString!.length - suffixLength - selLength) ..< (self.originalString!.length - suffixLength) @@ -698,19 +680,19 @@ final class SquirrelInputController: IMKInputController { self.selSegment = selSegment // update `expanded` and `sectionNum` variables in tabular layout // already processed the action if `currentIndex` == nil - if panel.isTabular && !showingStatus { + if panel.isTabular, !showingStatus { if numCandidates == 0 || didCompose { panel.sectionNum = 0 } else if currentIndex != nil { let currentPageNum: Int = currentIndex! / pageSize - if !panel.isLocked && panel.isExpanded && panel.isFirstLine && pageNum == 0 && hilitedCandidate == 0 && currentIndex == 0 { + if !panel.isLocked, panel.isExpanded, panel.isFirstLine, pageNum == 0, hilitedCandidate == 0, currentIndex == 0 { panel.isExpanded = false - } else if !panel.isLocked && !panel.isExpanded && pageNum > currentPageNum { + } else if !panel.isLocked, !panel.isExpanded, pageNum > currentPageNum { panel.isExpanded = true } - if panel.isExpanded && pageNum > currentPageNum && panel.sectionNum < (panel.isVertical ? 2 : 4) { + if panel.isExpanded, pageNum > currentPageNum, panel.sectionNum < (panel.isVertical ? 2 : 4) { panel.sectionNum = min(panel.sectionNum + pageNum - currentPageNum, (isLastPage ? 4 : 3) - (panel.isVertical ? 2 : 0)) - } else if panel.isExpanded && pageNum < currentPageNum && panel.sectionNum > 0 { + } else if panel.isExpanded, pageNum < currentPageNum, panel.sectionNum > 0 { panel.sectionNum = max(panel.sectionNum + pageNum - currentPageNum, pageNum == 0 ? 0 : 1) } } @@ -727,16 +709,16 @@ final class SquirrelInputController: IMKInputController { let candidatePreview: UnsafeMutablePointer! = ctx.commit_text_preview var candidatePreviewText = candidatePreview == nil ? "" : String(cString: candidatePreview) if inlinePreedit { - if end <= caretPos && caretPos < length { + if end <= caretPos, caretPos < length { candidatePreviewText += preeditText[caretPos...] } if !didCommit || !candidatePreviewText.isEmpty { showInlineString(candidatePreviewText, withSelRange: start ..< candidatePreviewText.length - (length - end), caretPos: caretPos < end ? caretPos : candidatePreviewText.length - (length - caretPos)) } } else { // preedit includes the soft cursor - if end < caretPos && caretPos <= length { + if end < caretPos, caretPos <= length { candidatePreviewText = candidatePreviewText[..<(candidatePreviewText.length - (caretPos - end))] - } else if caretPos < end && end < length { + } else if caretPos < end, end < length { candidatePreviewText = candidatePreviewText[..<(candidatePreviewText.length - (length - end))] } if !didCommit || !candidatePreviewText.isEmpty { @@ -745,13 +727,13 @@ final class SquirrelInputController: IMKInputController { } } else { if inlinePreedit { - if inlinePlaceholder && preeditText.isEmpty && numCandidates > 0 { + if inlinePlaceholder, preeditText.isEmpty, numCandidates > 0 { showPlaceholder(String.FullWidthSpace) } else if !didCommit || !preeditText.isEmpty { showInlineString(preeditText, withSelRange: start ..< end, caretPos: caretPos) } } else { - if inlinePlaceholder && preedit != nil { + if inlinePlaceholder, preedit != nil { showPlaceholder(String.FullWidthSpace) } else if !didCommit || preedit != nil { showInlineString("", withSelRange: 0 ..< 0, caretPos: 0) @@ -769,7 +751,7 @@ final class SquirrelInputController: IMKInputController { if index < endIndex { var iterator = RimeCandidateListIterator() if RimeApi.candidate_list_from_index(session, &iterator, CInt(index)) { - while index < endIndex && RimeApi.candidate_list_next(&iterator) { + while index < endIndex, RimeApi.candidate_list_next(&iterator) { updateCandidate(iterator.candidate, at: index) index += 1 } @@ -786,7 +768,7 @@ final class SquirrelInputController: IMKInputController { if index < endIndex { var iterator = RimeCandidateListIterator() if RimeApi.candidate_list_from_index(session, &iterator, CInt(index)) { - while index < endIndex && RimeApi.candidate_list_next(&iterator) { + while index < endIndex, RimeApi.candidate_list_next(&iterator) { updateCandidate(iterator.candidate, at: index) index += 1 } @@ -838,7 +820,7 @@ private func updateCapsLockLEDState(targetState: Bool) { private func getOptionLabel(session: RimeSessionId, option: UnsafePointer, state: Bool) -> String? { let labelShort: RimeStringSlice = RimeApi.get_state_label_abbreviated(session, option, state, true) - if labelShort.str != nil && labelShort.length >= strlen(labelShort.str) { + if labelShort.str != nil, labelShort.length >= strlen(labelShort.str) { return String(cString: labelShort.str) } else { let labelLong: RimeStringSlice = RimeApi.get_state_label_abbreviated(session, option, state, false) @@ -936,6 +918,6 @@ enum SquirrelIndex: RawRepresentable, Comparable, Sendable { } // SquirrelIndex extension Bool { - static func |= (lhs: inout Bool, rhs: Bool) { if !lhs && rhs { lhs = true } } - static func &= (lhs: inout Bool, rhs: Bool) { if lhs && !rhs { lhs = false } } + static func |= (lhs: inout Bool, rhs: Bool) { if !lhs, rhs { lhs = true } } + static func &= (lhs: inout Bool, rhs: Bool) { if lhs, !rhs { lhs = false } } } diff --git a/sources/SquirrelInputSource.swift b/sources/SquirrelInputSource.swift index c1ee625f5..9c8286f97 100644 --- a/sources/SquirrelInputSource.swift +++ b/sources/SquirrelInputSource.swift @@ -62,7 +62,7 @@ extension SquirrelApp { inputModesToEnable = [.HANS] } } - let sourceList = TISCreateInputSourceList(property, true).takeUnretainedValue() as! [TISInputSource] + let sourceList = TISCreateInputSourceList(property, true).takeRetainedValue() as! [TISInputSource] for source in sourceList { guard let sourceID: CFString = bridge(ptr: TISGetInputSourceProperty(source, kTISPropertyInputSourceID)), (sourceID == InputModeIDHans && inputModesToEnable.contains(.HANS)) || (sourceID == InputModeIDHant && inputModesToEnable.contains(.HANT)) || (sourceID == InputModeIDCant && inputModesToEnable.contains(.CANT)) else { continue } // print("Examining input source: \(sourceID)") @@ -81,13 +81,13 @@ extension SquirrelApp { var inputModeToSelect: RimeInputModes = mode if inputModeToSelect.isEmpty || !enabledInputModes.contains(inputModeToSelect) { for language in preferences { - if language.caseInsensitiveCompare("zh-Hans") == .orderedSame && enabledInputModes.contains(.HANS) { + if language.caseInsensitiveCompare("zh-Hans") == .orderedSame, enabledInputModes.contains(.HANS) { inputModeToSelect = .HANS; break } - if language.caseInsensitiveCompare("zh-Hant") == .orderedSame && enabledInputModes.contains(.HANT) { + if language.caseInsensitiveCompare("zh-Hant") == .orderedSame, enabledInputModes.contains(.HANT) { inputModeToSelect = .HANT; break } - if language.caseInsensitiveCompare("zh-HK") == .orderedSame && enabledInputModes.contains(.CANT) { + if language.caseInsensitiveCompare("zh-HK") == .orderedSame, enabledInputModes.contains(.CANT) { inputModeToSelect = .CANT; break } } @@ -95,12 +95,12 @@ extension SquirrelApp { if inputModeToSelect.isEmpty { print("No enabled input sources."); return } - let sourceList = TISCreateInputSourceList(property, false).takeUnretainedValue() as! [TISInputSource] + let sourceList = TISCreateInputSourceList(property, false).takeRetainedValue() as! [TISInputSource] for source in sourceList { guard let sourceID: CFString = bridge(ptr: TISGetInputSourceProperty(source, kTISPropertyInputSourceID)), (sourceID == InputModeIDHans && inputModeToSelect == .HANS) || (sourceID == InputModeIDHant && inputModeToSelect == .HANT) || (sourceID == InputModeIDCant && inputModeToSelect == .CANT) else { continue } // print("Examining input source: \(sourceID)") // select the first enabled input mode in Squirrel - guard let isSelectable: CFBoolean = bridge(ptr: TISGetInputSourceProperty(source, kTISPropertyInputSourceIsSelectCapable)), let isSelected: CFBoolean = bridge(ptr: TISGetInputSourceProperty(source, kTISPropertyInputSourceIsSelected)), !CFBooleanGetValue(isSelected) && CFBooleanGetValue(isSelectable) else { continue } + guard let isSelectable: CFBoolean = bridge(ptr: TISGetInputSourceProperty(source, kTISPropertyInputSourceIsSelectCapable)), let isSelected: CFBoolean = bridge(ptr: TISGetInputSourceProperty(source, kTISPropertyInputSourceIsSelected)), !CFBooleanGetValue(isSelected), CFBooleanGetValue(isSelectable) else { continue } let selectError: OSStatus = TISSelectInputSource(source) if selectError == noErr { print("Selected input source: \(sourceID)"); break @@ -111,7 +111,7 @@ extension SquirrelApp { } static func DisableInputSource() { - let sourceList = TISCreateInputSourceList(property, false).takeUnretainedValue() as! [TISInputSource] + let sourceList = TISCreateInputSourceList(property, false).takeRetainedValue() as! [TISInputSource] for source in sourceList { guard let sourceID: CFString = bridge(ptr: TISGetInputSourceProperty(source, kTISPropertyInputSourceID)), sourceID == InputModeIDHans || sourceID == InputModeIDHant || sourceID == InputModeIDCant else { continue } // print("Examining input source: \(sourceID)") @@ -126,7 +126,7 @@ extension SquirrelApp { static private func GetEnabledInputModes(includeAllInstalled: Bool) -> RimeInputModes { var inputModes: RimeInputModes = [] - let sourceList = TISCreateInputSourceList(property, includeAllInstalled).takeUnretainedValue() as! [TISInputSource] + let sourceList = TISCreateInputSourceList(property, includeAllInstalled).takeRetainedValue() as! [TISInputSource] for source in sourceList { guard let sourceID: CFString = bridge(ptr: TISGetInputSourceProperty(source, kTISPropertyInputSourceID)) else { continue } // print("Examining input source: \(sourceID)") diff --git a/sources/SquirrelPanel.swift b/sources/SquirrelPanel.swift index a595a72f2..15b2b2c89 100644 --- a/sources/SquirrelPanel.swift +++ b/sources/SquirrelPanel.swift @@ -11,7 +11,7 @@ extension Comparable { // coalesce: assign new value if current value is null infix operator ?= : AssignmentPrecedence -func ?= (lhs: inout T?, rhs: T?) { if lhs == nil && rhs != nil { lhs = rhs } } +func ?= (lhs: inout T?, rhs: T?) { if lhs == nil, rhs != nil { lhs = rhs } } // overwrite current value with new value (provided not null) infix operator =? : AssignmentPrecedence @@ -33,7 +33,7 @@ extension NSPoint: @retroactive AdditiveArithmetic { extension NSRect { // top-left -> bottom-left -> bottom-right -> top-right var vertices: [NSPoint] { isEmpty ? [] : [origin, NSPoint(x: minX, y: maxY), NSPoint(x: maxX, y: maxY), NSPoint(x: maxX, y: minY)] } - func integral(options: AlignmentOptions) -> NSRect { NSIntegralRectWithOptions(self, options) } + func integral(options: AlignmentOptions) -> Self { NSIntegralRectWithOptions(self, options) } func squirclePath(cornerRadius: Double) -> CGPath? { CGMutablePath.squirclePath(vertices: vertices, cornerRadius: cornerRadius)?.copy() @@ -104,10 +104,9 @@ struct SquirrelCandidateInfo: Sendable { extension CGPath { static func combinePaths(_ x: CGPath?, _ y: CGPath?) -> CGPath? { - if x == nil { return y?.copy() } - if y == nil { return x?.copy() } - let path: CGMutablePath? = x!.mutableCopy() - path?.addPath(y!) + guard let x = x, let y = y else { return y?.copy() ?? x?.copy() } + let path: CGMutablePath? = x.mutableCopy() + path?.addPath(y) return path?.copy() } } @@ -673,7 +672,7 @@ final class SquirrelTheme: NSObject { } private func updateSelectKeys(_ selectKeys: String, labels rawLabels: [String], directUpdate: Bool) { - if self.selectKeys == selectKeys && self.rawLabels == rawLabels { return } + guard self.selectKeys != selectKeys, self.rawLabels != rawLabels else { return } self.selectKeys = selectKeys self.rawLabels = rawLabels pageSize = rawLabels.count @@ -881,11 +880,13 @@ final class SquirrelTheme: NSObject { var colorScheme: String? if style == .dark { - _ = styleOptions.first(where: { if let value = config.string(forOption: "style/\($0)/color_scheme_dark") { colorScheme = value; return true } else { return false } }) + _ = styleOptions.first { guard let value = config.string(forOption: "style/\($0)/color_scheme_dark") else { return false } + colorScheme = value; return true } colorScheme ?= config.string(forOption: "style/color_scheme_dark") } if colorScheme == nil { - _ = styleOptions.first(where: { if let value = config.string(forOption: "style/\($0)/color_scheme") { colorScheme = value; return true } else { return false } }) + _ = styleOptions.first { guard let value = config.string(forOption: "style/\($0)/color_scheme") else { return false } + colorScheme = value; return true } colorScheme ?= config.string(forOption: "style/color_scheme") } let isNative: Bool = (colorScheme == nil) || (colorScheme! == "native") @@ -1057,7 +1058,7 @@ final class SquirrelTheme: NSObject { // CHROMATICS refinement translucency ?= .zero - if #available(macOS 10.14, *), translucency!.isNormal && !isNative && backColor != nil && (style == .dark ? backColor!.lStarComponent! > 0.6 : backColor!.lStarComponent! < 0.4) { + if #available(macOS 10.14, *), translucency!.isNormal, !isNative, backColor != nil, style == .dark ? backColor!.lStarComponent! > 0.6 : backColor!.lStarComponent! < 0.4 { backColor = backColor?.invertLuminance(toExtent: .standard) borderColor = borderColor?.invertLuminance(toExtent: .standard) preeditBackColor = preeditBackColor?.invertLuminance(toExtent: .standard) @@ -1124,7 +1125,7 @@ final class SquirrelTheme: NSObject { } func updateAnnotationHeight(_ height: Double) { - guard height.isNormal && lineSpacing < height * 2 else { return } + guard height.isNormal, lineSpacing < height * 2 else { return } lineSpacing = height * 2 let candidateParagraphStyle: NSMutableParagraphStyle = self.candidateParagraphStyle.mutableCopy() if isLinear { @@ -1298,7 +1299,7 @@ final class SquirrelLayoutManager: NSLayoutManager, NSLayoutManagerDelegate { } func layoutManager(_ layoutManager: NSLayoutManager, shouldUse action: NSLayoutManager.ControlCharacterAction, forControlCharacterAt charIndex: Int) -> NSLayoutManager.ControlCharacterAction { - if charIndex > 0 && layoutManager.textStorage!.mutableString.character(at: charIndex) == 0x8B && layoutManager.textStorage!.attribute(.rubyAnnotation, at: charIndex - 1, effectiveRange: nil) != nil { + if charIndex > 0, layoutManager.textStorage!.mutableString.character(at: charIndex) == 0x8B, layoutManager.textStorage!.attribute(.rubyAnnotation, at: charIndex - 1, effectiveRange: nil) != nil { return .whitespace } else { return action @@ -1507,13 +1508,13 @@ final class SquirrelTextView: NSTextView { var headLineRange: NSTextRange? var tailLineRange: NSTextRange? textLayoutManager?.enumerateTextSegments(in: textRange, type: .standard, options: [.middleFragmentsExcluded]) { segRange, segFrame, baseline, textContainer in - guard !segFrame.isEmpty else { return true } + guard !segFrame.isEmpty, let segRange = segRange else { return true } if headLineRect.isEmpty || segFrame.minY < headLineRect.maxY.nextDown { headLineRect = segFrame.union(headLineRect) - headLineRange = headLineRange == nil ? segRange! : segRange!.union(headLineRange!) + headLineRange = headLineRange == nil ? segRange : segRange.union(headLineRange!) } else { tailLineRect = segFrame.union(tailLineRect) - tailLineRange = tailLineRange == nil ? segRange! : segRange!.union(tailLineRange!) + tailLineRange = tailLineRange == nil ? segRange : segRange.union(tailLineRange!) } return true } @@ -1642,16 +1643,13 @@ final class SquirrelView: NSView { var sectionNum: Int = 0 var isExpanded: Bool = false var isLocked: Bool = false - override var isFlipped: Bool { true } - override var wantsUpdateLayer: Bool { true } var style: SquirrelStyle { - didSet { - guard #available(macOS 10.14, *), oldValue != style else { return } - theme = style == .dark ? Self.darkTheme : Self.lightTheme - scrollView.scrollerKnobStyle = style == .dark ? .light : .dark - updateColors() - } + didSet { theme = style == .dark ? Self.darkTheme : Self.lightTheme + scrollView.scrollerKnobStyle = style == .dark ? .light : .dark + updateColors() } } + override var isFlipped: Bool { true } + override var wantsUpdateLayer: Bool { true } override init(frame frameRect: NSRect) { candidateView = SquirrelTextView(contentBlock: .stackedCandidates, textStorage: candidateContents) @@ -1673,7 +1671,7 @@ final class SquirrelView: NSView { scrollView.scrollerStyle = .overlay scrollView.scrollerKnobStyle = .dark scrollView.wantsLayer = true - scrollView.layer!.isGeometryFlipped = true + scrollView.layer?.isGeometryFlipped = true style = .light theme = Self.lightTheme @@ -1682,18 +1680,18 @@ final class SquirrelView: NSView { } super.init(frame: frameRect) wantsLayer = true - layer!.isGeometryFlipped = true + layer?.isGeometryFlipped = true layerContentsRedrawPolicy = .onSetNeedsDisplay backImageLayer.actions = ["transform" : NSNull()] backColorLayer.fillRule = .evenOdd borderLayer.fillRule = .evenOdd - layer!.addSublayer(backImageLayer) - layer!.addSublayer(backColorLayer) - layer!.addSublayer(hilitedPreeditLayer) - layer!.addSublayer(functionButtonLayer) - layer!.addSublayer(logoLayer) - layer!.addSublayer(borderLayer) + layer?.addSublayer(backImageLayer) + layer?.addSublayer(backColorLayer) + layer?.addSublayer(hilitedPreeditLayer) + layer?.addSublayer(functionButtonLayer) + layer?.addSublayer(logoLayer) + layer?.addSublayer(borderLayer) documentLayer.fillRule = .evenOdd documentLayer.allowsGroupOpacity = true @@ -1701,12 +1699,12 @@ final class SquirrelView: NSView { gridLayer.lineCap = .round gridLayer.lineWidth = 1.0 clipLayer.fillColor = .white - documentView.layer!.addSublayer(documentLayer) + documentView.layer?.addSublayer(documentLayer) documentLayer.addSublayer(activePageLayer) - documentView.layer!.addSublayer(gridLayer) - documentView.layer!.addSublayer(nonHilitedCandidateLayer) - documentView.layer!.addSublayer(hilitedCandidateLayer) - scrollView.layer!.mask = clipLayer + documentView.layer?.addSublayer(gridLayer) + documentView.layer?.addSublayer(nonHilitedCandidateLayer) + documentView.layer?.addSublayer(hilitedCandidateLayer) + scrollView.layer?.mask = clipLayer } @available(*, unavailable) required init?(coder _: NSCoder) { @@ -1779,7 +1777,7 @@ final class SquirrelView: NSView { clipRect = .zero pagingRect = .zero clippedHeight = .zero - if !hasPreedit && candidateInfos.isEmpty { // status + if !hasPreedit, candidateInfos.isEmpty { // status contentRect = statusView.layoutText(); return } if hasPreedit { @@ -1789,7 +1787,7 @@ final class SquirrelView: NSView { if candidateInfos.isEmpty { return } documentRect = candidateView.layoutText() documentRect.size.height += theme.lineSpacing - if theme.isLinear && candidateInfos.allSatisfy({ !$0.isTruncated }) { + if theme.isLinear, candidateInfos.allSatisfy({ !$0.isTruncated }) { documentRect.size.width -= theme.fullWidth } clipRect = documentRect @@ -2004,7 +2002,7 @@ final class SquirrelView: NSView { preeditRect.size.width = backgroundRect.width preeditRect = backingAlignedRect(preeditRect, options: [.alignAllEdgesNearest]) // Draw the highlighted part of preedit text - if hilitedPreeditRange.length > 0 && theme.hilitedPreeditBackColor != nil { + if hilitedPreeditRange.length > 0, theme.hilitedPreeditBackColor != nil { let padding: Double = (theme.preeditParagraphStyle.minimumLineHeight * 0.05).rounded(.up) var innerBox: NSRect = preeditRect innerBox.origin.x += (theme.fullWidth * 0.5).rounded(.up) - padding @@ -2104,8 +2102,8 @@ final class SquirrelView: NSView { lineNum += candInfo.idx > 0 ? 1 : 0 // horizontal border except for the last line if bottomEdge < documentRect.maxY - 2 { - gridPath!.move(to: NSPoint(x: (theme.fullWidth * 0.5).rounded(.up), y: bottomEdge)) - gridPath!.addLine(to: NSPoint(x: documentRect.maxX - (theme.fullWidth * 0.5).rounded(.down), y: bottomEdge)) + gridPath?.move(to: NSPoint(x: (theme.fullWidth * 0.5).rounded(.up), y: bottomEdge)) + gridPath?.addLine(to: NSPoint(x: documentRect.maxX - (theme.fullWidth * 0.5).rounded(.down), y: bottomEdge)) } gridOriginY = bottomEdge } @@ -2113,8 +2111,8 @@ final class SquirrelView: NSView { let leadTabColumn = Int(((leadOrigin.x - documentRect.minX) / tabInterval).rounded()) // vertical bar if leadOrigin.x > documentRect.minX + theme.fullWidth { - gridPath!.move(to: NSPoint(x: leadOrigin.x, y: leadOrigin.y + (theme.lineSpacing * 0.5).rounded(.down) + theme.candidateParagraphStyle.minimumLineHeight * 0.2)) - gridPath!.addLine(to: NSPoint(x: leadOrigin.x, y: candidatePolygon.maxY - (theme.lineSpacing * 0.5).rounded(.up) - theme.candidateParagraphStyle.minimumLineHeight * 0.2)) + gridPath?.move(to: NSPoint(x: leadOrigin.x, y: leadOrigin.y + (theme.lineSpacing * 0.5).rounded(.down) + theme.candidateParagraphStyle.minimumLineHeight * 0.2)) + gridPath?.addLine(to: NSPoint(x: leadOrigin.x, y: candidatePolygon.maxY - (theme.lineSpacing * 0.5).rounded(.up) - theme.candidateParagraphStyle.minimumLineHeight * 0.2)) } tabularIndices.append(SquirrelTabularIndex(index: candInfo.idx, lineNum: lineNum, tabNum: leadTabColumn)) } @@ -2183,7 +2181,7 @@ final class SquirrelView: NSView { shape.path = shapePath } // highlighted preedit layer - if hilitedPreeditPath != nil && theme.hilitedPreeditBackColor != nil { + if hilitedPreeditPath != nil, theme.hilitedPreeditBackColor != nil { hilitedPreeditLayer.path = hilitedPreeditPath hilitedPreeditLayer.isHidden = false } else { @@ -2215,8 +2213,8 @@ final class SquirrelView: NSView { } else { nonHilitedCandidateLayer.isHidden = true } - if hilitedCandidate != nil && theme.hilitedCandidateBackColor != nil, let hilitedCandidatePath = theme.isLinear ? candidatePolygons[hilitedCandidate!].squirclePath(cornerRadius: hilitedCornerRadius) : candidatePolygons[hilitedCandidate!].body.squirclePath(cornerRadius: hilitedCornerRadius) { - if theme.stackColors && theme.hilitedCandidateBackColor!.alphaComponent < 1.0.nextDown { + if hilitedCandidate != nil, theme.hilitedCandidateBackColor != nil, let hilitedCandidatePath = theme.isLinear ? candidatePolygons[hilitedCandidate!].squirclePath(cornerRadius: hilitedCornerRadius) : candidatePolygons[hilitedCandidate!].body.squirclePath(cornerRadius: hilitedCornerRadius) { + if theme.stackColors, theme.hilitedCandidateBackColor!.alphaComponent < 1.0.nextDown { (expanded ? activePagePath : documentPath)?.addPath(hilitedCandidatePath.copy()!) } hilitedCandidateLayer.path = hilitedCandidatePath @@ -2262,7 +2260,7 @@ final class SquirrelView: NSView { if let clipPath = clipPath { let nonCandidatePath = backgroundPath?.mutableCopy() nonCandidatePath?.addPath(clipPath) - if theme.stackColors && theme.hilitedPreeditBackColor != nil && theme.hilitedPreeditBackColor!.alphaComponent < 1.0.nextDown { + if theme.stackColors, theme.hilitedPreeditBackColor != nil, theme.hilitedPreeditBackColor!.alphaComponent < 1.0.nextDown { if hilitedPreeditPath != nil { nonCandidatePath?.addPath(hilitedPreeditPath!) } @@ -2450,81 +2448,58 @@ final class SquirrelPanel: NSPanel, NSWindowDelegate { var isTabular: Bool { view.theme.isTabular } var isLocked: Bool { get { view.isLocked } - set { - guard view.theme.isTabular && view.isLocked != newValue else { return } - view.isLocked = newValue - let userConfig = SquirrelConfig(.user) - _ = userConfig.setOption("var/option/_isLockedTabular", withBool: newValue) - if newValue { - _ = userConfig.setOption("var/option/_isExpandedTabular", withBool: view.isExpanded) - } - userConfig.close() - } + set { guard view.theme.isTabular, view.isLocked != newValue else { return } + view.isLocked = newValue + let userConfig = SquirrelConfig(.user) + _ = userConfig.setOption("var/option/_isLockedTabular", withBool: newValue) + if newValue { _ = userConfig.setOption("var/option/_isExpandedTabular", withBool: view.isExpanded) } + userConfig.close() } } - - private func getLocked() { - guard view.theme.isTabular else { return } - let userConfig = SquirrelConfig(.user) - view.isLocked = userConfig.boolValue(forOption: "var/option/_isLockedTabular") - if view.isLocked { - view.isExpanded = userConfig.boolValue(forOption: "var/option/_isExpandedTabular") - } - userConfig.close() - view.sectionNum = 0 - } - var isFirstLine: Bool { view.tabularIndices.isEmpty ? true : view.tabularIndices[highlightedCandidate!].lineNum == 0 } var isExpanded: Bool { get { view.isExpanded } - set { - guard view.theme.isTabular && !view.isLocked && !(isLastPage && pageNum == 0) && view.isExpanded != newValue else { return } - view.isExpanded = newValue - view.sectionNum = 0 - needsRedraw = true - } + set { guard view.theme.isTabular, !view.isLocked, !(isLastPage && pageNum == 0), view.isExpanded != newValue else { return } + view.isExpanded = newValue + view.sectionNum = 0 + needsRedraw = true } } - var sectionNum: Int { get { view.sectionNum } - set { - guard view.theme.isTabular && view.isExpanded && view.sectionNum != newValue else { return } - view.sectionNum = newValue.clamp(min: 0, max: view.theme.isVertical ? 2 : 4) - } + set { guard view.theme.isTabular, view.isExpanded, view.sectionNum != newValue else { return } + view.sectionNum = newValue.clamp(min: 0, max: view.theme.isVertical ? 2 : 4) } } - - // position of the text input I-beam cursor on screen. + /// Position of the text input I-beam cursor on screen. var IbeamRect: NSRect = .zero { didSet { guard oldValue != IbeamRect else { return } needsRedraw = true if IbeamRect == .zero { initPosition = true - } else if !_screen.frame.contains(IbeamRect) && !_screen.frame.intersects(IbeamRect) { + } else if !_screen.frame.contains(IbeamRect), !_screen.frame.intersects(IbeamRect) { + willChangeValue(for: \.screen) updateScreen() + didChangeValue(for: \.screen) updateDisplayParameters() } } } - private var _screen: NSScreen = .main! override var screen: NSScreen? { _screen } weak var inputController: SquirrelInputController? var style: SquirrelStyle { - didSet { - guard oldValue != style else { return } - view.style = style - appearance = NSAppearance(named: style == .dark ? .darkAqua : .aqua) - } - } - - // Status message when pop-up is about to be displayed; nil when normal panel is about to be displayed - private var statusMessage: String? - var hasStatusMessage: Bool { statusMessage != nil } - // Store switch options that change style (color theme) settings + get { view.style } + set { guard #available(macOS 10.14, *), view.style != newValue else { return } + view.style = newValue + appearance = NSAppearance(named: newValue == .dark ? .darkAqua : .aqua) + view.needsDisplay = true + display() } + } + /// Status message when pop-up is about to be displayed; nil when normal panel is about to be displayed. + private(set) var statusMessage: String? + /// Stores switch options that change style (color theme) settings. var optionSwitcher = SquirrelOptionSwitcher() init() { - style = .light super.init(contentRect: .zero, styleMask: [.borderless, .nonactivatingPanel], backing: .buffered, defer: true) level = NSWindow.Level(Int(CGWindowLevelForKey(.cursorWindow) - 100)) hasShadow = false @@ -2532,6 +2507,7 @@ final class SquirrelPanel: NSPanel, NSWindowDelegate { backgroundColor = .clear delegate = self acceptsMouseMovedEvents = true + displaysWhenScreenProfileChanges = true worksWhenModal = true let contentView = NSFlippedView() @@ -2542,7 +2518,7 @@ final class SquirrelPanel: NSPanel, NSWindowDelegate { back.state = .active back.isEmphasized = true back.wantsLayer = true - back.layer!.mask = view.shape + back.layer?.mask = view.shape contentView.addSubview(back) } contentView.addSubview(view) @@ -2574,7 +2550,7 @@ final class SquirrelPanel: NSPanel, NSWindowDelegate { view.pagingView.setLayoutOrientation(theme.isVertical ? .vertical : .horizontal) view.statusView.setLayoutOrientation(theme.isVertical ? .vertical : .horizontal) // rotate the view, the core in vertical mode! - contentView!.boundsRotation = theme.isVertical ? 90 : .zero + contentView?.boundsRotation = theme.isVertical ? 90 : .zero view.candidateView.boundsRotation = .zero view.preeditView.boundsRotation = .zero view.pagingView.boundsRotation = .zero @@ -2595,16 +2571,16 @@ final class SquirrelPanel: NSPanel, NSWindowDelegate { let screenRect: NSRect = _screen.visibleFrame let textWidthRatio: Double = min(0.8, 1.0 / (theme.isVertical ? 4 : 3) + (theme.textAttrs[.font] as! NSFont).pointSize / 144.0) textWidthLimit = ((theme.isVertical ? screenRect.height : screenRect.width) * textWidthRatio - theme.borderInsets.width * 2 - theme.fullWidth).rounded(.up) - if theme.lineLength.isNormal && theme.lineLength < textWidthLimit { + if theme.lineLength.isNormal, theme.lineLength < textWidthLimit { textWidthLimit = theme.lineLength } if view.theme.isTabular { textWidthLimit = (textWidthLimit / (theme.fullWidth * 2)).rounded(.down) * (theme.fullWidth * 2) } - view.candidateView.textContainer!.size = NSSize(width: textWidthLimit, height: .zero) - view.preeditView.textContainer!.size = NSSize(width: textWidthLimit, height: .zero) - view.pagingView.textContainer!.size = NSSize(width: textWidthLimit, height: .zero) - view.statusView.textContainer!.size = NSSize(width: textWidthLimit, height: .zero) + view.candidateView.textContainer?.size = NSSize(width: textWidthLimit, height: .zero) + view.preeditView.textContainer?.size = NSSize(width: textWidthLimit, height: .zero) + view.pagingView.textContainer?.size = NSSize(width: textWidthLimit, height: .zero) + view.statusView.textContainer?.size = NSSize(width: textWidthLimit, height: .zero) // color, opacity and transluecency alphaValue = theme.opacity @@ -2636,19 +2612,19 @@ final class SquirrelPanel: NSPanel, NSWindowDelegate { } func candidateIndex(onDirection arrowKey: SquirrelIndex) -> Int? { - guard let highlightedCandidate = highlightedCandidate, isTabular && !indexRange.isEmpty else { return nil } + guard let highlightedCandidate = highlightedCandidate, isTabular, !indexRange.isEmpty else { return nil } let currentTab: Int = view.tabularIndices[highlightedCandidate].tabNum let currentLine: Int = view.tabularIndices[highlightedCandidate].lineNum let finalLine: Int = view.tabularIndices[indexRange.count - 1].lineNum if arrowKey == (view.theme.isVertical ? .LeftKey : .DownKey) { - if highlightedCandidate == indexRange.count - 1 && isLastPage { + if highlightedCandidate == indexRange.count - 1, isLastPage { return nil } - if currentLine == finalLine && !isLastPage { + if currentLine == finalLine, !isLastPage { return indexRange.upperBound } var newIndex: Int = highlightedCandidate + 1 - while newIndex < indexRange.count && (view.tabularIndices[newIndex].lineNum == currentLine || (view.tabularIndices[newIndex].lineNum == currentLine + 1 && view.tabularIndices[newIndex].tabNum <= currentTab)) { + while newIndex < indexRange.count, view.tabularIndices[newIndex].lineNum == currentLine || (view.tabularIndices[newIndex].lineNum == currentLine + 1 && view.tabularIndices[newIndex].tabNum <= currentTab) { newIndex += 1 } if newIndex != indexRange.count || isLastPage { @@ -2660,7 +2636,7 @@ final class SquirrelPanel: NSPanel, NSWindowDelegate { return pageNum == 0 ? nil : indexRange.lowerBound - 1 } var newIndex: Int = highlightedCandidate - 1 - while newIndex > 0 && (view.tabularIndices[newIndex].lineNum == currentLine || (view.tabularIndices[newIndex].lineNum == currentLine - 1 && view.tabularIndices[newIndex].tabNum > currentTab)) { + while newIndex > 0, view.tabularIndices[newIndex].lineNum == currentLine || (view.tabularIndices[newIndex].lineNum == currentLine - 1 && view.tabularIndices[newIndex].tabNum > currentTab) { newIndex -= 1 } return newIndex + indexRange.lowerBound @@ -2728,23 +2704,23 @@ final class SquirrelPanel: NSPanel, NSWindowDelegate { let noDelay: Bool = event.modifierFlags.intersection(.deviceIndependentFlagsMask) == [.option] cursorIndex = view.index(mouseSpot: mouseLocationOutsideOfEventStream) if let cursorIndex = cursorIndex { - if cursorIndex != highlightedCandidate && cursorIndex != functionButton { + if cursorIndex != highlightedCandidate, cursorIndex != functionButton { toolTip.clear() } else if noDelay { toolTip.show() } - if 0 ..< indexRange.count ~= cursorIndex.rawValue && cursorIndex != highlightedCandidate { + if 0 ..< indexRange.count ~= cursorIndex.rawValue, cursorIndex != highlightedCandidate { if functionButton != .VoidSymbol { highlightFunctionButton(.VoidSymbol, displayToolTip: .none) } - if theme.isLinear && view.candidateInfos[cursorIndex.rawValue].isTruncated { + if theme.isLinear, view.candidateInfos[cursorIndex.rawValue].isTruncated { toolTip.show(withToolTip: view.candidateContents.mutableString.substring(with: view.candidateInfos[cursorIndex.rawValue].candidateRange), display: .now) } else { toolTip.show(withToolTip: Bundle.main.localizedString(forKey: "candidate", value: nil, table: "Tooltips"), display: .onRequest) } sectionNum = cursorIndex.rawValue / theme.pageSize inputController?.perform(action: .Highlight, onIndex: cursorIndex + indexRange.lowerBound) - } else if (cursorIndex == .PageUpKey || cursorIndex == .PageDownKey || cursorIndex == .ExpandButton || cursorIndex == .BackSpaceKey) && functionButton != cursorIndex { + } else if cursorIndex == .PageUpKey || cursorIndex == .PageDownKey || cursorIndex == .ExpandButton || cursorIndex == .BackSpaceKey, functionButton != cursorIndex { highlightFunctionButton(cursorIndex, displayToolTip: noDelay ? .now : .delayed) } } else { @@ -2765,7 +2741,7 @@ final class SquirrelPanel: NSPanel, NSWindowDelegate { if event.phase == .began { scrollLocus = .zero scrollByLine = false - } else if event.phase == .changed && scrollLocus.x.isFinite && scrollLocus.y.isFinite { + } else if event.phase == .changed, scrollLocus.x.isFinite, scrollLocus.y.isFinite { var scrollDistance: Double = .zero // determine scrolling direction by confining to sectors within ±30º of any axis if event.scrollingDeltaX.magnitude > event.scrollingDeltaY.magnitude * 3.squareRoot() { @@ -2778,7 +2754,7 @@ final class SquirrelPanel: NSPanel, NSWindowDelegate { // compare accumulated locus length against threshold and limit paging to max once switch scrollLocus { case let p where p.x > scrollThreshold: - if theme.isVertical && view.scrollView.documentVisibleRect.maxY < view.documentRect.maxY.nextDown { + if theme.isVertical, view.scrollView.documentVisibleRect.maxY < view.documentRect.maxY.nextDown { scrollByLine = true var origin: NSPoint = view.scrollView.contentView.bounds.origin origin.y += min(scrollDistance, view.documentRect.maxY - view.scrollView.documentVisibleRect.maxY) @@ -2800,7 +2776,7 @@ final class SquirrelPanel: NSPanel, NSWindowDelegate { scrollLocus = NSPoint(x: Double.infinity, y: Double.infinity) } case let p where p.x < -scrollThreshold: - if theme.isVertical && view.scrollView.documentVisibleRect.minY > view.documentRect.minY.nextUp { + if theme.isVertical, view.scrollView.documentVisibleRect.minY > view.documentRect.minY.nextUp { scrollByLine = true var origin: NSPoint = view.scrollView.contentView.bounds.origin origin.y += max(scrollDistance, view.documentRect.minY - view.scrollView.documentVisibleRect.minY) @@ -2843,7 +2819,7 @@ final class SquirrelPanel: NSPanel, NSWindowDelegate { // apply new foreground colors for i in 0 ..< theme.pageSize { let priorCandidate: Int = i + priorSectionNum * theme.pageSize - if (view.sectionNum != priorSectionNum || priorCandidate == priorHilitedCandidate) && priorCandidate < indexRange.count { + if view.sectionNum != priorSectionNum || priorCandidate == priorHilitedCandidate, priorCandidate < indexRange.count { let labelColor = priorCandidate == priorHilitedCandidate && view.sectionNum == priorSectionNum ? theme.labelForeColor : theme.dimmedLabelForeColor! view.candidateContents.addAttribute(.foregroundColor, value: labelColor, range: view.candidateInfos[priorCandidate].labelRange) if priorCandidate == priorHilitedCandidate { @@ -2852,7 +2828,7 @@ final class SquirrelPanel: NSPanel, NSWindowDelegate { } } let newCandidate: Int = i + view.sectionNum * theme.pageSize - if (view.sectionNum != priorSectionNum || newCandidate == highlightedCandidate) && newCandidate < indexRange.count { + if view.sectionNum != priorSectionNum || newCandidate == highlightedCandidate, newCandidate < indexRange.count { view.candidateContents.addAttribute(.foregroundColor, value: newCandidate == highlightedCandidate ? theme.hilitedLabelForeColor : theme.labelForeColor, range: view.candidateInfos[newCandidate].labelRange) if newCandidate == highlightedCandidate { view.candidateContents.addAttribute(.foregroundColor, value: theme.hilitedTextForeColor, range: view.candidateInfos[newCandidate].textRange) @@ -2908,7 +2884,7 @@ final class SquirrelPanel: NSPanel, NSWindowDelegate { // Get the window size, it will be the dirtyRect in SquirrelView.drawRect private func show() { - if !needsRedraw && !initPosition { + if !needsRedraw, !initPosition { isVisible ? displayIfNeeded() : orderFront(nil); return } // Break line if the text is too long, based on screen size. @@ -2921,13 +2897,13 @@ final class SquirrelPanel: NSPanel, NSWindowDelegate { let sweepVertical: Bool = IbeamRect.width > IbeamRect.height var contentRect: NSRect = view.contentRect // fixed line length (text width), but not applicable to status message - if theme.lineLength.isNormal && view.statusView.isHidden { + if theme.lineLength.isNormal, view.statusView.isHidden { contentRect.size.width = textWidthLimit } // remember panel size (fix the top leading anchor of the panel in screen coordiantes) // but only when the text would expand upstreams (towards the leading and/or top edges) - if theme.rememberSize && view.statusView.isHidden { - if theme.lineLength.isFinite && !theme.lineLength.isNormal { + if theme.rememberSize, view.statusView.isHidden { + if theme.lineLength.isFinite, !theme.lineLength.isNormal { let attained: Bool = theme.isVertical ? sweepVertical ? (IbeamRect.minY - max(contentRect.width, maxSizeAttained.width) - border.width - (theme.fullWidth * 0.5).rounded(.down) < screenRect.minY.nextUp) : (IbeamRect.minY - Self.kOffsetGap - screenRect.height * textWidthRatio - border.width * 2 - theme.fullWidth < screenRect.minY.nextUp) @@ -3027,7 +3003,7 @@ final class SquirrelPanel: NSPanel, NSWindowDelegate { windowRect = _screen.backingAlignedRect(windowRect.intersection(screenRect), options: [.alignAllEdgesNearest]) setFrame(windowRect, display: true) - contentView!.setBoundsOrigin(theme.isVertical ? NSPoint(x: -windowRect.width, y: .zero) : .zero) + contentView?.setBoundsOrigin(theme.isVertical ? NSPoint(x: -windowRect.width, y: .zero) : .zero) let viewRect: NSRect = contentView!.bounds.integral(options: [.alignAllEdgesNearest]) view.frame = viewRect if !view.statusView.isHidden { @@ -3108,7 +3084,7 @@ final class SquirrelPanel: NSPanel, NSWindowDelegate { let theme: SquirrelTheme = view.theme var rulerAttrsPreedit: NSParagraphStyle? let priorSize: NSSize = !view.candidateInfos.isEmpty || !view.preeditView.isHidden ? view.contentRect.size : .zero - if (indexRange.isEmpty || !updateCandidates) && !preedit.isEmpty && view.preeditContents.length > 0 { + if indexRange.isEmpty || !updateCandidates, !preedit.isEmpty, view.preeditContents.length > 0 { rulerAttrsPreedit = view.preeditContents.attribute(.paragraphStyle, at: 0, effectiveRange: nil) as? NSParagraphStyle } if updateCandidates { @@ -3143,7 +3119,7 @@ final class SquirrelPanel: NSPanel, NSWindowDelegate { view.preeditContents.addAttribute(.paragraphStyle, value: rulerAttrsPreedit, range: NSRange(location: 0, length: view.preeditContents.length)) } - if updateCandidates && indexRange.isEmpty { + if updateCandidates, indexRange.isEmpty { sectionNum = 0 } else { view.setPreedit(hilitedPreeditRange: selRange) @@ -3210,13 +3186,13 @@ final class SquirrelPanel: NSPanel, NSWindowDelegate { // store final in-candidate locations of label, text, and comment textRange = candidate.mutableString.range(of: text) - if idx > 0 && col == 0 && theme.isLinear && !candidateInfos[idx - 1].isTruncated { + if idx > 0, col == 0, theme.isLinear, !candidateInfos[idx - 1].isTruncated { view.candidateContents.mutableString.append("\n") } let candidateStart: Int = view.candidateContents.length view.candidateContents.append(candidate) // for linear layout, middle-truncate candidates that are longer than one line - if theme.isLinear && view.candidateView.blockRect(for: NSRange(location: candidateStart, length: candidate.length)).width > textWidthLimit - theme.fullWidth * (theme.isTabular ? 3 : 2) { + if theme.isLinear, view.candidateView.blockRect(for: NSRange(location: candidateStart, length: candidate.length)).width > textWidthLimit - theme.fullWidth * (theme.isTabular ? 3 : 2) { candidateInfos.append(SquirrelCandidateInfo(location: candidateStart, length: view.candidateContents.length - candidateStart, text: textRange.location, comment: textRange.upperBound, idx: idx, col: col, isTruncated: true)) if idx < indexRange.count - 1 || theme.isTabular || theme.showPaging { view.candidateContents.mutableString.append("\n") @@ -3253,7 +3229,7 @@ final class SquirrelPanel: NSPanel, NSWindowDelegate { view.estimateBounds(onScreen: _screen.visibleFrame, withPreedit: !preedit.isEmpty, candidates: candidateInfos, paging: !indexRange.isEmpty && (theme.isTabular || theme.showPaging)) let textWidth: Double = view.contentRect.width.clamp(min: maxSizeAttained.width, max: textWidthLimit) // right-align the backward delete symbol - if !preedit.isEmpty && (rulerAttrsPreedit == nil || rulerAttrsPreedit!.tabStops[0].location < textWidth.nextDown) { + if !preedit.isEmpty, rulerAttrsPreedit == nil || rulerAttrsPreedit!.tabStops[0].location < textWidth.nextDown { if rulerAttrsPreedit == nil { view.preeditContents.replaceCharacters(in: NSRange(location: view.preeditContents.length - 2, length: 1), with: "\t") } @@ -3261,7 +3237,7 @@ final class SquirrelPanel: NSPanel, NSWindowDelegate { rulerAttrs.tabStops = [NSTextTab(textAlignment: .right, location: textWidth)] view.preeditContents.addAttribute(.paragraphStyle, value: rulerAttrs, range: NSRange(location: 0, length: view.preeditContents.length)) } - if !theme.isLinear && theme.showPaging { + if !theme.isLinear, theme.showPaging { let rulerAttrsPaging: NSMutableParagraphStyle = theme.pagingParagraphStyle.mutableCopy() view.pagingContents.replaceCharacters(in: NSRange(location: 1, length: 1), with: "\t") view.pagingContents.replaceCharacters(in: NSRange(location: view.pagingContents.length - 2, length: 1), with: "\t") @@ -3328,6 +3304,15 @@ final class SquirrelPanel: NSPanel, NSWindowDelegate { if update { updateDisplayParameters() } } + private func getLocked() { + guard view.theme.isTabular else { return } + let userConfig = SquirrelConfig(.user) + view.isLocked = userConfig.boolValue(forOption: "var/option/_isLockedTabular") + if view.isLocked { view.isExpanded = userConfig.boolValue(forOption: "var/option/_isExpandedTabular") } + userConfig.close() + view.sectionNum = 0 + } + func loadConfig(_ config: SquirrelConfig) { SquirrelView.lightTheme.updateTheme(withConfig: config, styleOptions: optionSwitcher.optionStates, scriptVariant: optionSwitcher.currentScriptVariant) if #available(macOS 10.14, *) {