Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions Core/Sources/Core/InputUtils/RomanCaseCandidates.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Foundation

/// Case variants of the original keyboard input, before kana conversion.
enum RomanCaseCandidates {
static func variants(for input: String) -> [String] {
guard input.unicodeScalars.allSatisfy({ (0x21...0x7e).contains($0.value) }),
input.unicodeScalars.contains(where: { (0x41...0x5a).contains($0.value) || (0x61...0x7a).contains($0.value) }) else {
return []
}
let lower = input.lowercased()
let title = lower.prefix(1).uppercased() + lower.dropFirst()
var seen = Set<String>()
return [lower, title, input.uppercased()].filter { seen.insert($0).inserted }
}
}
18 changes: 17 additions & 1 deletion Core/Sources/Core/InputUtils/SegmentsManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,7 @@ public final class SegmentsManager {

let leftSideContext = forcedLeftSideContext ?? self.getCleanLeftSideContext(maxCount: ContextLength.conversion)
let rightSideContext = forcedRightSideContext ?? self.getCleanRightSideContext(maxCount: ContextLength.conversion)
let result = self.kanaKanjiConverter.requestCandidates(
var result = self.kanaKanjiConverter.requestCandidates(
self.composingText,
options: options(
leftSideContext: leftSideContext,
Expand All @@ -574,6 +574,22 @@ public final class SegmentsManager {
requireEnglishPrediction: Config.DebugPredictiveTyping().value ? .manualMix : .disabled
)
)
let romanComposingText = self.composingText.prefixToCursorPosition()
let romanInput = romanComposingText.input.map(\.piece).inputString(preferIntention: false)
let romanVariants = RomanCaseCandidates.variants(for: romanInput)
if !romanVariants.isEmpty {
let romanTexts = Set(result.mainResults.map(\.text))
let romanCandidates = romanVariants.filter { !romanTexts.contains($0) }.map { text in
Candidate(
text: text, value: -18,
composingCount: .inputCount(romanComposingText.input.count),
lastMid: MIDData.一般.mid,
data: [.init(word: text, ruby: romanInput, cid: CIDData.固有名詞.cid, mid: MIDData.一般.mid, value: -18)],
isLearningTarget: false
)
}
result.mainResults.insert(contentsOf: romanCandidates, at: min(5, result.mainResults.count))
}
self.rawCandidates = result
}

Expand Down
23 changes: 23 additions & 0 deletions Core/Tests/CoreTests/InputUtilsTests/EditedVariantRangeTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import Core
import Foundation
import KanaKanjiConverterModuleWithDefaultDictionary
import Testing

@MainActor
@Test func editedVariantConsumesOnlySelectedPrefix() throws {
let directory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
defer { try? FileManager.default.removeItem(at: directory) }
let manager = SegmentsManager(kanaKanjiConverter: .withDefaultDictionary(), applicationDirectoryURL: directory,
containerURL: nil, context: .init(useZenzai: false))
manager.insertAtCursorPosition("sushi", inputStyle: .roman2kana)
manager.editSegment(count: -1)
manager.requestSetCandidateWindowState(visible: true)
guard case .selecting(let choices, _) = manager.getCurrentCandidateWindow(inputState: .selecting) else {
Issue.record("Expected candidates for edited segment")
return
}
let candidate = try #require(choices.first { $0.text == "SU" })
manager.prefixCandidateCommited(candidate, leftSideContext: "")
#expect(manager.convertTarget == "し")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
@testable import Core
import Foundation
import KanaKanjiConverterModuleWithDefaultDictionary
import Testing

@MainActor
struct RomanCaseCandidatesTests {
@Test func variantsAndExclusions() {
#expect(RomanCaseCandidates.variants(for: "fable") == ["fable", "Fable", "FABLE"])
#expect(RomanCaseCandidates.variants(for: "GitHub") == ["github", "Github", "GITHUB"])
#expect(RomanCaseCandidates.variants(for: "a") == ["a", "A"])
for input in ["", "1111", "ふぁbぇ", "コーヒー", "abc\ndef"] {
#expect(RomanCaseCandidates.variants(for: input).isEmpty)
}
}

@Test(arguments: ["fable", "github", "FABLE"], ["lower", "title", "upper"])
func originalKeysCanBeConvertedAndFullyCommitted(input: String, form: String) throws {
let directory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
defer { try? FileManager.default.removeItem(at: directory) }
let manager = SegmentsManager(kanaKanjiConverter: .withDefaultDictionary(), applicationDirectoryURL: directory,
containerURL: nil, context: .init(useZenzai: false))
for character in input {
manager.insertAtCursorPosition(String(character), inputStyle: .roman2kana)
}
if input == "fable" { #expect(manager.convertTarget == "ふぁbぇ") }
let lower = input.lowercased()
let expected = form == "lower" ? lower : form == "title" ? lower.prefix(1).uppercased() + lower.dropFirst() : input.uppercased()
for rich in [false, true] {
manager.update(requestRichCandidates: rich)
manager.requestSetCandidateWindowState(visible: true)
guard case .selecting(let choices, _) = manager.getCurrentCandidateWindow(inputState: .selecting) else {
Issue.record("Expected conversion candidates")
return
}
#expect(choices.filter { $0.text == expected }.count == 1)
if rich {
let row = try #require(choices.firstIndex { $0.text == expected })
manager.requestSelectingRow(row)
let candidate = try #require(manager.selectedCandidate)
manager.prefixCandidateCommited(candidate, leftSideContext: "")
#expect(manager.isEmpty)
}
}
}
}