From 7315da18f2b46e3b909d4f4fbeae0a080ccf9fcd Mon Sep 17 00:00:00 2001 From: WYJRichhhhh Date: Wed, 17 Jun 2026 10:23:37 +0800 Subject: [PATCH 1/3] feat: add ReservedProperty protocol for preserving reserved comments Introduce ReservedProperty to track and preserve reserved comments across Rime updates, and avoid clearing them on caret/selection-only updates. Co-Authored-By: Claude Opus 4.8 (1M context) --- Squirrel.xcodeproj/project.pbxproj | 4 + sources/Main.swift | 10 +- sources/ReservedProperty.swift | 113 ++++++++++++++++++++++ sources/SquirrelApplicationDelegate.swift | 20 ++++ sources/SquirrelInputController.swift | 47 ++++++++- sources/SquirrelPanel.swift | 23 ++++- sources/SquirrelTheme.swift | 9 ++ 7 files changed, 223 insertions(+), 3 deletions(-) create mode 100644 sources/ReservedProperty.swift diff --git a/Squirrel.xcodeproj/project.pbxproj b/Squirrel.xcodeproj/project.pbxproj index 148fe3f37..813deaeb0 100644 --- a/Squirrel.xcodeproj/project.pbxproj +++ b/Squirrel.xcodeproj/project.pbxproj @@ -85,6 +85,7 @@ 7B5488C91D2DACDF0056A1BE /* symbols.yaml in Copy Shared Support Files */ = {isa = PBXBuildFile; fileRef = 7B54883B1D2DAAD10056A1BE /* symbols.yaml */; }; B3216E5C2BF438F800E292D2 /* rime.pdf in Resources */ = {isa = PBXBuildFile; fileRef = B3216E5B2BF438F800E292D2 /* rime.pdf */; }; B35D2FE82BF00839009D156B /* BridgingFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B35D2FE72BF00839009D156B /* BridgingFunctions.swift */; }; + B3A001022F260100009D156B /* ReservedProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3A001012F260100009D156B /* ReservedProperty.swift */; }; B38E9B912BE9AE1E0036ABEF /* SquirrelApplicationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B38E9B902BE9AE1E0036ABEF /* SquirrelApplicationDelegate.swift */; }; B38E9B952BEAFEFD0036ABEF /* SquirrelInputController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B38E9B942BEAFEFD0036ABEF /* SquirrelInputController.swift */; }; B39771232BECEA150093A49B /* MacOSKeyCodes.swift in Sources */ = {isa = PBXBuildFile; fileRef = B39771222BECEA150093A49B /* MacOSKeyCodes.swift */; }; @@ -305,6 +306,7 @@ B3216E5B2BF438F800E292D2 /* rime.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; name = rime.pdf; path = resources/rime.pdf; sourceTree = ""; }; B32B80772BE7FAA200FCF3BC /* Squirrel.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; name = Squirrel.entitlements; path = resources/Squirrel.entitlements; sourceTree = ""; }; B35D2FE72BF00839009D156B /* BridgingFunctions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BridgingFunctions.swift; path = sources/BridgingFunctions.swift; sourceTree = ""; }; + B3A001012F260100009D156B /* ReservedProperty.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ReservedProperty.swift; path = sources/ReservedProperty.swift; sourceTree = ""; }; B38E9B8F2BE9AE1E0036ABEF /* Squirrel-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "Squirrel-Bridging-Header.h"; path = "sources/Squirrel-Bridging-Header.h"; sourceTree = ""; }; B38E9B902BE9AE1E0036ABEF /* SquirrelApplicationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SquirrelApplicationDelegate.swift; path = sources/SquirrelApplicationDelegate.swift; sourceTree = ""; }; B38E9B942BEAFEFD0036ABEF /* SquirrelInputController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SquirrelInputController.swift; path = sources/SquirrelInputController.swift; sourceTree = ""; }; @@ -351,6 +353,7 @@ B39771282BEDAF4A0093A49B /* SquirrelView.swift */, B39771242BED899F0093A49B /* SquirrelConfig.swift */, B35D2FE72BF00839009D156B /* BridgingFunctions.swift */, + B3A001012F260100009D156B /* ReservedProperty.swift */, B38E9B8F2BE9AE1E0036ABEF /* Squirrel-Bridging-Header.h */, ); name = Sources; @@ -623,6 +626,7 @@ B38E9B912BE9AE1E0036ABEF /* SquirrelApplicationDelegate.swift in Sources */, B39771272BED9B250093A49B /* SquirrelTheme.swift in Sources */, B35D2FE82BF00839009D156B /* BridgingFunctions.swift in Sources */, + B3A001022F260100009D156B /* ReservedProperty.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/sources/Main.swift b/sources/Main.swift index 01faeeb43..847bec8a4 100644 --- a/sources/Main.swift +++ b/sources/Main.swift @@ -18,7 +18,15 @@ struct SquirrelApp { static let appDir = "/Library/Input Library/Squirrel.app".withCString { dir in URL(fileURLWithFileSystemRepresentation: dir, isDirectory: false, relativeTo: nil) } - static let logDir = FileManager.default.temporaryDirectory.appending(component: "rime.squirrel", directoryHint: .isDirectory) + // Use ~/Library/Logs/Squirrel/ instead of TMPDIR so that the log files are + // visible from a normal user shell (the IMK sandbox redirects TMPDIR to a + // location that is not reachable outside the sandbox, which makes debugging + // very hard — see https://github.com/rime/squirrel/issues for context). + static let logDir = if let pwuid = getpwuid(getuid()) { + URL(fileURLWithFileSystemRepresentation: pwuid.pointee.pw_dir, isDirectory: true, relativeTo: nil).appending(components: "Library", "Logs", "Squirrel") + } else { + try! FileManager.default.url(for: .libraryDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent("Logs/Squirrel", isDirectory: true) + } // swiftlint:disable:next cyclomatic_complexity static func main() { diff --git a/sources/ReservedProperty.swift b/sources/ReservedProperty.swift new file mode 100644 index 000000000..621f58e24 --- /dev/null +++ b/sources/ReservedProperty.swift @@ -0,0 +1,113 @@ +// +// ReservedProperty.swift +// Squirrel +// +// Cross-frontend protocol for plugin -> frontend coordination over +// librime's notification_handler. See rime/squirrel#1124. +// +// ┌──────────────────────────── flow ────────────────────────────┐ +// │ Plugin ctx->set_property("_", "") │ +// │ librime notification_handler(type:"property", │ +// │ value:"_=") │ +// │ Squirrel ApplicationDelegate parses prefix → dispatches to │ +// │ the active InputController via handleReservedProperty│ +// └──────────────────────────────────────────────────────────────┘ +// +// The leading-underscore namespace marks the key as part of this +// reserved protocol. Plugin-private keys SHOULD use a "/key" +// namespace instead so they will never collide with reserved keys. +// +// Value encoding: URL-style query string (RFC 3986 application/x-www- +// form-urlencoded). Picked over JSON / YAML because: +// - Builtin parser is available on every target frontend +// (Swift URLComponents / Win HTTP / Lua / C++ Boost) +// - weasel previously used JSON for IPC and dropped it on +// performance grounds (rime/squirrel#1124, fxliang 2026-05-27) +// - Forward-compatible: unknown fields are preserved and ignored +// +// Backward-compatible shorthand: +// A bare value without "=" is treated as { "indices": "" }, +// so the historical "_comment_highlight=0,2" form still works. + +import Foundation + +/// Reserved property keys recognised by Squirrel. Plugins targeting any +/// Rime frontend should only use keys listed here; unrecognised "_*" +/// keys are silently ignored so the table can grow without breaking +/// older Squirrel builds. +enum ReservedPropertyKey: String { + /// State - candidates at these indices should render their comment + /// with `accent_text_color` from the active color scheme. + /// Fields: `indices` (comma-separated non-negative integers) + case commentHighlight = "_comment_highlight" + + /// State - candidates at these indices should render their comment + /// with `warning_text_color` from the active color scheme. + /// Fields: `indices` (comma-separated non-negative integers) + case commentWarning = "_comment_warning" + + /// Action - the candidate panel should be refreshed because an async + /// task (network / inference / ...) has produced new candidates. + /// Optional fields: `source` (plugin codename), `kind` (full|partial) + case refreshUI = "_refresh_ui" + + /// `true` when the key represents a one-shot action that should be + /// applied and forgotten. `false` when it represents a piece of + /// composition-scoped state that sticks until the next overwrite. + var isAction: Bool { + switch self { + case .refreshUI: + return true + case .commentHighlight, .commentWarning: + return false + } + } +} + +/// Parsed representation of a reserved-property value. +/// +/// Use `fields[name]` for a single scalar (e.g. `source`, `kind`) and +/// `indices()` for the conventional comma-separated non-negative integer +/// list that several keys carry. +struct ReservedPropertyValue { + let fields: [String: String] + + static let empty = ReservedPropertyValue(fields: [:]) + + /// Parses raw value strings written by plugins. + /// + /// Accepts two shapes: + /// 1. URL-style query string: `indices=0,2&source=ai_predict` + /// 2. Bare comma list: `0,2` (normalised to `indices=0,2`) + /// + /// Both shapes round-trip through the same `fields[name]` API so + /// callers never need to know which one the plugin used. + static func parse(_ raw: String) -> ReservedPropertyValue { + guard !raw.isEmpty else { return .empty } + if !raw.contains("=") { + return ReservedPropertyValue(fields: ["indices": raw]) + } + // URLComponents needs a scheme-less URL with a leading "?". + guard let queryItems = URLComponents(string: "?\(raw)")?.queryItems else { + return .empty + } + let pairs = queryItems.map { ($0.name, $0.value ?? "") } + let dict = Dictionary(pairs, uniquingKeysWith: { _, new in new }) + return ReservedPropertyValue(fields: dict) + } + + /// Extracts a non-negative integer index list from the conventional + /// `indices` field. Whitespace and malformed entries are skipped so + /// stray spaces in hand-written plugin code don't break rendering. + func indices() -> Set { + guard let raw = fields["indices"] else { return [] } + var out = Set() + for part in raw.split(separator: ",") { + let trimmed = part.trimmingCharacters(in: .whitespaces) + if let n = Int(trimmed), n >= 0 { + out.insert(n) + } + } + return out + } +} diff --git a/sources/SquirrelApplicationDelegate.swift b/sources/SquirrelApplicationDelegate.swift index 27f5f02ae..bcecf47a6 100644 --- a/sources/SquirrelApplicationDelegate.swift +++ b/sources/SquirrelApplicationDelegate.swift @@ -18,6 +18,7 @@ final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUSta let rimeAPI: RimeApi_stdbool = rime_get_api_stdbool().pointee var config: SquirrelConfig? var panel: SquirrelPanel? + weak var activeInputController: SquirrelInputController? var enableNotifications = false var showStatusIcon: Bool = true var statusItem: NSStatusItem? @@ -139,6 +140,11 @@ final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUSta func setupRime() { createDirIfNotExist(path: SquirrelApp.userDir) createDirIfNotExist(path: SquirrelApp.logDir) + // librime 不会把 log_dir 透传给插件 dylib(每个插件 dylib 各自静态 + // 链接了一份 glog,与主进程实例互不可见,参见 rime/librime#983)。 + // 我们在这里把日志目录通过环境变量暴露出来,让插件初始化它那一份 + // glog 实例时可以输出到与主进程相同的目录,方便用户集中查看。 + setenv("RIME_LOG_DIR", SquirrelApp.logDir.path(), 1) // swiftlint:disable identifier_name let notification_handler: @convention(c) (UnsafeMutableRawPointer?, RimeSessionId, UnsafePointer?, UnsafePointer?) -> Void = notificationHandler let context_object = Unmanaged.passUnretained(self).toOpaque() @@ -274,6 +280,20 @@ private func notificationHandler(contextObject: UnsafeMutableRawPointer?, sessio let messageType = messageTypeC.map { String(cString: $0) } let messageValue = messageValueC.map { String(cString: $0) } + // Reserved property keys: cross-frontend protocol per rime/squirrel#1124. + // librime forwards every ctx->set_property() as ("property", "="). + // We honour keys with the leading "_" namespace, treating them as a + // contract between plugins and frontends. Unrecognized "_*" keys are + // silently ignored, so adding a new reserved key is backward-compatible. + if messageType == "property", let messageValue = messageValue, + let eq = messageValue.firstIndex(of: "="), messageValue.first == "_" { + let key = String(messageValue[.. = [] + private(set) var warningCommentIndices: Set = [] + + /// Dispatched on the main queue from notificationHandler when librime + /// forwards a reserved property key (leading underscore). The wire + /// format and reserved-key table are documented in ReservedProperty.swift + /// (rime/squirrel#1124). Unknown keys are silently ignored so the table + /// can grow over time without breaking older Squirrel builds. + func handleReservedProperty(key rawKey: String, value rawValue: String, for sessionId: RimeSessionId) { + guard session == sessionId, session != 0, rimeAPI.find_session(session) else { return } + guard let key = ReservedPropertyKey(rawValue: rawKey) else { return } + let parsed = ReservedPropertyValue.parse(rawValue) + switch key { + case .commentHighlight: + accentCommentIndices = parsed.indices() + case .commentWarning: + warningCommentIndices = parsed.indices() + case .refreshUI: + // Preserve the indices just set by _comment_highlight/_comment_warning; + // this is the render pass that paints them. + rimeUpdate(clearReservedComments: false) + } + } + deinit { destroySession() } @@ -466,8 +500,19 @@ private extension SquirrelInputController { } // swiftlint:disable:next cyclomatic_complexity - func rimeUpdate() { + // `clearReservedComments` defaults to true so every state-changing update + // (keystroke, paging, caret move, chord release, ascii toggle) drops the + // reserved-comment indices set by the *previous* Compose(). They are only + // preserved for the `_refresh_ui`-driven render (see handleReservedProperty), + // which is the pass that actually paints the indices the plugin just set. + // Without this, stale indices from an earlier keystroke colour the wrong + // candidates in the new list, or linger after the plugin stops highlighting. + func rimeUpdate(clearReservedComments: Bool = true) { // print("[DEBUG] rimeUpdate") + if clearReservedComments { + accentCommentIndices = [] + warningCommentIndices = [] + } rimeConsumeCommittedText() var status = RimeStatus_stdbool.rimeStructInit() diff --git a/sources/SquirrelPanel.swift b/sources/SquirrelPanel.swift index 7742b2ebb..61216bcce 100644 --- a/sources/SquirrelPanel.swift +++ b/sources/SquirrelPanel.swift @@ -240,7 +240,28 @@ final class SquirrelPanel: NSPanel { } } for range in line.string.ranges(of: /\[comment\]/) { - line.addAttributes(commentAttrs, range: convert(range: range, in: line.string)) + let convertedRange = convert(range: range, in: line.string) + // Apply semantic accent/warning colors only for non-highlighted rows; + // when the row is highlighted, the highlighted comment color wins so + // selection state stays unambiguous. Indices come from reserved + // property keys (_comment_highlight / _comment_warning) maintained + // on the input controller; see rime/squirrel#1124. + let semanticColor: NSColor? = if i == index { + nil + } else if inputController?.accentCommentIndices.contains(i) == true { + theme.accentCommentTextColor + } else if inputController?.warningCommentIndices.contains(i) == true { + theme.warningCommentTextColor + } else { + nil + } + if let semanticColor = semanticColor { + var override = commentAttrs + override[.foregroundColor] = semanticColor + line.addAttributes(override, range: convertedRange) + } else { + line.addAttributes(commentAttrs, range: convertedRange) + } } line.mutableString.replaceOccurrences(of: "[label]", with: label, range: NSRange(location: 0, length: line.length)) let labeledLine = line.copy() as! NSAttributedString diff --git a/sources/SquirrelTheme.swift b/sources/SquirrelTheme.swift index feacfccb4..210ea0ca6 100644 --- a/sources/SquirrelTheme.swift +++ b/sources/SquirrelTheme.swift @@ -47,6 +47,13 @@ final class SquirrelTheme { private var highlightedCandidateLabelColor: NSColor? = .secondaryLabelColor private var commentTextColor: NSColor? = .secondaryLabelColor private var highlightedCommentTextColor: NSColor? = .secondaryLabelColor + // Semantic comment colors (proposal in rime/squirrel#1124). + // Plugins / translators don't pick literal RGB values; instead they tag + // candidates by semantic role and the active color scheme owns the actual + // values. Both default to nil → fall back to commentTextColor at render + // time, so existing themes need no change. + private(set) var accentCommentTextColor: NSColor? + private(set) var warningCommentTextColor: NSColor? private(set) var cornerRadius: CGFloat = 0 private(set) var hilitedCornerRadius: CGFloat = 0 @@ -243,6 +250,8 @@ final class SquirrelTheme { highlightedCandidateLabelColor = config.getColor("\(prefix)/hilited_candidate_label_color", inSpace: colorSpace) commentTextColor = config.getColor("\(prefix)/comment_text_color", inSpace: colorSpace) highlightedCommentTextColor = config.getColor("\(prefix)/hilited_comment_text_color", inSpace: colorSpace) + accentCommentTextColor = config.getColor("\(prefix)/accent_text_color", inSpace: colorSpace) + warningCommentTextColor = config.getColor("\(prefix)/warning_text_color", inSpace: colorSpace) // the following per-color-scheme configurations, if exist, will // override configurations with the same name under the global 'style' From b983b60dd5f0a385b8819334917127169e0e7552 Mon Sep 17 00:00:00 2001 From: WYJRichhhhh Date: Wed, 17 Jun 2026 12:09:09 +0800 Subject: [PATCH 2/3] refactor: address review on ReservedProperty protocol - Document leading-underscore keys as librime transient properties whose lifetime is bound to the active input schema (per @lotem, context.cc). - Rename the bare-value shorthand field from `indices` to the neutral `value` so non-index keys can reuse the same scalar shorthand. Co-Authored-By: Claude Opus 4.8 (1M context) --- sources/ReservedProperty.swift | 36 +++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/sources/ReservedProperty.swift b/sources/ReservedProperty.swift index 621f58e24..07d18dd0d 100644 --- a/sources/ReservedProperty.swift +++ b/sources/ReservedProperty.swift @@ -13,9 +13,12 @@ // │ the active InputController via handleReservedProperty│ // └──────────────────────────────────────────────────────────────┘ // -// The leading-underscore namespace marks the key as part of this -// reserved protocol. Plugin-private keys SHOULD use a "/key" -// namespace instead so they will never collide with reserved keys. +// Leading-underscore keys are librime "transient properties": their +// lifetime is bound to the active input schema in the context and they +// are cleared when the schema changes (see librime context.cc). This +// protocol reserves a subset of that namespace for cross-frontend use. +// Plugin-private keys SHOULD use a "/key" namespace instead so +// they will never collide with reserved keys. // // Value encoding: URL-style query string (RFC 3986 application/x-www- // form-urlencoded). Picked over JSON / YAML because: @@ -26,7 +29,7 @@ // - Forward-compatible: unknown fields are preserved and ignored // // Backward-compatible shorthand: -// A bare value without "=" is treated as { "indices": "" }, +// A bare value without "=" is treated as { "value": "" }, // so the historical "_comment_highlight=0,2" form still works. import Foundation @@ -38,12 +41,12 @@ import Foundation enum ReservedPropertyKey: String { /// State - candidates at these indices should render their comment /// with `accent_text_color` from the active color scheme. - /// Fields: `indices` (comma-separated non-negative integers) + /// Fields: `value` (comma-separated non-negative integers) case commentHighlight = "_comment_highlight" /// State - candidates at these indices should render their comment /// with `warning_text_color` from the active color scheme. - /// Fields: `indices` (comma-separated non-negative integers) + /// Fields: `value` (comma-separated non-negative integers) case commentWarning = "_comment_warning" /// Action - the candidate panel should be refreshed because an async @@ -68,24 +71,29 @@ enum ReservedPropertyKey: String { /// /// Use `fields[name]` for a single scalar (e.g. `source`, `kind`) and /// `indices()` for the conventional comma-separated non-negative integer -/// list that several keys carry. +/// list carried in the neutral `value` field. struct ReservedPropertyValue { let fields: [String: String] + /// Field name a bare (no "=") value is stored under, and the field + /// `indices()` reads from. Neutral so keys that aren't index lists can + /// reuse the same shorthand for their own scalar payload. + static let defaultField = "value" + static let empty = ReservedPropertyValue(fields: [:]) /// Parses raw value strings written by plugins. /// /// Accepts two shapes: - /// 1. URL-style query string: `indices=0,2&source=ai_predict` - /// 2. Bare comma list: `0,2` (normalised to `indices=0,2`) + /// 1. URL-style query string: `value=0,2&source=ai_predict` + /// 2. Bare comma list: `0,2` (normalised to `value=0,2`) /// /// Both shapes round-trip through the same `fields[name]` API so /// callers never need to know which one the plugin used. static func parse(_ raw: String) -> ReservedPropertyValue { guard !raw.isEmpty else { return .empty } if !raw.contains("=") { - return ReservedPropertyValue(fields: ["indices": raw]) + return ReservedPropertyValue(fields: [defaultField: raw]) } // URLComponents needs a scheme-less URL with a leading "?". guard let queryItems = URLComponents(string: "?\(raw)")?.queryItems else { @@ -96,11 +104,11 @@ struct ReservedPropertyValue { return ReservedPropertyValue(fields: dict) } - /// Extracts a non-negative integer index list from the conventional - /// `indices` field. Whitespace and malformed entries are skipped so - /// stray spaces in hand-written plugin code don't break rendering. + /// Extracts a non-negative integer index list from the neutral `value` + /// field. Whitespace and malformed entries are skipped so stray spaces + /// in hand-written plugin code don't break rendering. func indices() -> Set { - guard let raw = fields["indices"] else { return [] } + guard let raw = fields[Self.defaultField] else { return [] } var out = Set() for part in raw.split(separator: ",") { let trimmed = part.trimmingCharacters(in: .whitespaces) From 822f1977b1daccbc27ff4bdeacadddd63e51ea04 Mon Sep 17 00:00:00 2001 From: WYJRichhhhh Date: Wed, 17 Jun 2026 13:18:58 +0800 Subject: [PATCH 3/3] style: fix swiftlint identifier_name warnings Rename short local bindings flagged by swiftlint and reposition a cyclomatic_complexity disable comment so it sits directly above the function declaration: - ReservedProperty.swift: n -> index - SquirrelApplicationDelegate.swift: eq -> eqIndex - SquirrelInputController.swift: move disable directive to the decl No behavioural change. Co-Authored-By: Claude Opus 4.8 (1M context) --- sources/ReservedProperty.swift | 4 ++-- sources/SquirrelApplicationDelegate.swift | 7 ++++--- sources/SquirrelInputController.swift | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/sources/ReservedProperty.swift b/sources/ReservedProperty.swift index 07d18dd0d..cdb3d021c 100644 --- a/sources/ReservedProperty.swift +++ b/sources/ReservedProperty.swift @@ -112,8 +112,8 @@ struct ReservedPropertyValue { var out = Set() for part in raw.split(separator: ",") { let trimmed = part.trimmingCharacters(in: .whitespaces) - if let n = Int(trimmed), n >= 0 { - out.insert(n) + if let index = Int(trimmed), index >= 0 { + out.insert(index) } } return out diff --git a/sources/SquirrelApplicationDelegate.swift b/sources/SquirrelApplicationDelegate.swift index bcecf47a6..de62840a3 100644 --- a/sources/SquirrelApplicationDelegate.swift +++ b/sources/SquirrelApplicationDelegate.swift @@ -275,6 +275,7 @@ extension RimeStringSlice { } } +// swiftlint:disable:next cyclomatic_complexity private func notificationHandler(contextObject: UnsafeMutableRawPointer?, sessionId: RimeSessionId, messageTypeC: UnsafePointer?, messageValueC: UnsafePointer?) { let delegate: SquirrelApplicationDelegate = Unmanaged.fromOpaque(contextObject!).takeUnretainedValue() @@ -286,9 +287,9 @@ private func notificationHandler(contextObject: UnsafeMutableRawPointer?, sessio // contract between plugins and frontends. Unrecognized "_*" keys are // silently ignored, so adding a new reserved key is backward-compatible. if messageType == "property", let messageValue = messageValue, - let eq = messageValue.firstIndex(of: "="), messageValue.first == "_" { - let key = String(messageValue[..