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
19 changes: 19 additions & 0 deletions Core/Sources/Core/InputUtils/ArrowSymbolCandidates.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
enum ArrowSymbolCandidates {
/// 日本語入力で変わる長音符・全角記号を、矢印の入力パターンとして扱う。
static func variants(for reading: String) -> [String] {
let normalized = String(reading.map { character -> Character in
switch character {
case "ー", "ー", "-", "−": "-"
case ">": ">"
case "<": "<"
default: character
}
})
return switch normalized {
case "->", "-->": ["→", "⇒", "➜"]
case "<-", "<--": ["←", "⇐", "⇦"]
case "<->", "<-->": ["↔", "⇔"]
default: []
}
}
}
13 changes: 12 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,17 @@ public final class SegmentsManager {
requireEnglishPrediction: Config.DebugPredictiveTyping().value ? .manualMix : .disabled
)
)
let arrowInput = self.composingText.prefixToCursorPosition()
let arrowVariants = ArrowSymbolCandidates.variants(for: arrowInput.convertTarget)
if !arrowVariants.isEmpty {
let existingTexts = Set(result.mainResults.map(\.text))
let arrowCandidates = arrowVariants.filter { !existingTexts.contains($0) }.map { text in
Candidate(
lastMid: 0,
)
}
result.mainResults.insert(contentsOf: arrowCandidates, at: min(5, result.mainResults.count))
}
self.rawCandidates = result
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
@testable import Core
import Foundation
import KanaKanjiConverterModuleWithDefaultDictionary
import Testing

@MainActor
struct ArrowSymbolCandidatesTests {
@Test func patterns() {
for input in ["ー>", "ー>", "->", "->", "−>", "ーー>"] {
#expect(ArrowSymbolCandidates.variants(for: input) == ["→", "⇒", "➜"])
}
#expect(ArrowSymbolCandidates.variants(for: "<ー") == ["←", "⇐", "⇦"])
#expect(ArrowSymbolCandidates.variants(for: "<ー>") == ["↔", "⇔"])
for input in ["", "ー", ">", "a->b", "コーヒー>", "<=", "ー >"] {
#expect(ArrowSymbolCandidates.variants(for: input).isEmpty)
}
}

@Test(arguments: ["→", "⇒", "➜"])
func convertAndCommit(expected: String) throws {
try withManager { manager in
manager.insertAtCursorPosition("ー>", inputStyle: .direct)
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 arrow candidates")
return
}
#expect(choices.filter { $0.text == expected }.count == 1)
#expect(choices.contains { $0.text == "ー>" })
if rich {
let candidate = try #require(choices.first { $0.text == expected })
manager.prefixCandidateCommited(candidate, leftSideContext: "")
#expect(manager.isEmpty)
}
}
}
}

@Test func editedPrefixPreservesSuffix() throws {
try withManager { manager in
manager.insertAtCursorPosition("ー>あ", inputStyle: .direct)
manager.editSegment(count: -1)
guard case .selecting(let choices, _) = manager.getCurrentCandidateWindow(inputState: .selecting) else {
Issue.record("Expected edited candidates")
return
}
let candidate = try #require(choices.first { $0.text == "⇒" })
manager.prefixCandidateCommited(candidate, leftSideContext: "")
#expect(manager.convertTarget == "あ")
}
}

private func withManager(_ body: (SegmentsManager) throws -> Void) 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))
try body(manager)
}
}
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,7 @@ Thanks to authors!!

## Acknowledgement
本プロジェクトは情報処理推進機構(IPA)による[2024年度未踏IT人材発掘・育成事業](https://www.ipa.go.jp/jinzai/mitou/it/2024/koubokekka.html)の支援を受けて開発を行いました。

### 記号の組み合わせから矢印へ変換

`ー>` / `->` → `→`・`⇒`・`➜`、`<ー` → `←`・`⇐`・`⇦`、`<ー>` → `↔`・`⇔`を候補に追加します。全角・半角の記号に対応し、元の入力も選べます。文節編集中は選択範囲だけを確定します。