Skip to content

Commit 40ab11a

Browse files
committed
fix: preserve composition across input mode switches
1 parent 07ced5b commit 40ab11a

4 files changed

Lines changed: 88 additions & 5 deletions

File tree

Core/Sources/ConverterServer/main.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,6 @@ private final class ConverterServer: NSObject, ConverterServerXPCProtocol, @unch
326326
submitSelectedCandidate(manager: manager, leftSideContext: request.leftSideContext, effects: &effects)
327327
manager.requestResettingSelection()
328328
case .selectInputLanguage(let language):
329-
manager.stopComposition()
330329
inputLanguage = language
331330
effects.append(.switchInputLanguage(language))
332331
case .commitMarkedTextAndSelectInputLanguage(let language):

Core/Tests/CoreTests/InputUtilsTests/ControlShortcutRoutingTests.swift

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,54 @@ private func makeControlEvent(
265265
}
266266
}
267267

268+
@Test func testEisuKeepsCompositionWhenSwitchingToEnglish() {
269+
let event = makeControlEvent(
270+
logicalKey: nil,
271+
characters: nil,
272+
modifiers: [],
273+
keyCode: 102
274+
)
275+
let composingStates: [InputState] = [.composing, .previewing, .replaceSuggestion]
276+
for state in composingStates {
277+
let (action, callback) = state.event(
278+
eventCore: event,
279+
userAction: .英数,
280+
inputLanguage: .japanese,
281+
liveConversionEnabled: false,
282+
enableDebugWindow: false,
283+
enableSuggestion: false
284+
)
285+
guard case .selectInputLanguage(.english) = action, case .fallthrough = callback else {
286+
Issue.record("Expected Eisu in \(state) to keep composition while switching, got \(action), \(callback)")
287+
return
288+
}
289+
}
290+
}
291+
292+
@Test func testKanaDoesNotDropJapaneseComposition() {
293+
let event = makeControlEvent(
294+
logicalKey: nil,
295+
characters: nil,
296+
modifiers: [],
297+
keyCode: 104
298+
)
299+
let composingStates: [InputState] = [.composing, .previewing, .selecting, .replaceSuggestion]
300+
for state in composingStates {
301+
let (action, callback) = state.event(
302+
eventCore: event,
303+
userAction: .かな,
304+
inputLanguage: .japanese,
305+
liveConversionEnabled: false,
306+
enableDebugWindow: false,
307+
enableSuggestion: false
308+
)
309+
guard case .selectInputLanguage(.japanese) = action, case .fallthrough = callback else {
310+
Issue.record("Expected Kana in Japanese \(state) to keep composition, got \(action), \(callback)")
311+
return
312+
}
313+
}
314+
}
315+
268316
@Test func testNonModifierUnknownStillFallsThroughDuringComposing() {
269317
// Ctrlを伴わない.unknownは従来通りfallthroughされる(既存挙動の回帰防止)
270318
let bareEvent = makeControlEvent(

azooKeyMac/InputController/azooKeyMacInputController.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,6 @@ class azooKeyMacInputController: IMKInputController, NSMenuItemValidation { // s
241241
if self.inputLanguage == .japanese {
242242
self.inputLanguage = .english
243243
self.segmentsManager.stopJapaneseInput()
244-
self.discardConverterServerComposition()
245244
self.refreshCandidateWindow()
246245
self.refreshPredictionWindow()
247246
}
@@ -509,7 +508,6 @@ class azooKeyMacInputController: IMKInputController, NSMenuItemValidation { // s
509508
case .english:
510509
client.selectMode("dev.ensan.inputmethod.azooKeyMac.Roman")
511510
self.segmentsManager.stopJapaneseInput()
512-
self.discardConverterServerComposition()
513511
case .japanese:
514512
client.selectMode("dev.ensan.inputmethod.azooKeyMac.Japanese")
515513
}

azooKeyMac/Windows/ConfigWindow.swift

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ struct ConfigWindow: View {
6565
}
6666

6767
private var azooKeyApplicationSupportDirectoryURL: URL {
68+
AppGroup.applicationSupportDirectoryURL()
69+
}
70+
71+
private var legacyAzooKeyApplicationSupportDirectoryURL: URL {
6872
if #available(macOS 13, *) {
6973
URL.applicationSupportDirectory
7074
.appending(path: "azooKey", directoryHint: .isDirectory)
@@ -80,6 +84,12 @@ struct ConfigWindow: View {
8084
)
8185
}
8286

87+
private var legacyDebugTypoCorrectionModelDirectoryURL: URL {
88+
DebugTypoCorrectionWeights.modelDirectoryURL(
89+
azooKeyApplicationSupportDirectoryURL: self.legacyAzooKeyApplicationSupportDirectoryURL
90+
)
91+
}
92+
8393
private var debugTypoCorrectionStatusText: String {
8494
if self.debugTypoCorrectionDownloadInProgress {
8595
return "ダウンロード中..."
@@ -97,8 +107,13 @@ struct ConfigWindow: View {
97107
@MainActor
98108
private func refreshDebugTypoCorrectionState() async {
99109
let modelDirectoryURL = self.debugTypoCorrectionModelDirectoryURL
100-
let state = await Task.detached(priority: .utility) {
101-
DebugTypoCorrectionWeights.state(modelDirectoryURL: modelDirectoryURL)
110+
let legacyModelDirectoryURL = self.legacyDebugTypoCorrectionModelDirectoryURL
111+
let state = await Task.detached(priority: .utility) { () -> DebugTypoCorrectionState in
112+
Self.migrateLegacyDebugTypoCorrectionWeightsIfNeeded(
113+
from: legacyModelDirectoryURL,
114+
to: modelDirectoryURL
115+
)
116+
return DebugTypoCorrectionWeights.state(modelDirectoryURL: modelDirectoryURL)
102117
}.value
103118
self.debugTypoCorrectionState = state
104119
if state != .failed {
@@ -136,6 +151,29 @@ struct ConfigWindow: View {
136151
}
137152
}
138153

154+
nonisolated private static func migrateLegacyDebugTypoCorrectionWeightsIfNeeded(from sourceURL: URL, to targetURL: URL) {
155+
guard sourceURL.standardizedFileURL != targetURL.standardizedFileURL else {
156+
return
157+
}
158+
guard !DebugTypoCorrectionWeights.hasRequiredWeightFiles(modelDirectoryURL: targetURL),
159+
DebugTypoCorrectionWeights.hasRequiredWeightFiles(modelDirectoryURL: sourceURL) else {
160+
return
161+
}
162+
do {
163+
let fileManager = FileManager.default
164+
try fileManager.createDirectory(
165+
at: targetURL.deletingLastPathComponent(),
166+
withIntermediateDirectories: true
167+
)
168+
if fileManager.fileExists(atPath: targetURL.path) {
169+
try fileManager.removeItem(at: targetURL)
170+
}
171+
try fileManager.copyItem(at: sourceURL, to: targetURL)
172+
} catch {
173+
// The status check below will surface a notDownloaded/failed state.
174+
}
175+
}
176+
139177
private func openAzooKeyDataDirectoryInFinder() {
140178
do {
141179
try FileManager.default.createDirectory(

0 commit comments

Comments
 (0)