| ... | @@ -129,6 +129,7 @@ export default function ThreadTerminalDrawer({ | ... | @@ -129,6 +129,7 @@ export default function ThreadTerminalDrawer({ |
| 129 | startY: number; | 129 | startY: number; |
| 130 | startHeight: number; | 130 | startHeight: number; |
| 131 | } | null>(null); | 131 | } | null>(null); |
| | 132 | const didResizeDuringDragRef = useRef(false); |
| 132 | | 133 | |
| 133 | useEffect(() => { | 134 | useEffect(() => { |
| 134 | onHeightChangeRef.current = onHeightChange; | 135 | onHeightChangeRef.current = onHeightChange; |
| ... | @@ -152,11 +153,17 @@ export default function ThreadTerminalDrawer({ | ... | @@ -152,11 +153,17 @@ export default function ThreadTerminalDrawer({ |
| 152 | [], | 153 | [], |
| 153 | ); | 154 | ); |
| 154 | | 155 | |
| 155 | const fitAndResizeTerminal = useCallback(() => { | 156 | const fitAndResizeTerminal = useCallback((preserveBottom = false) => { |
| 156 | const activeTerminal = terminalRef.current; | 157 | const activeTerminal = terminalRef.current; |
| 157 | const activeFitAddon = fitAddonRef.current; | 158 | const activeFitAddon = fitAddonRef.current; |
| 158 | if (!activeTerminal || !activeFitAddon) return; | 159 | if (!activeTerminal || !activeFitAddon) return; |
| | 160 | const wasAtBottom = |
| | 161 | preserveBottom && |
| | 162 | activeTerminal.buffer.active.viewportY >= activeTerminal.buffer.active.baseY; |
| 159 | activeFitAddon.fit(); | 163 | activeFitAddon.fit(); |
| | 164 | if (wasAtBottom) { |
| | 165 | activeTerminal.scrollToBottom(); |
| | 166 | } |
| 160 | void api.terminal | 167 | void api.terminal |
| 161 | .resize({ | 168 | .resize({ |
| 162 | threadId, | 169 | threadId, |
| ... | @@ -177,13 +184,14 @@ export default function ThreadTerminalDrawer({ | ... | @@ -177,13 +184,14 @@ export default function ThreadTerminalDrawer({ |
| 177 | if (event.button !== 0) return; | 184 | if (event.button !== 0) return; |
| 178 | event.preventDefault(); | 185 | event.preventDefault(); |
| 179 | event.currentTarget.setPointerCapture(event.pointerId); | 186 | event.currentTarget.setPointerCapture(event.pointerId); |
| | 187 | didResizeDuringDragRef.current = false; |
| 180 | resizeStateRef.current = { | 188 | resizeStateRef.current = { |
| 181 | pointerId: event.pointerId, | 189 | pointerId: event.pointerId, |
| 182 | startY: event.clientY, | 190 | startY: event.clientY, |
| 183 | startHeight: drawerHeight, | 191 | startHeight: drawerHeightRef.current, |
| 184 | }; | 192 | }; |
| 185 | }, | 193 | }, |
| 186 | [drawerHeight], | 194 | [], |
| 187 | ); | 195 | ); |
| 188 | | 196 | |
| 189 | const handleResizePointerMove = useCallback( | 197 | const handleResizePointerMove = useCallback( |
| ... | @@ -194,6 +202,11 @@ export default function ThreadTerminalDrawer({ | ... | @@ -194,6 +202,11 @@ export default function ThreadTerminalDrawer({ |
| 194 | const clampedHeight = clampDrawerHeight( | 202 | const clampedHeight = clampDrawerHeight( |
| 195 | resizeState.startHeight + (resizeState.startY - event.clientY), | 203 | resizeState.startHeight + (resizeState.startY - event.clientY), |
| 196 | ); | 204 | ); |
| | 205 | if (clampedHeight === drawerHeightRef.current) { |
| | 206 | return; |
| | 207 | } |
| | 208 | didResizeDuringDragRef.current = true; |
| | 209 | drawerHeightRef.current = clampedHeight; |
| 197 | setDrawerHeight(clampedHeight); | 210 | setDrawerHeight(clampedHeight); |
| 198 | }, | 211 | }, |
| 199 | [], | 212 | [], |
| ... | @@ -207,9 +220,13 @@ export default function ThreadTerminalDrawer({ | ... | @@ -207,9 +220,13 @@ export default function ThreadTerminalDrawer({ |
| 207 | if (event.currentTarget.hasPointerCapture(event.pointerId)) { | 220 | if (event.currentTarget.hasPointerCapture(event.pointerId)) { |
| 208 | event.currentTarget.releasePointerCapture(event.pointerId); | 221 | event.currentTarget.releasePointerCapture(event.pointerId); |
| 209 | } | 222 | } |
| | 223 | if (!didResizeDuringDragRef.current) { |
| | 224 | return; |
| | 225 | } |
| 210 | syncHeight(drawerHeightRef.current); | 226 | syncHeight(drawerHeightRef.current); |
| | 227 | fitAndResizeTerminal(true); |
| 211 | }, | 228 | }, |
| 212 | [syncHeight], | 229 | [fitAndResizeTerminal, syncHeight], |
| 213 | ); | 230 | ); |
| 214 | | 231 | |
| 215 | useEffect(() => { | 232 | useEffect(() => { |
| ... | @@ -219,7 +236,7 @@ export default function ThreadTerminalDrawer({ | ... | @@ -219,7 +236,7 @@ export default function ThreadTerminalDrawer({ |
| 219 | if (changed) { | 236 | if (changed) { |
| 220 | setDrawerHeight(clampedHeight); | 237 | setDrawerHeight(clampedHeight); |
| 221 | } else { | 238 | } else { |
| 222 | fitAndResizeTerminal(); | 239 | fitAndResizeTerminal(true); |
| 223 | } | 240 | } |
| 224 | if (!resizeStateRef.current) { | 241 | if (!resizeStateRef.current) { |
| 225 | syncHeight(clampedHeight); | 242 | syncHeight(clampedHeight); |
| ... | @@ -366,7 +383,9 @@ export default function ThreadTerminalDrawer({ | ... | @@ -366,7 +383,9 @@ export default function ThreadTerminalDrawer({ |
| 366 | } | 383 | } |
| 367 | }); | 384 | }); |
| 368 | | 385 | |
| 369 | const fitTimer = window.setTimeout(fitAndResizeTerminal, 30); | 386 | const fitTimer = window.setTimeout(() => { |
| | 387 | fitAndResizeTerminal(true); |
| | 388 | }, 30); |
| 370 | void openTerminal(); | 389 | void openTerminal(); |
| 371 | | 390 | |
| 372 | return () => { | 391 | return () => { |
| ... | @@ -396,8 +415,12 @@ export default function ThreadTerminalDrawer({ | ... | @@ -396,8 +415,12 @@ export default function ThreadTerminalDrawer({ |
| 396 | const terminal = terminalRef.current; | 415 | const terminal = terminalRef.current; |
| 397 | const fitAddon = fitAddonRef.current; | 416 | const fitAddon = fitAddonRef.current; |
| 398 | if (!terminal || !fitAddon) return; | 417 | if (!terminal || !fitAddon) return; |
| | 418 | const wasAtBottom = terminal.buffer.active.viewportY >= terminal.buffer.active.baseY; |
| 399 | const frame = window.requestAnimationFrame(() => { | 419 | const frame = window.requestAnimationFrame(() => { |
| 400 | fitAddon.fit(); | 420 | fitAddon.fit(); |
| | 421 | if (wasAtBottom) { |
| | 422 | terminal.scrollToBottom(); |
| | 423 | } |
| 401 | void api.terminal | 424 | void api.terminal |
| 402 | .resize({ | 425 | .resize({ |
| 403 | threadId, | 426 | threadId, |