Remove CharacterInfo struct

This commit is contained in:
1024jp
2026-06-03 18:03:07 +02:00
parent 60331690c8
commit dff5f7ef74
4 changed files with 58 additions and 77 deletions
@@ -8,7 +8,7 @@
//
// ---------------------------------------------------------------------------
//
// © 2021-2025 1024jp
// © 2021-2026 1024jp
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -28,51 +28,56 @@ import CharacterInfo
struct CharacterInspectorView: View {
var info: CharacterInfo
var character: Character
init(_ character: Character) {
self.character = character
}
var body: some View {
HStack(alignment: .top) {
CharacterView(info: self.info)
CharacterView(character: self.character)
.frame(minWidth: 64)
CharacterDetailView(info: self.info)
CharacterDetailView(character: self.character)
}
.padding(14)
}
}
private struct CharacterDetailView: View {
var info: CharacterInfo
var character: Character
var body: some View {
VStack(alignment: .leading, spacing: 0) {
if let description = self.info.localizedDescription {
if let description = self.character.localizedDescription {
Text(description)
.fontWeight(self.info.isComplex ? .regular : .semibold)
.fontWeight(self.character.isComplex ? .regular : .semibold)
.textSelection(.enabled)
} else {
Text("Unknown", tableName: "CharacterInspector")
.foregroundStyle(.secondary)
}
if !self.info.isComplex {
ScalarDetailView(scalar: self.info.character.unicodeScalars.first!)
if !self.character.isComplex {
ScalarDetailView(scalar: self.character.unicodeScalars.first!)
.controlSize(.small)
.padding(.top, 4)
}
if self.info.character.unicodeScalars.count > 1 {
if self.character.unicodeScalars.count > 1 {
VStack(spacing: 0) {
ForEach(Array(self.info.character.unicodeScalars).enumerated(), id: \.offset) { _, scalar in
ForEach(Array(self.character.unicodeScalars).enumerated(), id: \.offset) { _, scalar in
DisclosureGroup {
HStack(alignment: .top) {
let character = Character(scalar)
let pictureCharacter = CharacterInfo(character: character).pictureCharacter
let pictureCharacter = character.pictureCharacter
Text(String(pictureCharacter ?? character))
.font(.system(size: 28, design: .serif))
@@ -204,7 +209,7 @@ private struct CharacterView: NSViewRepresentable {
typealias NSViewType = NSTextField
var info: CharacterInfo
var character: Character
private let fontSize: CGFloat = 64
@@ -223,8 +228,8 @@ private struct CharacterView: NSViewRepresentable {
func updateNSView(_ nsView: NSTextField, context: Context) {
nsView.stringValue = String(self.info.pictureCharacter ?? self.info.character)
nsView.textColor = (self.info.pictureCharacter != nil) ? .tertiaryLabelColor : .labelColor
nsView.stringValue = String(self.character.pictureCharacter ?? self.character)
nsView.textColor = (self.character.pictureCharacter != nil) ? .tertiaryLabelColor : .labelColor
}
@@ -247,11 +252,11 @@ private struct DeprecatedBadge: View {
}
private extension CharacterInfo {
private extension Character {
var localizedDescription: String? {
let unicodes = self.character.unicodeScalars
let unicodes = self.unicodeScalars
if self.isComplex {
return String(localized: "<a letter consisting of \(unicodes.count) characters>",
table: "CharacterInspector",
@@ -272,30 +277,30 @@ private extension CharacterInfo {
// MARK: - Preview
#Preview("𓆏") {
CharacterInspectorView(info: CharacterInfo(character: "𓆏"))
CharacterInspectorView("𓆏")
}
#Preview("\\n") {
CharacterInspectorView(info: CharacterInfo(character: "\n"))
CharacterInspectorView("\n")
}
#Preview("ơ̟̤̖̗͖͇̍͋̀͆̓́͞͡") {
CharacterInspectorView(info: CharacterInfo(character: "ơ̟̤̖̗͖͇̍͋̀͆̓́͞͡"))
CharacterInspectorView("ơ̟̤̖̗͖͇̍͋̀͆̓́͞͡")
}
#Preview("✔︎") {
CharacterInspectorView(info: CharacterInfo(character: "✔︎"))
CharacterInspectorView("✔︎")
.frame(height: 240, alignment: .top)
}
#Preview("🏴‍☠️") {
CharacterInspectorView(info: CharacterInfo(character: "🏴‍☠️"))
CharacterInspectorView("🏴‍☠️")
}
#Preview("🇦🇦") {
CharacterInspectorView(info: CharacterInfo(character: "🇦🇦"))
CharacterInspectorView("🇦🇦")
}
#Preview("deprecated") {
CharacterInspectorView(info: CharacterInfo(character: "ឣ"))
CharacterInspectorView("ឣ")
}
@@ -404,10 +404,10 @@ final class EditorTextViewController: NSViewController, NSServicesMenuRequestor,
let character = self.textView.selectedString.first
else { return assertionFailure() }
let characterInfo = CharacterInfo(character: character)
let view = NSHostingView(rootView: CharacterInspectorView(character).padding(14))
view.frame.size = view.intrinsicContentSize
let popoverController = DetachablePopoverViewController()
popoverController.view = NSHostingView(rootView: CharacterInspectorView(info: characterInfo))
popoverController.view.frame.size = popoverController.view.intrinsicContentSize
popoverController.view = view
let textView = self.textView
let positioningRect = textView.boundingRect(for: textView.selectedRange)?.insetBy(dx: -4, dy: -4) ?? .zero
@@ -1,5 +1,5 @@
//
// CharacterInfo.swift
// Character.swift
// CharacterInfo
//
// CotEditor
@@ -9,7 +9,7 @@
//
// ---------------------------------------------------------------------------
//
// © 2015-2024 1024jp
// © 2015-2026 1024jp
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -24,50 +24,28 @@
// limitations under the License.
//
public struct CharacterInfo: Sendable {
// MARK: Public Properties
public var character: Character
// MARK: Public Methods
public init(character: Character) {
self.character = character
}
public extension Character {
/// The representative character to display in the user interface.
public var pictureCharacter: Character? {
var pictureCharacter: Character? {
self.character.unicodeScalars.count == 1 // ignore CRLF
? self.character.unicodeScalars.first?.pictureRepresentation.map(Character.init)
self.unicodeScalars.count == 1 // ignore CRLF
? self.unicodeScalars.first?.pictureRepresentation.map(Character.init)
: nil
}
/// Whether the character consists with multiple Unicode scalars.
public var isComplex: Bool {
var isComplex: Bool {
self.character.unicodeScalars.count > 1 && !self.isVariant
self.unicodeScalars.count > 1 && !self.isVariant
}
/// Whether the character is a single variant character.
public var isVariant: Bool {
var isVariant: Bool {
(self.character.unicodeScalars.count == 2 &&
self.character.unicodeScalars.last?.variantDescription != nil)
}
}
extension CharacterInfo: CustomStringConvertible {
public var description: String {
String(self.character)
(self.unicodeScalars.count == 2 &&
self.unicodeScalars.last?.variantDescription != nil)
}
}
@@ -9,7 +9,7 @@
//
// ---------------------------------------------------------------------------
//
// © 2015-2024 1024jp
// © 2015-2026 1024jp
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -31,39 +31,37 @@ struct CharacterInfoTests {
@Test func singleCharacterWithVSInfo() {
let charInfo = CharacterInfo(character: "☺︎")
let character: Character = "☺︎"
#expect(charInfo.character == "☺︎")
#expect(!charInfo.isComplex)
#expect(charInfo.character.unicodeScalars.map(\.codePoint) == ["U+263A", "U+FE0E"])
#expect(charInfo.character.unicodeScalars.map(\.name) == ["WHITE SMILING FACE", "VARIATION SELECTOR-15"])
#expect(!character.isComplex)
#expect(character.unicodeScalars.map(\.codePoint) == ["U+263A", "U+FE0E"])
#expect(character.unicodeScalars.map(\.name) == ["WHITE SMILING FACE", "VARIATION SELECTOR-15"])
}
@Test func combiningCharacterInfo() {
let charInfo = CharacterInfo(character: "1️⃣")
let character: Character = "1️⃣"
#expect(charInfo.isComplex)
#expect(charInfo.character.unicodeScalars.map(\.codePoint) == ["U+0031", "U+FE0F", "U+20E3"])
#expect(character.isComplex)
#expect(character.unicodeScalars.map(\.codePoint) == ["U+0031", "U+FE0F", "U+20E3"])
}
@Test func nationalIndicatorInfo() {
let charInfo = CharacterInfo(character: "🇯🇵")
let character: Character = "🇯🇵"
#expect(charInfo.isComplex)
#expect(charInfo.character.unicodeScalars.map(\.codePoint) == ["U+1F1EF", "U+1F1F5"])
#expect(character.isComplex)
#expect(character.unicodeScalars.map(\.codePoint) == ["U+1F1EF", "U+1F1F5"])
}
@Test func controlCharacterInfo() {
let charInfo = CharacterInfo(character: " ")
let character: Character = " "
#expect(charInfo.character == " ")
#expect(charInfo.pictureCharacter == "␠")
#expect(charInfo.character.unicodeScalars.map(\.name) == ["SPACE"])
#expect(character.pictureCharacter == "␠")
#expect(character.unicodeScalars.map(\.name) == ["SPACE"])
}
}