From fe3dd9bc2cfde7481177fd9efbc44cb437cee1e0 Mon Sep 17 00:00:00 2001 From: Yukihiro Shinoda Date: Sat, 5 Sep 2026 12:38:36 +0900 Subject: [PATCH] =?UTF-8?q?feat(input):=20=E8=A6=8B=E5=88=86=E3=81=91?= =?UTF-8?q?=E3=81=AB=E3=81=8F=E3=81=84=E5=8D=98=E7=8B=AC=E8=A8=98=E5=8F=B7?= =?UTF-8?q?=E3=81=AE=E5=A4=89=E6=8F=9B=E5=80=99=E8=A3=9C=E3=81=AB=E8=AA=AC?= =?UTF-8?q?=E6=98=8E=E3=82=92=E8=A1=A8=E7=A4=BA=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CandidatePresentationContext.swift | 14 +++++++ .../CandidateSymbolAnnotationTests.swift | 38 +++++++++++++++++++ README.md | 4 ++ .../CandidateWindow/CandidateView.swift | 16 +++++--- .../CandidateSelectionFeedbackTests.swift | 24 ++++++++++++ 5 files changed, 90 insertions(+), 6 deletions(-) create mode 100644 Core/Tests/CoreTests/InputUtilsTests/CandidateSymbolAnnotationTests.swift diff --git a/Core/Sources/Core/InputUtils/CandidatePresentationContext.swift b/Core/Sources/Core/InputUtils/CandidatePresentationContext.swift index 57e214ec..3f776dac 100644 --- a/Core/Sources/Core/InputUtils/CandidatePresentationContext.swift +++ b/Core/Sources/Core/InputUtils/CandidatePresentationContext.swift @@ -17,5 +17,19 @@ public struct CandidatePresentation: Sendable { public init(candidate: Candidate, displayContext: CandidatePresentationContext = .init()) { self.candidate = candidate self.displayContext = displayContext + if self.displayContext.annotationText == nil { + self.displayContext.annotationText = Self.symbolAnnotation(for: candidate.text) + } + } + + private static func symbolAnnotation(for text: String) -> String? { + switch text { + case "-": "半角ハイフン" + case "-": "全角ハイフン" + case "ー": "半角長音符" + case "ー": "全角長音符" + case "−": "マイナス記号" + default: nil + } } } diff --git a/Core/Tests/CoreTests/InputUtilsTests/CandidateSymbolAnnotationTests.swift b/Core/Tests/CoreTests/InputUtilsTests/CandidateSymbolAnnotationTests.swift new file mode 100644 index 00000000..d7939d11 --- /dev/null +++ b/Core/Tests/CoreTests/InputUtilsTests/CandidateSymbolAnnotationTests.swift @@ -0,0 +1,38 @@ +import Core +import Foundation +import KanaKanjiConverterModuleWithDefaultDictionary +import Testing + +private func symbolCandidate(_ text: String) -> Candidate { + Candidate(text: text, value: 0, composingCount: .surfaceCount(text.count), lastMid: 0, data: []) +} + +@Test(arguments: [ + ("-", "半角ハイフン"), ("-", "全角ハイフン"), ("ー", "半角長音符"), + ("ー", "全角長音符"), ("−", "マイナス記号") +]) +func testSymbolCandidateAnnotation(text: String, annotation: String) throws { + let presentation = CandidatePresentation(candidate: symbolCandidate(text)) + #expect(presentation.candidate.text == text) + #expect(presentation.displayContext.annotationText == annotation) + let transported = try JSONDecoder().decode( + ConverterCandidatePresentation.self, + from: JSONEncoder().encode(ConverterCandidatePresentation(presentation)) + ) + #expect(transported.text == text) + #expect(transported.annotationText == annotation) +} + +@Test(arguments: ["", "コーヒー", "ーー", "--", " -", "ー ", "A-B", "1−2", "漢字"]) +func testSymbolAnnotationRequiresEntireCandidate(text: String) { + #expect(CandidatePresentation(candidate: symbolCandidate(text)).displayContext.annotationText == nil) +} + +@Test func testSymbolAnnotationPreservesExistingContext() { + let presentation = CandidatePresentation( + candidate: symbolCandidate("ー"), + displayContext: .init(annotationText: "半角カナ", extraValues: ["source": "additional"]) + ) + #expect(presentation.displayContext.annotationText == "半角カナ") + #expect(presentation.displayContext.extraValues == ["source": "additional"]) +} diff --git a/README.md b/README.md index e710c7a7..40aa73ea 100644 --- a/README.md +++ b/README.md @@ -151,3 +151,7 @@ Thanks to authors!! ## Acknowledgement 本プロジェクトは情報処理推進機構(IPA)による[2024年度未踏IT人材発掘・育成事業](https://www.ipa.go.jp/jinzai/mitou/it/2024/koubokekka.html)の支援を受けて開発を行いました。 + +### 記号候補の説明 + +単独の `-`・`-`・`ー`・`ー`・`−` の変換候補に、それぞれ「半角ハイフン」「全角ハイフン」「半角長音符」「全角長音符」「マイナス記号」を表示します。候補の文字や順序は変わりません。語中の記号には説明を付けず、既存の「半角カナ」などの説明がある場合はそちらを優先します。 diff --git a/azooKeyMac/InputController/CandidateWindow/CandidateView.swift b/azooKeyMac/InputController/CandidateWindow/CandidateView.swift index 2685f123..1dc95ed1 100644 --- a/azooKeyMac/InputController/CandidateWindow/CandidateView.swift +++ b/azooKeyMac/InputController/CandidateWindow/CandidateView.swift @@ -68,6 +68,10 @@ class CandidatesViewController: BaseCandidateViewController { } cell.candidateTextField.attributedStringValue = attributedString + cell.candidateTextField.setAccessibilityLabel( + annotationText.map { "\(displayText)、\($0)" } ?? displayText + ) + cell.candidateAnnotationTextField.setAccessibilityElement(false) if let annotationText { let annotationString = NSAttributedString( @@ -100,12 +104,12 @@ class CandidatesViewController: BaseCandidateViewController { } override func getWindowWidth(maxContentWidth: CGFloat) -> CGFloat { - let hasAnnotation = self.candidates.contains { $0.annotationText != nil } - if self.showCandidateIndex { - return maxContentWidth + 48 + (hasAnnotation ? 56 : 0) - } else { - return maxContentWidth + 20 + (hasAnnotation ? 56 : 0) - } + let annotationTexts = self.candidates.compactMap(\.annotationText) + let annotationWidth = annotationTexts.isEmpty ? 0 : self.getMaxTextWidth( + candidates: annotationTexts, + font: .systemFont(ofSize: 12) + ) + 8 + return maxContentWidth + (self.showCandidateIndex ? 48 : 20) + annotationWidth } } diff --git a/azooKeyMacTests/CandidateSelectionFeedbackTests.swift b/azooKeyMacTests/CandidateSelectionFeedbackTests.swift index 19b61f40..0f023010 100644 --- a/azooKeyMacTests/CandidateSelectionFeedbackTests.swift +++ b/azooKeyMacTests/CandidateSelectionFeedbackTests.swift @@ -53,4 +53,28 @@ final class CandidateSelectionFeedbackTests: XCTestCase { XCTAssertEqual(controller.currentSelectedRow, 1) XCTAssertEqual(delegate.selectedRows, [1]) } + + func testSymbolAnnotationIsAccessibleAndClearedWhenCellIsReused() { + let controller = CandidatesViewController() + controller.candidates = [ + ConverterCandidatePresentation(text: "-", annotationText: "半角ハイフン"), + ConverterCandidatePresentation(text: "文章") + ] + let cell = CandidateTableCellView() + controller.configureCellView(cell, forRow: 0) + XCTAssertFalse(cell.candidateAnnotationTextField.isHidden) + XCTAssertEqual(cell.candidateTextField.accessibilityLabel(), "-、半角ハイフン") + controller.configureCellView(cell, forRow: 1) + XCTAssertTrue(cell.candidateAnnotationTextField.isHidden) + XCTAssertEqual(cell.candidateTextField.accessibilityLabel(), "文章") + } + + func testWindowWidthIncludesFullAnnotation() { + let controller = CandidatesViewController() + controller.candidates = [ConverterCandidatePresentation(text: "-", annotationText: "半角ハイフン")] + let width = controller.getWindowWidth(maxContentWidth: 18) + let annotationWidth = controller.getMaxTextWidth(candidates: ["半角ハイフン"], font: .systemFont(ofSize: 12)) + XCTAssertGreaterThanOrEqual(width, 18 + 20 + 8 + annotationWidth) + } + }