@@ -2,13 +2,13 @@ import { MouseEventHandler, MutableRefObject, useEffect, useRef, useState } from
22import styles from "./Terminal.module.css" ;
33import { OutputLine } from "./OutputLine" ;
44import { InputLine } from "./InputLine" ;
5- import { App , SettingsManager , useSettingsManager , useSystemManager , useVirtualRoot , VirtualFolder , WindowProps } from "@prozilla-os/core" ;
5+ import { useSettingsManager , useSystemManager , useVirtualRoot , VirtualFolder , WindowProps } from "@prozilla-os/core" ;
66import { HOSTNAME , USERNAME , WELCOME_MESSAGE } from "../constants/terminal.const" ;
77import { Stream } from "../core/stream" ;
88import { CommandResponse } from "../core/command" ;
99import { formatError } from "../core/_utils/terminal.utils" ;
1010import { CommandsManager } from "../core/commands" ;
11- import { ANSI , clamp , removeFromArray } from "@prozilla-os/shared" ;
11+ import { Ansi , ANSI , clamp , removeFromArray , Vector2 } from "@prozilla-os/shared" ;
1212
1313export interface TerminalProps extends WindowProps {
1414 path ?: string ;
@@ -32,13 +32,14 @@ export function Terminal({ app, path: startPath, input, setTitle, close: exit, a
3232 } ] ) ;
3333 const virtualRoot = useVirtualRoot ( ) ;
3434 const [ currentDirectory , setCurrentDirectory ] = useState < VirtualFolder > ( virtualRoot ?. navigate ( startPath ?? "~" ) as VirtualFolder ) ;
35- const inputRef = useRef ( null ) ;
35+ const inputRef = useRef < HTMLInputElement > ( null ) ;
3636 const [ historyIndex , setHistoryIndex ] = useState ( 0 ) ;
3737 const [ stream , setStream ] = useState < Stream | null > ( null ) ;
3838 const [ streamOutput , setStreamOutput ] = useState < string | null > ( null ) ;
39- const ref = useRef ( null ) ;
39+ const ref = useRef < HTMLDivElement > ( null ) ;
4040 const [ streamFocused , setStreamFocused ] = useState ( false ) ;
4141 const settingsManager = useSettingsManager ( ) ;
42+ const sizeRef = useRef ( Vector2 . ZERO ) ;
4243
4344 useEffect ( ( ) => {
4445 if ( currentDirectory != null )
@@ -49,11 +50,12 @@ export function Terminal({ app, path: startPath, input, setTitle, close: exit, a
4950 if ( ! inputRef . current || ! active )
5051 return ;
5152
52- ( inputRef . current as unknown as HTMLInputElement ) . focus ( ) ;
53+ inputRef . current . focus ( ) ;
5354 } , [ inputRef , active ] ) ;
5455
5556 const scrollDown = ( ) => {
56- ( ref . current as unknown as HTMLDivElement ) . scrollTop = ( ref . current as unknown as HTMLDivElement ) . scrollHeight ;
57+ if ( ref . current )
58+ ref . current . scrollTop = ref . current . scrollHeight ;
5759 } ;
5860
5961 useEffect ( ( ) => {
@@ -71,8 +73,33 @@ export function Terminal({ app, path: startPath, input, setTitle, close: exit, a
7173 scrollDown ( ) ;
7274 } , [ inputValue ] ) ;
7375
74- const prefix = `${ ANSI . fg . cyan + USERNAME } @${ HOSTNAME + ANSI . reset } :`
75- + `${ ANSI . fg . blue + ( ( currentDirectory ?. root || currentDirectory == null ) ? "/" : currentDirectory ?. path ) + ANSI . reset } $ ` ;
76+ useEffect ( ( ) => {
77+ if ( ! ref . current ) return ;
78+
79+ const measure = ( ) => {
80+ if ( ! ref . current ) return ;
81+
82+ const style = getComputedStyle ( ref . current ) ;
83+ const fontSize = parseFloat ( style . fontSize ) ;
84+ const charWidth = 0.585 * fontSize ;
85+ const charHeight = 1.25 * fontSize ;
86+ const { width, height } = ref . current . getBoundingClientRect ( ) ;
87+
88+ sizeRef . current . set (
89+ Math . ceil ( width / charWidth ) ,
90+ Math . ceil ( height / charHeight )
91+ ) ;
92+ } ;
93+
94+ const observer = new ResizeObserver ( measure ) ;
95+ observer . observe ( ref . current ) ;
96+ measure ( ) ;
97+
98+ return ( ) => observer . disconnect ( ) ;
99+ } , [ ref ] ) ;
100+
101+ const prefix = Ansi . cyan ( `${ USERNAME } @${ HOSTNAME } ` ) + ":"
102+ + Ansi . blue ( `${ ( ( currentDirectory ?. root || currentDirectory == null ) ? "/" : currentDirectory ?. path ) } ` ) + "$ " ;
76103
77104 const updatedHistory = history ;
78105 const pushHistory = ( entry : HistoryEntry ) => {
@@ -100,7 +127,7 @@ export function Terminal({ app, path: startPath, input, setTitle, close: exit, a
100127 let lastOutput : CommandResponse | null = null ;
101128
102129 stream . onAsync ( Stream . SEND_EVENT , async ( text ) => {
103- let output : CommandResponse = text as CommandResponse ;
130+ let output : CommandResponse = text ;
104131
105132 for ( const pipe of pipes ) {
106133 if ( output instanceof Stream )
@@ -110,7 +137,7 @@ export function Terminal({ app, path: startPath, input, setTitle, close: exit, a
110137 output = await handleInput ( output ? `${ pipe } ${ output as string } ` : pipe ) ;
111138 }
112139
113- if ( ( output as unknown ) instanceof Stream ) {
140+ if ( output instanceof Stream ) {
114141 stream . stop ( ) ;
115142 promptOutput ( ANSI . fg . red + "Stream failed" ) ;
116143 return ;
@@ -214,9 +241,10 @@ export function Terminal({ app, path: startPath, input, setTitle, close: exit, a
214241 exit,
215242 inputs,
216243 timestamp,
217- settingsManager : settingsManager as SettingsManager ,
244+ settingsManager : settingsManager ! ,
218245 systemManager,
219- app : app as App ,
246+ app : app ! ,
247+ size : sizeRef . current ,
220248 } ) ;
221249
222250 if ( response == null )
@@ -354,7 +382,7 @@ export function Terminal({ app, path: startPath, input, setTitle, close: exit, a
354382 onClick = { ( event ) => {
355383 if ( window . getSelection ( ) ?. toString ( ) === "" ) {
356384 event . preventDefault ( ) ;
357- ( inputRef . current as HTMLInputElement | null ) ?. focus ( ) ;
385+ ( inputRef . current ) ?. focus ( ) ;
358386 }
359387 } }
360388 >
0 commit comments