From eeed8478240cc2adaf64b2cee5c6e898893c86da Mon Sep 17 00:00:00 2001 From: phranck Date: Fri, 30 Jan 2026 10:53:35 +0100 Subject: [PATCH] refactor: Introduce named constants for magic hex values and ANSI style codes (F.1, F.2) - Add ASCIIByte enum with named constants for all 16+ raw hex values in KeyEvent.swift (escape, carriageReturn, arrowUp, openBracket, etc.) - Add ANSIRenderer.StyleCode enum with named constants for SGR attribute codes (bold, dim, italic, underline, blink, inverse, strikethrough) - F.3 (block characters) and F.4 (layout magic numbers) deferred to Phase 4 where BorderRenderer extraction will centralize them naturally --- Sources/TUIKit/Core/KeyEvent.swift | 69 ++++++++++++++++----- Sources/TUIKit/Rendering/ANSIRenderer.swift | 29 ++++++--- 2 files changed, 75 insertions(+), 23 deletions(-) diff --git a/Sources/TUIKit/Core/KeyEvent.swift b/Sources/TUIKit/Core/KeyEvent.swift index 6adb7db..0894324 100644 --- a/Sources/TUIKit/Core/KeyEvent.swift +++ b/Sources/TUIKit/Core/KeyEvent.swift @@ -72,6 +72,43 @@ public enum Key: Hashable, Sendable { } } +// MARK: - ASCII Byte Constants + +/// Named constants for ASCII byte values used in terminal input parsing. +/// +/// Replaces raw hex literals (e.g. `0x1B`, `0x0D`) with readable names, +/// making the key parsing logic self-documenting. +private enum ASCIIByte { + // Control characters + static let backspace: UInt8 = 0x08 + static let tab: UInt8 = 0x09 + static let lineFeed: UInt8 = 0x0A + static let carriageReturn: UInt8 = 0x0D + static let escape: UInt8 = 0x1B + static let delete: UInt8 = 0x7F + + // Ctrl+key range (Ctrl+A = 0x01 … Ctrl+Z = 0x1A) + static let ctrlRangeStart: UInt8 = 0x01 + static let ctrlRangeEnd: UInt8 = 0x1A + static let ctrlToLowerOffset: UInt8 = 0x60 + + // Printable ASCII range + static let printableStart: UInt8 = 0x20 + static let printableEnd: UInt8 = 0x7E + + // CSI introducer + static let openBracket: UInt8 = 0x5B // '[' + + // Arrow / navigation keys (CSI final byte) + static let arrowUp: UInt8 = 0x41 // 'A' + static let arrowDown: UInt8 = 0x42 // 'B' + static let arrowRight: UInt8 = 0x43 // 'C' + static let arrowLeft: UInt8 = 0x44 // 'D' + static let home: UInt8 = 0x48 // 'H' + static let end: UInt8 = 0x46 // 'F' + static let tilde: UInt8 = 0x7E // '~' (extended key terminator) +} + // MARK: - Key Parsing extension KeyEvent { @@ -93,7 +130,7 @@ extension KeyEvent { } // Escape sequence - if bytes[0] == 0x1B { + if bytes[0] == ASCIIByte.escape { return parseEscapeSequence(bytes) } @@ -109,18 +146,18 @@ extension KeyEvent { /// Parses a single byte into a key event. private static func parseSingleByte(_ byte: UInt8) -> KeyEvent? { switch byte { - case 0x1B: // Escape + case ASCIIByte.escape: return KeyEvent(key: .escape) - case 0x0D, 0x0A: // Enter (CR or LF) + case ASCIIByte.carriageReturn, ASCIIByte.lineFeed: return KeyEvent(key: .enter) - case 0x09: // Tab + case ASCIIByte.tab: return KeyEvent(key: .tab) - case 0x7F, 0x08: // Backspace (DEL or BS) + case ASCIIByte.delete, ASCIIByte.backspace: return KeyEvent(key: .backspace) - case 0x01...0x1A: // Ctrl+A through Ctrl+Z - let char = Character(UnicodeScalar(byte + 0x60)) + case ASCIIByte.ctrlRangeStart...ASCIIByte.ctrlRangeEnd: + let char = Character(UnicodeScalar(byte + ASCIIByte.ctrlToLowerOffset)) return KeyEvent(key: .character(char), ctrl: true) - case 0x20...0x7E: // Printable ASCII + case ASCIIByte.printableStart...ASCIIByte.printableEnd: let char = Character(UnicodeScalar(byte)) return KeyEvent(character: char) default: @@ -136,7 +173,7 @@ extension KeyEvent { } // CSI sequences: ESC [ - if bytes[1] == 0x5B { // '[' + if bytes[1] == ASCIIByte.openBracket { return parseCSISequence(Array(bytes.dropFirst(2))) } @@ -156,19 +193,19 @@ extension KeyEvent { // Arrow keys: A=up, B=down, C=right, D=left switch params.last { - case 0x41: // 'A' + case ASCIIByte.arrowUp: return KeyEvent(key: .up) - case 0x42: // 'B' + case ASCIIByte.arrowDown: return KeyEvent(key: .down) - case 0x43: // 'C' + case ASCIIByte.arrowRight: return KeyEvent(key: .right) - case 0x44: // 'D' + case ASCIIByte.arrowLeft: return KeyEvent(key: .left) - case 0x48: // 'H' - Home + case ASCIIByte.home: return KeyEvent(key: .home) - case 0x46: // 'F' - End + case ASCIIByte.end: return KeyEvent(key: .end) - case 0x7E: // '~' - Extended keys + case ASCIIByte.tilde: return parseExtendedKey(params) default: return nil diff --git a/Sources/TUIKit/Rendering/ANSIRenderer.swift b/Sources/TUIKit/Rendering/ANSIRenderer.swift index 5ad3433..e0b1b97 100644 --- a/Sources/TUIKit/Rendering/ANSIRenderer.swift +++ b/Sources/TUIKit/Rendering/ANSIRenderer.swift @@ -25,6 +25,21 @@ public enum ANSIRenderer { /// Regex pattern that matches any ANSI escape sequence. public static let ansiPattern = "\u{1B}\\[[0-9;]*[a-zA-Z]" + // MARK: - SGR Style Codes + + /// Named constants for ANSI SGR (Select Graphic Rendition) attribute codes. + /// + /// These replace bare string literals like `"1"`, `"7"` in `buildStyleCodes()`. + private enum StyleCode { + static let bold = "1" + static let dim = "2" + static let italic = "3" + static let underline = "4" + static let blink = "5" + static let inverse = "7" + static let strikethrough = "9" + } + // MARK: - Style Rendering /// Renders text with the specified style. @@ -53,25 +68,25 @@ public enum ANSIRenderer { // Text attributes if style.isBold { - codes.append("1") + codes.append(StyleCode.bold) } if style.isDim { - codes.append("2") + codes.append(StyleCode.dim) } if style.isItalic { - codes.append("3") + codes.append(StyleCode.italic) } if style.isUnderlined { - codes.append("4") + codes.append(StyleCode.underline) } if style.isBlink { - codes.append("5") + codes.append(StyleCode.blink) } if style.isInverted { - codes.append("7") + codes.append(StyleCode.inverse) } if style.isStrikethrough { - codes.append("9") + codes.append(StyleCode.strikethrough) } // Foreground color