@@ -35,15 +35,17 @@ const xTermTheme = {
35
35
36
36
export function Terminal ( props : HtmlHTMLAttributes < HTMLDivElement > ) {
37
37
const ref = useRef < HTMLDivElement > ( null ) ;
38
- const { term, resetTerm } = useTerminalContext ( ) ;
38
+ const { term, resetTerm, socket } = useTerminalContext ( ) ;
39
39
40
40
// we have to recreate the terminal on every mount of the component.
41
41
// not sure why we cannot restore.
42
42
useEffect ( resetTerm , [ resetTerm ] ) ;
43
43
44
- // resetting term also retriggers this guy:
44
+ // resetting term also retriggers this guy.
45
+ // having socket as a dependencies should make sure we retrigger when
46
+ // the if socket had connection issues.
45
47
useEffect ( ( ) => {
46
- if ( ! ref . current || ! term ) return ;
48
+ if ( ! ref . current || ! term || ! socket ) return ;
47
49
const ele = ref . current ;
48
50
function copyPasteHandler ( e : KeyboardEvent ) {
49
51
if ( ! term ) return false ;
@@ -83,23 +85,19 @@ export function Terminal(props: HtmlHTMLAttributes<HTMLDivElement>) {
83
85
const fitAddon = new xTermFitAddon ( ) ;
84
86
term . loadAddon ( fitAddon ) ;
85
87
term . open ( ele ) ;
88
+ term . focus ( ) ;
86
89
fitAddon . fit ( ) ;
87
- console . log ( term . rows ) ;
88
90
89
- // Resize on window resize
90
91
const resizeObserver = new ResizeObserver ( ( ) => {
91
92
fitAddon . fit ( ) ;
92
93
} ) ;
93
94
resizeObserver . observe ( ele ) ;
94
95
95
- // On visibility change rerender terminal
96
- console . log ( "Term mounted" ) ;
97
96
return ( ) => {
98
- // term.dispose();
97
+ term . dispose ( ) ;
99
98
if ( ele ) resizeObserver . unobserve ( ele ) ;
100
- console . log ( "Term unmounted" ) ;
101
99
} ;
102
- } , [ term ] ) ;
100
+ } , [ term , ref , socket ] ) ;
103
101
104
102
return < div ref = { ref } { ...props } /> ;
105
103
}
@@ -139,12 +137,45 @@ export function TerminalContextProvider({ children }: { children: React.ReactNod
139
137
} ) ;
140
138
} , [ ] ) ;
141
139
142
- useEffect ( resetTerm , [ resetTerm ] ) ;
140
+ // useEffect(resetTerm, [resetTerm]);
141
+
142
+ const onCursorUpdate = useCallback (
143
+ ( data : { x : number ; y : number } ) => {
144
+ if ( ! term ) return ;
145
+ // xterm uses 1-based indexing
146
+ // console.log("Cursor update", data);
147
+ term . write ( `\x1b[${ data . y + 1 } ;${ data . x + 1 } H` ) ;
148
+ } ,
149
+ [ term ]
150
+ ) ;
151
+
152
+ const onOutput = useCallback (
153
+ ( data : { output : string [ ] } ) => {
154
+ if ( ! term ) return ;
155
+ // term!.clear(); seems to be preferred from the documentation,
156
+ // but it leaves the prompt on the first line in place - which we here do not want
157
+ // ideally we would directly access the buffer.
158
+ // console.log("ptyOutput", data);
159
+ term . reset ( ) ;
160
+ data . output . forEach ( ( line , index ) => {
161
+ if ( index < data . output . length - 1 ) {
162
+ term . writeln ( line ) ;
163
+ } else {
164
+ // Workaround: strip all trailing whitespaces except for one
165
+ // not a perfect fix (one wrong space remains when backspacing)
166
+ const stripped_line = line . replace ( / \s + $ / , " " ) ;
167
+ term . write ( stripped_line ) ;
168
+ }
169
+ } ) ;
170
+ } ,
171
+ [ term ]
172
+ ) ;
143
173
144
174
// Attach socket handler
145
175
useEffect ( ( ) => {
146
176
if ( ! term || ! isConnected || ! socket ) return ;
147
177
178
+ // spaces are needed to clear out the longer "Connecting..."
148
179
term . writeln ( "\rConnected! " ) ;
149
180
150
181
const onInput = term . onData ( ( data ) => {
@@ -156,33 +187,10 @@ export function TerminalContextProvider({ children }: { children: React.ReactNod
156
187
} ) ;
157
188
158
189
const onResize = term . onResize ( ( { cols, rows } ) => {
159
- // console.log(`Terminal was resized to ${cols} cols and ${rows} rows.`);
190
+ console . log ( `Terminal was resized to ${ cols } cols and ${ rows } rows.` ) ;
160
191
socket . emit ( "ptyResize" , { cols, rows : rows } ) ;
161
192
} ) ;
162
193
163
- function onOutput ( data : { output : string [ ] } ) {
164
- // term!.clear(); seems to be preferred from the documentation,
165
- // but it leaves the prompt on the first line in place - which we here do not want
166
- // ideally we would directly access the buffer.
167
- // console.log("ptyOutput", data);
168
- term ! . reset ( ) ;
169
- data . output . forEach ( ( line , index ) => {
170
- if ( index < data . output . length - 1 ) {
171
- term ! . writeln ( line ) ;
172
- } else {
173
- // Workaround: strip all trailing whitespaces except for one
174
- // not a perfect fix (one wrong space remains when backspacing)
175
- const stripped_line = line . replace ( / \s + $ / , " " ) ;
176
- term ! . write ( stripped_line ) ;
177
- }
178
- } ) ;
179
- }
180
-
181
- function onCursorUpdate ( data : { x : number ; y : number } ) {
182
- // xterm uses 1-based indexing
183
- term ! . write ( `\x1b[${ data . y + 1 } ;${ data . x + 1 } H` ) ;
184
- }
185
-
186
194
socket . on ( "ptyOutput" , onOutput ) ;
187
195
socket . on ( "ptyCursorPosition" , onCursorUpdate ) ;
188
196
@@ -197,7 +205,7 @@ export function TerminalContextProvider({ children }: { children: React.ReactNod
197
205
socket . off ( "ptyOutput" , onOutput ) ;
198
206
socket . off ( "ptyCursorPosition" , onCursorUpdate ) ;
199
207
} ;
200
- } , [ isConnected , term , socket ] ) ;
208
+ } , [ isConnected , term , socket , onOutput , onCursorUpdate ] ) ;
201
209
202
210
// make first responder directly after opening
203
211
useEffect ( ( ) => {
@@ -211,7 +219,6 @@ export function TerminalContextProvider({ children }: { children: React.ReactNod
211
219
console . error ( "No socket available" ) ;
212
220
return ;
213
221
}
214
-
215
222
socket . emit ( "ptyInput" , { input : t } ) ;
216
223
}
217
224
@@ -220,7 +227,6 @@ export function TerminalContextProvider({ children }: { children: React.ReactNod
220
227
console . error ( "No socket available" ) ;
221
228
return ;
222
229
}
223
-
224
230
socket . emit ( "ptyInput" , { input : "\x15" } ) ;
225
231
}
226
232
0 commit comments