Skip to content

Commit 08c570e

Browse files
authored
Merge pull request #407 from mtgto/skkserv-completion
SKKServに補完候補を問い合わせする設定を追加
2 parents 26cfbf3 + f8f536c commit 08c570e

15 files changed

+130
-74
lines changed

macSKK/Global.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import Combine
2525
/// 1文字目を常に未確定扱いするワークアラウンドを適用するBundle Identifierの集合
2626
static let treatFirstCharacterAsMarkedTextBundleIdentifiers = CurrentValueSubject<[String], Never>([])
2727
/// ユーザー辞書だけでなくすべての辞書から補完候補を検索するか?
28-
static let findCompletionFromAllDicts = CurrentValueSubject<Bool, Never>(false)
28+
static var findCompletionFromAllDicts = false
2929
/// 現在のローマ字かな変換ルール
3030
static var kanaRule: Romaji!
3131
/// デフォルトでもってるローマ字かな変換ルール
@@ -51,6 +51,8 @@ import Combine
5151
static var candidateListDirection = CurrentValueSubject<CandidateListDirection, Never>(.vertical)
5252
/// ピリオドで補完候補の最初の要素で確定するか
5353
static var fixedCompletionByPeriod: Bool = true
54+
/// SKKServから補完候補を検索するか
55+
static var searchCompletionsSkkserv: Bool = false
5456
/// 現在のモードを表示するパネル
5557
private let inputModePanel: InputModePanel
5658
/// 変換候補を表示するパネル

macSKK/InputController.swift

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -184,11 +184,12 @@ class InputController: IMKInputController {
184184
}
185185
.receive(on: DispatchQueue.global())
186186
.compactMap { (yomi, cursorPosition) -> (String, Completion, NSRect)? in
187+
let skkservDict = Global.searchCompletionsSkkserv ? Global.skkservDict : nil
187188
if Global.showCandidateForCompletion {
188-
let candidates = Global.dictionary.candidatesForCompletion(prefix: yomi)
189+
let candidates = Global.dictionary.candidatesForCompletion(prefix: yomi, skkservDict: skkservDict, findFromAllDicts: Global.findCompletionFromAllDicts)
189190
return (yomi, .candidates(candidates), cursorPosition)
190-
} else {
191-
let completions = Global.dictionary.findCompletions(prefix: yomi)
191+
} else {
192+
let completions = Global.dictionary.findCompletionsDicts(prefix: yomi, skkservDict: skkservDict, findFromAllDicts: Global.findCompletionFromAllDicts)
192193
return (yomi, .yomi(completions, 0), cursorPosition)
193194
}
194195
}
@@ -267,12 +268,6 @@ class InputController: IMKInputController {
267268
self?.stateMachine.inlineCandidateCount = inlineCandidateCount
268269
}
269270
}.store(in: &cancellables)
270-
NotificationCenter.default.publisher(for: notificationNameFindCompletionFromAllDicts)
271-
.sink { notification in
272-
if let findCompletionFromAllDicts = notification.object as? Bool {
273-
Global.findCompletionFromAllDicts.send(findCompletionFromAllDicts)
274-
}
275-
}.store(in: &cancellables)
276271
}
277272

278273
@MainActor override func handle(_ event: NSEvent!, client sender: Any!) -> Bool {

macSKK/SKKServDict.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,37 @@ struct SKKServDict {
6262
}
6363
}
6464

65+
func findCompletions(prefix: String) -> [String] {
66+
do {
67+
let result = try service.completion(yomi: prefix, destination: destination, timeout: 1.0)
68+
// 補完結果が見つかった場合は "1/ほかん/ほかんこうほ/" のように 1が先頭でスラッシュで区切られた文字列
69+
// 見つからなかた場合は "4ほかん" のように4が先頭の文字列
70+
guard result.hasPrefix("1/") else {
71+
logger.debug("skkservから補完候補が見つからなかったレスポンスが返りました")
72+
return []
73+
}
74+
return result.dropFirst(2).split(separator: "/").map { String($0) }
75+
} catch {
76+
if let error = error as? SKKServClientError {
77+
switch error {
78+
case .connectionRefused:
79+
logger.log("skkservが応答しません")
80+
case .connectionTimeout:
81+
logger.log("skkservとの接続がタイムアウトしました")
82+
case .invalidResponse:
83+
logger.warning("skkservから想定しない応答が返りました")
84+
case .timeout:
85+
logger.log("skkservから応答が一定時間返りませんでした")
86+
default:
87+
logger.error("skkservから不明なエラーが返りました")
88+
}
89+
} else {
90+
logger.error("skkserv辞書の検索でエラーが発生しました: \(error, privacy: .public)")
91+
}
92+
return []
93+
}
94+
}
95+
6596
func disconnect() {
6697
do {
6798
try service.disconnect()

macSKK/SKKServService.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Foundation
55

66
protocol SKKServServiceProtocol {
77
func refer(yomi: String, destination: SKKServDestination, timeout: TimeInterval) throws -> String
8+
func completion(yomi: String, destination: SKKServDestination, timeout: TimeInterval) throws -> String
89
func disconnect() throws
910
}
1011

macSKK/Settings/SKKServDictSetting.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,16 @@ final class SKKServDictSetting: ObservableObject {
1212
@Published var encoding: String.Encoding
1313
/// 変換履歴をユーザー辞書に保存するかどうか
1414
@Published var saveToUserDict: Bool
15+
/// 補完候補をSKKServから取得するか
16+
@Published var enableCompletion: Bool
1517

16-
init(enabled: Bool, address: String, port: UInt16, encoding: String.Encoding, saveToUserDict: Bool) {
18+
init(enabled: Bool, address: String, port: UInt16, encoding: String.Encoding, saveToUserDict: Bool, enableCompletion: Bool) {
1719
self.enabled = enabled
1820
self.address = address
1921
self.port = port
2022
self.encoding = encoding
2123
self.saveToUserDict = saveToUserDict
24+
self.enableCompletion = enableCompletion
2225
}
2326

2427
// UserDefaultsのDictionaryを受け取る
@@ -31,9 +34,12 @@ final class SKKServDictSetting: ObservableObject {
3134
self.port = port
3235
guard let encoding = dictionary["encoding"] as? UInt else { return nil }
3336
self.encoding = String.Encoding(rawValue: encoding)
34-
// v2.2.1までは存在しなかった設定
37+
// v2.2.1まで存在しなかった設定
3538
let saveToUserDict = dictionary["saveToUserDict"] as? Bool ?? true
3639
self.saveToUserDict = saveToUserDict
40+
// v2.5.0まで存在しなかった設定
41+
let enableCompletion = dictionary["enableCompletion"] as? Bool ?? false
42+
self.enableCompletion = enableCompletion
3743
}
3844

3945
// UserDefaults用にDictionaryにシリアライズ
@@ -44,6 +50,7 @@ final class SKKServDictSetting: ObservableObject {
4450
"port": port,
4551
"encoding": encoding.rawValue,
4652
"saveToUserDict": saveToUserDict,
53+
"enableCompletion": enableCompletion,
4754
]
4855
}
4956
}

macSKK/Settings/SKKServDictView.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ struct SKKServDictView: View {
2828
Text("Save conversion history to User Dictionary")
2929
}
3030
.toggleStyle(.switch)
31+
Toggle(isOn: $settingsViewModel.skkservDictSetting.enableCompletion) {
32+
Text("Search completions")
33+
}
34+
.toggleStyle(.switch)
3135
} header: {
3236
Text("SKKServDictTitle")
3337
}
@@ -167,7 +171,8 @@ struct SKKServDictView: View {
167171
address: "127.0.0.1",
168172
port: 1178,
169173
encoding: .japaneseEUC,
170-
saveToUserDict: true)
174+
saveToUserDict: true,
175+
enableCompletion: false)
171176
return SKKServDictView(settingsViewModel: try! SettingsViewModel(skkservDictSetting: setting),
172177
isShowSheet: .constant(true), information: "skkservが応答していません")
173178
}

macSKK/Settings/SettingsViewModel.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ final class SettingsViewModel: ObservableObject {
286286
Global.punctuation = Punctuation(comma: comma, period: period)
287287
Global.ignoreUserDictInPrivateMode.send(ignoreUserDictInPrivateMode)
288288
Global.candidateListDirection.send(candidateListDirection)
289-
Global.findCompletionFromAllDicts.send(findCompletionFromAllDicts)
289+
Global.findCompletionFromAllDicts = findCompletionFromAllDicts
290290

291291
// SKK-JISYO.Lのようなファイルの読み込みが遅いのでバックグラウンドで処理
292292
$dictSettings.filter({ !$0.isEmpty }).receive(on: DispatchQueue.global()).sink { dictSettings in
@@ -333,6 +333,7 @@ final class SettingsViewModel: ObservableObject {
333333
logger.log("skkserv辞書は無効化されています")
334334
Global.skkservDict = nil
335335
}
336+
Global.searchCompletionsSkkserv = setting.enableCompletion
336337
UserDefaults.app.set(setting.encode(), forKey: UserDefaultsKeys.skkservClient)
337338
}.store(in: &cancellables)
338339

@@ -451,7 +452,7 @@ final class SettingsViewModel: ObservableObject {
451452

452453
$findCompletionFromAllDicts.dropFirst().sink { findCompletionFromAllDicts in
453454
UserDefaults.app.set(findCompletionFromAllDicts, forKey: UserDefaultsKeys.findCompletionFromAllDicts)
454-
NotificationCenter.default.post(name: notificationNameFindCompletionFromAllDicts, object: findCompletionFromAllDicts)
455+
Global.findCompletionFromAllDicts = findCompletionFromAllDicts
455456
logger.log("一般の辞書を使って補完するかを\(findCompletionFromAllDicts)に変更しました")
456457
}.store(in: &cancellables)
457458

@@ -582,7 +583,8 @@ final class SettingsViewModel: ObservableObject {
582583
address: "127.0.0.1",
583584
port: 1178,
584585
encoding: .japaneseEUC,
585-
saveToUserDict: true)
586+
saveToUserDict: true,
587+
enableCompletion: false)
586588
selectCandidateKeys = "123456789"
587589
findCompletionFromAllDicts = false
588590
keyBindingSets = [KeyBindingSet.defaultKeyBindingSet]

macSKK/UserDict.swift

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,20 +29,17 @@ class UserDict: NSObject, DictProtocol {
2929
private let privateMode: CurrentValueSubject<Bool, Never>
3030
/// プライベートモード時に変換候補にユーザー辞書を無視するかどうか
3131
private let ignoreUserDictInPrivateMode: CurrentValueSubject<Bool, Never>
32-
// ユーザー辞書だけでなくすべての辞書から補完候補を検索するか?
33-
private let findCompletionFromAllDicts: CurrentValueSubject<Bool, Never>
3432
private var cancellables: Set<AnyCancellable> = []
3533
let saveToUserDict = true
3634

3735
// MARK: NSFilePresenter
3836
let presentedItemURL: URL?
3937
let presentedItemOperationQueue: OperationQueue = OperationQueue()
4038

41-
init(dicts: [any DictProtocol], userDictEntries: [String: [Word]]? = nil, privateMode: CurrentValueSubject<Bool, Never>, ignoreUserDictInPrivateMode: CurrentValueSubject<Bool, Never>, findCompletionFromAllDicts: CurrentValueSubject<Bool, Never>, dateYomis: [DateConversion.Yomi], dateConversions: [DateConversion]) throws {
39+
init(dicts: [any DictProtocol], userDictEntries: [String: [Word]]? = nil, privateMode: CurrentValueSubject<Bool, Never>, ignoreUserDictInPrivateMode: CurrentValueSubject<Bool, Never>, dateYomis: [DateConversion.Yomi], dateConversions: [DateConversion]) throws {
4240
self.dicts = dicts
4341
self.privateMode = privateMode
4442
self.ignoreUserDictInPrivateMode = ignoreUserDictInPrivateMode
45-
self.findCompletionFromAllDicts = findCompletionFromAllDicts
4643
self.dateYomis = dateYomis
4744
self.dateConversions = dateConversions
4845
dictionariesDirectoryURL = try FileManager.default.url(
@@ -207,6 +204,46 @@ class UserDict: NSObject, DictProtocol {
207204
}
208205

209206
/**
207+
* 保持する辞書を順に引き現在入力中のprefixに続く入力候補を返す。見つからなければ空配列を返す。
208+
*
209+
* ## skkservについて
210+
* skkservを辞書とする場合はすべてのファイル辞書の候補の末尾に付けて返す。
211+
* skkserv辞書の変換候補を末尾につけるのは仮の仕様で将来は利用者が選択可能にする可能性がある。
212+
*
213+
* skkservからの応答が一定時間なかった場合はTCP接続を切断する。
214+
* 実装を簡単にするためskkserv辞書が有効なまま再度このメソッドが呼ばれたら再接続から試みる。
215+
*
216+
* - Parameters:
217+
* - prefix: SKK辞書の見出しの接頭辞。複数のひらがな、もしくは複数のひらがな + ローマ字からなる文字列
218+
* - skkservDict: SKKServ辞書。nilのときはskkservを引かない
219+
* - findFromAllDicts: ユーザー辞書以外を検索するか
220+
*/
221+
func findCompletionsDicts(prefix: String, skkservDict: SKKServDict?, findFromAllDicts: Bool) -> [String] {
222+
if prefix.isEmpty {
223+
return []
224+
}
225+
var results: [String] = findCompletions(prefix: prefix)
226+
if findFromAllDicts {
227+
for dict in dicts {
228+
for yomi in dict.findCompletions(prefix: prefix) {
229+
if !results.contains(yomi) {
230+
results.append(yomi)
231+
}
232+
}
233+
}
234+
}
235+
if let skkservDict {
236+
for yomi in skkservDict.findCompletions(prefix: prefix) {
237+
if !results.contains(yomi) {
238+
results.append(yomi)
239+
}
240+
}
241+
}
242+
return results
243+
}
244+
245+
/**
246+
* ユーザー辞書のみから検索して他のSKK辞書は参照しない。すべての辞書から参照する場合は ``referDicts(_:option:skkservDict:findFromAllDicts:)`` を使用すること。
210247
* プライベートモードで入力したエントリは参照しない。
211248
*/
212249
func refer(_ yomi: String, option: DictReferringOption? = nil) -> [Word] {
@@ -316,15 +353,6 @@ class UserDict: NSObject, DictProtocol {
316353
results.append(dateYomi.yomi)
317354
}
318355
}
319-
if findCompletionFromAllDicts.value {
320-
for dict in dicts {
321-
for yomi in dict.findCompletions(prefix: prefix) {
322-
if !results.contains(yomi) {
323-
results.append(yomi)
324-
}
325-
}
326-
}
327-
}
328356
return results
329357
}
330358

@@ -334,20 +362,20 @@ class UserDict: NSObject, DictProtocol {
334362
* asyncにするかも? (skkservとかで便利そう)
335363
* AsyncStreamにするかも?
336364
*/
337-
func candidatesForCompletion(prefix: String) -> [Candidate] {
365+
func candidatesForCompletion(prefix: String, skkservDict: SKKServDict?, findFromAllDicts: Bool) -> [Candidate] {
338366
// 1文字のときは全探索するとめちゃくちゃ量が多いので完全一致だけ探す
339367
if prefix.count == 1 {
340-
return referDicts(prefix, option: nil, skkservDict: nil, findFromAllDicts: findCompletionFromAllDicts.value)
368+
return referDicts(prefix, option: nil, skkservDict: skkservDict, findFromAllDicts: findFromAllDicts)
341369
.map { candidate in
342370
candidate.withOriginal(Candidate.Original(midashi: prefix, word: candidate.word))
343371
}
344372
}
345373
// あとでいろいろ拡張するけどひとまずfindCompletionsの結果を[Candidate]にするだけ
346374
// 別スレッドから実行したいのでひとまずskkserv以外を検索する
347-
return findCompletions(prefix: prefix).flatMap { midashi in
375+
return findCompletionsDicts(prefix: prefix, skkservDict: skkservDict, findFromAllDicts: findFromAllDicts).flatMap { midashi in
348376
// NOTE: 多すぎても役に立たないだろうと思うのでひとまず先頭100件に制限。設定項目にしてもよさそう
349377
// FIXME: Candidateの配列じゃなくて、(String, Candidate) のように見出し語と変換候補のタプルの配列を返すほうがよさそう
350-
referDicts(midashi, option: nil, skkservDict: nil, findFromAllDicts: findCompletionFromAllDicts.value)
378+
referDicts(midashi, option: nil, skkservDict: skkservDict, findFromAllDicts: findFromAllDicts)
351379
.prefix(100)
352380
.map { candidate in
353381
candidate.withOriginal(Candidate.Original(midashi: midashi, word: candidate.word))

macSKK/en.lproj/Localizable.strings

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
"TCP Port" = "TCP Port No.";
9494
"Response Encoding" = "Response Encoding";
9595
"Save conversion history to User Dictionary" = "Save conversion history to User Dictionary";
96+
"Search completions" = "Search completions";
9697
"Yomi" = "Yomi";
9798
"Find Candidates" = "Find Candidates";
9899
"Find Completions" = "Find Completions";

macSKK/ja.lproj/Localizable.strings

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
"TCP Port" = "TCPポート番号";
9494
"Response Encoding" = "応答エンコーディング";
9595
"Save conversion history to User Dictionary" = "ユーザー辞書に変換履歴を保存する";
96+
"Search completions" = "補完候補を検索する";
9697
"Yomi" = "読み";
9798
"Find Candidates" = "変換候補を検索";
9899
"Find Completions" = "補完候補を検索";

0 commit comments

Comments
 (0)