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
This commit is contained in:
phranck
2026-01-30 10:53:35 +01:00
parent f118437092
commit eeed847824
2 changed files with 75 additions and 23 deletions
+53 -16
View File
@@ -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
+22 -7
View File
@@ -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