Skip to content

Commit aa28ad5

Browse files
committed
Core: introduce VTCapabilities
Introduce the infrastructure to query the terminal what it actually supports. This allows the safe use of advanced features and will allow defaulting to 8-bit encoding if the terminal supports 8-bit encoding rather than the 7-bit encoding.
1 parent 03ebb4c commit aa28ad5

File tree

6 files changed

+348
-18
lines changed

6 files changed

+348
-18
lines changed

Sources/VirtualTerminal/Input/VTEvent.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,17 @@ import Geometry
2424
/// }
2525
/// ```
2626
public enum VTEvent: Equatable, Sendable {
27+
/// A keyboard input event.
2728
case key(KeyEvent)
29+
30+
/// A mouse input event.
2831
case mouse(MouseEvent)
32+
33+
/// A terminal resize event.
2934
case resize(ResizeEvent)
35+
36+
/// A device response event.
37+
case response(VTDeviceAttributesResponse)
3038
}
3139

3240
// MARK: - Key Event

Sources/VirtualTerminal/Input/VTEventStream.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
/// handleMouseInput(mouse)
2626
/// case .resize(let resize):
2727
/// handleTerminalResize(resize)
28+
/// case .response(let response):
29+
/// handleTerminalResponse(response)
2830
/// }
2931
/// }
3032
/// ```

Sources/VirtualTerminal/Input/VTInputParser.swift

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,22 @@ internal enum ParseResult<Output> {
7272
case indeterminate
7373
}
7474

75+
/// Device attribute response types based on intermediate characters.
76+
///
77+
/// Different device attribute queries return different types of information.
78+
/// The intermediate character in the CSI sequence indicates which type of
79+
/// response is being provided.
80+
public enum VTDeviceAttributesResponse: Equatable, Sendable {
81+
/// Primary device attributes (DA1) - basic terminal identification
82+
case primary([Int])
83+
84+
/// Secondary device attributes (DA2) - version and capability info
85+
case secondary([Int])
86+
87+
/// Tertiary device attributes (DA3) - unit identification
88+
case tertiary([Int])
89+
}
90+
7591
/// Represents different types of parsed terminal input sequences.
7692
///
7793
/// Terminal input consists of various sequence types, from simple characters
@@ -86,6 +102,7 @@ internal enum ParseResult<Output> {
86102
internal enum ParsedSequence {
87103
case character(Character)
88104
case cursor(direction: Direction, count: Int)
105+
case DeviceAttributes(VTDeviceAttributesResponse)
89106
case function(number: Int, modifiers: KeyModifiers)
90107
case unknown(sequence: [UInt8])
91108
}
@@ -307,6 +324,21 @@ extension VTInputParser {
307324
input = input.dropFirst()
308325
return parse(next: &input)
309326

327+
case 0x3d: // '=' (DEC Private Mode)
328+
state = .CSI(parameters: parameters, intermediate: intermediate + [byte])
329+
input = input.dropFirst()
330+
return parse(next: &input)
331+
332+
case 0x3e: // '>' (DEC Private Mode)
333+
state = .CSI(parameters: parameters, intermediate: intermediate + [byte])
334+
input = input.dropFirst()
335+
return parse(next: &input)
336+
337+
case 0x3f: // '?' (DEC Private Mode)
338+
state = .CSI(parameters: parameters, intermediate: intermediate + [byte])
339+
input = input.dropFirst()
340+
return parse(next: &input)
341+
310342
case 0x40 ... 0x7e: // command
311343
state = .normal
312344
input = input.dropFirst()
@@ -376,7 +408,8 @@ extension VTInputParser {
376408
}
377409
input = input.dropFirst(2)
378410
state = .normal
379-
return .success(.unknown(sequence: [0x1b, 0x50] + data), buffer: input)
411+
return .success(.unknown(sequence: [0x1b, 0x50] + data + [0x1b, byte]),
412+
buffer: input)
380413
}
381414

382415
input = input.dropFirst()
@@ -416,6 +449,12 @@ extension VTInputParser {
416449
return .cursor(direction: .right, count: count)
417450
case 0x44: // 'D' (CUB)
418451
return .cursor(direction: .left, count: count)
452+
case 0x63 where intermediate == [0x3f]: // '\033[?...c' (DA1)
453+
return .DeviceAttributes(.primary(parameters))
454+
case 0x63 where intermediate == [0x3e]: // '\033[>...c' (DA2)
455+
return .DeviceAttributes(.secondary(parameters))
456+
case 0x63 where intermediate == [0x3d]: // '\033[=...c' (DA3)
457+
return .DeviceAttributes(.tertiary(parameters))
419458
default:
420459
let sequence: [UInt8] = [UInt8(0x1b), UInt8(0x5b)] + parameters.flatMap { String($0).utf8 } + [UInt8(0x3b)] + intermediate + [command]
421460
return .unknown(sequence: sequence)
@@ -449,37 +488,35 @@ extension ParsedSequence {
449488
/// }
450489
/// ```
451490
///
452-
/// - Returns: A `KeyEvent` if the sequence represents keyboard input,
453-
/// `nil` otherwise.
454-
internal var event: KeyEvent? {
491+
/// - Returns: A `VTEvent` if the sequence represents keyboard input,
492+
/// or a terminal response, `nil` otherwise.
493+
internal var event: VTEvent? {
455494
return switch self {
456495
case let .character(character):
457496
if character == "\u{1b}" {
458-
KeyEvent(character: character, keycode: VTKeyCode.escape, modifiers: [], type: .press)
497+
.key(.init(character: character, keycode: VTKeyCode.escape, modifiers: [], type: .press))
459498
} else {
460-
KeyEvent(character: character, keycode: 0, modifiers: [], type: .press)
499+
.key(.init(character: character, keycode: 0, modifiers: [], type: .press))
461500
}
462501

463502
case let .cursor(direction, _):
464503
switch direction {
465504
case .up:
466-
KeyEvent(character: nil, keycode: VTKeyCode.up, modifiers: [],
467-
type: .press)
505+
.key(.init(character: nil, keycode: VTKeyCode.up, modifiers: [], type: .press))
468506
case .down:
469-
KeyEvent(character: nil, keycode: VTKeyCode.down, modifiers: [],
470-
type: .press)
507+
.key(.init(character: nil, keycode: VTKeyCode.down, modifiers: [], type: .press))
471508
case .left:
472-
KeyEvent(character: nil, keycode: VTKeyCode.left, modifiers: [],
473-
type: .press)
509+
.key(.init(character: nil, keycode: VTKeyCode.left, modifiers: [], type: .press))
474510
case .right:
475-
KeyEvent(character: nil, keycode: VTKeyCode.right, modifiers: [],
476-
type: .press)
511+
.key(.init(character: nil, keycode: VTKeyCode.right, modifiers: [], type: .press))
477512
}
478513

514+
case let .DeviceAttributes(attributes):
515+
.response(attributes)
516+
479517
case let .function(number, modifiers):
480-
KeyEvent(character: nil,
481-
keycode: UInt16(Int(VTKeyCode.F1) + number - 1),
482-
modifiers: modifiers, type: .press)
518+
.key(.init(character: nil, keycode: UInt16(Int(VTKeyCode.F1) + number - 1),
519+
modifiers: modifiers, type: .press))
483520

484521
case .unknown(_):
485522
nil

Sources/VirtualTerminal/Platform/POSIXTerminal.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ internal final actor POSIXTerminal: VTTerminal {
221221
return parser.parse(ArraySlice(buffer))
222222
}
223223

224-
return sequences.compactMap { $0.event.map { VTEvent.key($0) } }
224+
return sequences.compactMap(\.event)
225225
}
226226
continuation.yield(events)
227227
} catch {

Sources/VirtualTerminal/Rendering/VTRenderer.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ public final class VTRenderer: Sendable {
134134
/// The underlying platform-specific terminal implementation.
135135
private let _terminal: PlatformTerminal
136136

137+
/// The terminal capabilities.
138+
private let capabilities: VTCapabilities
139+
137140
/// The currently displayed buffer state (visible to the user).
138141
package nonisolated(unsafe) var front: VTBuffer
139142

@@ -159,6 +162,7 @@ public final class VTRenderer: Sendable {
159162
/// - Throws: Terminal initialization errors
160163
public init(mode: VTMode) async throws {
161164
self._terminal = try await PlatformTerminal(mode: mode)
165+
self.capabilities = await VTCapabilities.query(self._terminal)
162166
self.front = VTBuffer(size: _terminal.size)
163167
self.back = VTBuffer(size: _terminal.size)
164168
}

0 commit comments

Comments
 (0)