11import GhosttyVtRaw
22
33extension Terminal {
4+ private static let frameDataKeys : [ GhosttyRenderStateData ] = [
5+ GHOSTTY_RENDER_STATE_DATA_COLS,
6+ GHOSTTY_RENDER_STATE_DATA_ROWS,
7+ GHOSTTY_RENDER_STATE_DATA_DIRTY,
8+ ]
9+
10+ private static let cursorDataKeys : [ GhosttyRenderStateData ] = [
11+ GHOSTTY_RENDER_STATE_DATA_CURSOR_VISIBLE,
12+ GHOSTTY_RENDER_STATE_DATA_CURSOR_BLINKING,
13+ GHOSTTY_RENDER_STATE_DATA_CURSOR_PASSWORD_INPUT,
14+ GHOSTTY_RENDER_STATE_DATA_CURSOR_VIEWPORT_HAS_VALUE,
15+ GHOSTTY_RENDER_STATE_DATA_CURSOR_VISUAL_STYLE,
16+ ]
17+
18+ private static let cursorPositionDataKeys : [ GhosttyRenderStateData ] = [
19+ GHOSTTY_RENDER_STATE_DATA_CURSOR_VIEWPORT_X,
20+ GHOSTTY_RENDER_STATE_DATA_CURSOR_VIEWPORT_Y,
21+ GHOSTTY_RENDER_STATE_DATA_CURSOR_VIEWPORT_WIDE_TAIL,
22+ ]
23+
424 /// Captures a Swift-owned incremental render update.
525 ///
626 /// This uses libghostty-vt's two-phase render update. Terminal access is
@@ -16,85 +36,75 @@ extension Terminal {
1636 try Self . check ( beginResult)
1737 try Self . check ( ghostty_render_state_end_update ( renderState) )
1838
19- let size = try frameSize ( )
20- let dirtyState = try frameDirtyState ( )
21- let theme = try frameTheme ( refresh: dirtyState == . full || cachedTheme == nil )
39+ let metadata = try frameMetadata ( )
40+ let theme = try frameTheme ( refresh: metadata. dirtyState == . full || cachedTheme == nil )
2241 let cursor = try frameCursor ( )
23- let rows = try changedRows ( for: dirtyState, size: size)
42+ let rows = try changedRows ( for: metadata . dirtyState, size: metadata . size)
2443
2544 try clearGlobalDirtyState ( )
2645
2746 return . init(
28- size: size,
29- dirtyState: dirtyState,
47+ size: metadata . size,
48+ dirtyState: metadata . dirtyState,
3049 theme: theme,
3150 cursor: cursor,
3251 rows: rows
3352 )
3453 }
3554
36- private func frameSize ( ) throws -> Size {
55+ private func frameMetadata ( ) throws -> ( size : Size , dirtyState : TerminalFrame . DirtyState ) {
3756 var columns : UInt16 = 0
3857 var rows : UInt16 = 0
39- try Self . check ( ghostty_render_state_get ( renderState, GHOSTTY_RENDER_STATE_DATA_COLS, & columns) )
40- try Self . check ( ghostty_render_state_get ( renderState, GHOSTTY_RENDER_STATE_DATA_ROWS, & rows) )
41- return . init( columns: columns, rows: rows)
42- }
58+ var rawDirtyState : GhosttyRenderStateDirty = GHOSTTY_RENDER_STATE_DIRTY_FALSE
59+ var written = 0
4360
44- private func frameDirtyState( ) throws -> TerminalFrame . DirtyState {
45- var rawState : GhosttyRenderStateDirty = GHOSTTY_RENDER_STATE_DIRTY_FALSE
46- try Self . check ( ghostty_render_state_get ( renderState, GHOSTTY_RENDER_STATE_DATA_DIRTY, & rawState) )
61+ let result = COutputPointers . withPointers ( & columns, & rows, & rawDirtyState) { values in
62+ Self . frameDataKeys. withUnsafeBufferPointer { keyBuffer in
63+ ghostty_render_state_get_multi (
64+ renderState,
65+ keyBuffer. count,
66+ keyBuffer. baseAddress,
67+ values. baseAddress,
68+ & written
69+ )
70+ }
71+ }
72+ try Self . check ( result)
73+ guard written == Self . frameDataKeys. count else {
74+ throw TerminalError . unexpectedResult
75+ }
4776
48- switch rawState {
77+ let dirtyState : TerminalFrame . DirtyState
78+ switch rawDirtyState {
4979 case GHOSTTY_RENDER_STATE_DIRTY_PARTIAL:
50- return . partial
80+ dirtyState = . partial
5181 case GHOSTTY_RENDER_STATE_DIRTY_FULL:
52- return . full
82+ dirtyState = . full
5383 default :
54- return . clean
84+ dirtyState = . clean
5585 }
86+
87+ return ( . init( columns: columns, rows: rows) , dirtyState)
5688 }
5789
5890 private func frameTheme( refresh: Bool ) throws -> TerminalFrame . Theme {
5991 if !refresh, let cachedTheme {
6092 return cachedTheme
6193 }
6294
63- var background = GhosttyColorRgb ( )
64- var foreground = GhosttyColorRgb ( )
65- var hasCursor = false
66- var cursor = GhosttyColorRgb ( )
67- var palette = [ GhosttyColorRgb] ( repeating: GhosttyColorRgb ( ) , count: 256 )
68-
69- try Self . check (
70- ghostty_render_state_get ( renderState, GHOSTTY_RENDER_STATE_DATA_COLOR_BACKGROUND, & background)
71- )
72- try Self . check (
73- ghostty_render_state_get ( renderState, GHOSTTY_RENDER_STATE_DATA_COLOR_FOREGROUND, & foreground)
74- )
75- try Self . check (
76- ghostty_render_state_get ( renderState, GHOSTTY_RENDER_STATE_DATA_COLOR_CURSOR_HAS_VALUE, & hasCursor)
77- )
78- if hasCursor {
79- try Self . check (
80- ghostty_render_state_get ( renderState, GHOSTTY_RENDER_STATE_DATA_COLOR_CURSOR, & cursor)
81- )
82- }
95+ var colors = GhosttyRenderStateColors ( )
96+ colors. size = MemoryLayout< GhosttyRenderStateColors> . size
97+ try Self . check ( ghostty_render_state_colors_get ( renderState, & colors) )
8398
84- let paletteResult = palette. withUnsafeMutableBufferPointer { buffer in
85- ghostty_render_state_get (
86- renderState,
87- GHOSTTY_RENDER_STATE_DATA_COLOR_PALETTE,
88- UnsafeMutableRawPointer ( buffer. baseAddress!)
89- )
99+ let palette = withUnsafeBytes ( of: & colors. palette) { bytes in
100+ bytes. bindMemory ( to: GhosttyColorRgb . self) . map ( Self . color ( from: ) )
90101 }
91- try Self . check ( paletteResult)
92102
93103 let theme = TerminalFrame . Theme (
94- foreground: Self . color ( from: foreground) ,
95- background: Self . color ( from: background) ,
96- cursor: hasCursor ? Self . color ( from: cursor) : nil ,
97- palette: palette. map ( Self . color ( from : ) )
104+ foreground: Self . color ( from: colors . foreground) ,
105+ background: Self . color ( from: colors . background) ,
106+ cursor: colors . cursor_has_value ? Self . color ( from: colors . cursor) : nil ,
107+ palette: palette
98108 )
99109 cachedTheme = theme
100110 return theme
@@ -107,25 +117,28 @@ extension Terminal {
107117 var hasPosition = false
108118 var style : GhosttyRenderStateCursorVisualStyle = GHOSTTY_RENDER_STATE_CURSOR_VISUAL_STYLE_BLOCK
109119
110- try Self . check ( ghostty_render_state_get ( renderState, GHOSTTY_RENDER_STATE_DATA_CURSOR_VISIBLE, & visible) )
111- try Self . check ( ghostty_render_state_get ( renderState, GHOSTTY_RENDER_STATE_DATA_CURSOR_BLINKING, & blinking) )
112- try Self . check (
113- ghostty_render_state_get (
114- renderState,
115- GHOSTTY_RENDER_STATE_DATA_CURSOR_PASSWORD_INPUT,
116- & passwordInput
117- )
118- )
119- try Self . check (
120- ghostty_render_state_get (
121- renderState,
122- GHOSTTY_RENDER_STATE_DATA_CURSOR_VIEWPORT_HAS_VALUE,
123- & hasPosition
124- )
125- )
126- try Self . check (
127- ghostty_render_state_get ( renderState, GHOSTTY_RENDER_STATE_DATA_CURSOR_VISUAL_STYLE, & style)
128- )
120+ var written = 0
121+ let result = COutputPointers . withPointers (
122+ & visible,
123+ & blinking,
124+ & passwordInput,
125+ & hasPosition,
126+ & style
127+ ) { values in
128+ Self . cursorDataKeys. withUnsafeBufferPointer { keyBuffer in
129+ ghostty_render_state_get_multi (
130+ renderState,
131+ keyBuffer. count,
132+ keyBuffer. baseAddress,
133+ values. baseAddress,
134+ & written
135+ )
136+ }
137+ }
138+ try Self . check ( result)
139+ guard written == Self . cursorDataKeys. count else {
140+ throw TerminalError . unexpectedResult
141+ }
129142
130143 guard hasPosition else {
131144 return . init(
@@ -141,19 +154,22 @@ extension Terminal {
141154 var column : UInt16 = 0
142155 var row : UInt16 = 0
143156 var isWideTail = false
144- try Self . check (
145- ghostty_render_state_get ( renderState, GHOSTTY_RENDER_STATE_DATA_CURSOR_VIEWPORT_X, & column)
146- )
147- try Self . check (
148- ghostty_render_state_get ( renderState, GHOSTTY_RENDER_STATE_DATA_CURSOR_VIEWPORT_Y, & row)
149- )
150- try Self . check (
151- ghostty_render_state_get (
152- renderState,
153- GHOSTTY_RENDER_STATE_DATA_CURSOR_VIEWPORT_WIDE_TAIL,
154- & isWideTail
155- )
156- )
157+ written = 0
158+ let positionResult = COutputPointers . withPointers ( & column, & row, & isWideTail) { values in
159+ Self . cursorPositionDataKeys. withUnsafeBufferPointer { keyBuffer in
160+ ghostty_render_state_get_multi (
161+ renderState,
162+ keyBuffer. count,
163+ keyBuffer. baseAddress,
164+ values. baseAddress,
165+ & written
166+ )
167+ }
168+ }
169+ try Self . check ( positionResult)
170+ guard written == Self . cursorPositionDataKeys. count else {
171+ throw TerminalError . unexpectedResult
172+ }
157173
158174 return . init(
159175 isVisible: visible,
0 commit comments