1- import { useCallback , useState , memo } from "react"
1+ import { useCallback , useState , memo , useMemo } from "react"
22import { useEvent } from "react-use"
33import { ChevronDown , Skull } from "lucide-react"
44
@@ -16,32 +16,25 @@ import CodeBlock from "../common/CodeBlock"
1616interface CommandExecutionProps {
1717 executionId : string
1818 text ?: string
19+ icon ?: JSX . Element | null
20+ title ?: JSX . Element | null
1921}
2022
21- const parseCommandAndOutput = ( text : string ) => {
22- const index = text . indexOf ( COMMAND_OUTPUT_STRING )
23- if ( index === - 1 ) {
24- return { command : text , output : "" }
25- }
26- return {
27- command : text . slice ( 0 , index ) ,
28- output : text . slice ( index + COMMAND_OUTPUT_STRING . length ) ,
29- }
30- }
31-
32- export const CommandExecution = ( { executionId, text } : CommandExecutionProps ) => {
23+ export const CommandExecution = ( { executionId, text, icon, title } : CommandExecutionProps ) => {
3324 const { terminalShellIntegrationDisabled = false } = useExtensionState ( )
3425
3526 // If we aren't opening the VSCode terminal for this command then we default
3627 // to expanding the command execution output.
3728 const [ isExpanded , setIsExpanded ] = useState ( terminalShellIntegrationDisabled )
3829
39- const [ status , setStatus ] = useState < CommandExecutionStatus | null > ( null )
40- const { command : initialCommand , output : initialOutput } = text
41- ? parseCommandAndOutput ( text )
42- : { command : "" , output : "" }
30+ const { command : initialCommand , output : initialOutput } = useMemo (
31+ ( ) => ( text ? parseCommandAndOutput ( text ) : { command : "" , output : "" } ) ,
32+ [ text ] ,
33+ )
34+
4335 const [ output , setOutput ] = useState ( initialOutput )
4436 const [ command , setCommand ] = useState ( initialCommand )
37+ const [ status , setStatus ] = useState < CommandExecutionStatus | null > ( null )
4538
4639 const onMessage = useCallback (
4740 ( event : MessageEvent ) => {
@@ -81,62 +74,84 @@ export const CommandExecution = ({ executionId, text }: CommandExecutionProps) =
8174 useEvent ( "message" , onMessage )
8275
8376 return (
84- < div className = "w-full bg-vscode-editor-background border border-vscode-border rounded-xs p-2" >
85- < CodeBlock source = { text ? parseCommandAndOutput ( text ) . command : command } language = "shell" />
86- < div className = "flex flex-row items-center justify-between gap-2 px-1" >
77+ < >
78+ < div className = "flex flex-row items-center justify-between gap-2 mb-1" >
8779 < div className = "flex flex-row items-center gap-1" >
88- { status ?. status === "started" && (
89- < div className = "flex flex-row items-center gap-2 font-mono text-xs" >
90- < div className = "rounded-full size-1.5 bg-lime-400" />
91- < div > Running</ div >
92- { status . pid && < div className = "whitespace-nowrap" > (PID: { status . pid } )</ div > }
93- < Button
94- variant = "ghost"
95- size = "icon"
96- onClick = { ( ) =>
97- vscode . postMessage ( { type : "terminalOperation" , terminalOperation : "abort" } )
98- } >
99- < Skull />
80+ { icon }
81+ { title }
82+ </ div >
83+ < div className = "flex flex-row items-center justify-between gap-2 px-1" >
84+ < div className = "flex flex-row items-center gap-1" >
85+ { status ?. status === "started" && (
86+ < div className = "flex flex-row items-center gap-2 font-mono text-xs" >
87+ < div className = "rounded-full size-1.5 bg-lime-400" />
88+ < div > Running</ div >
89+ { status . pid && < div className = "whitespace-nowrap" > (PID: { status . pid } )</ div > }
90+ < Button
91+ variant = "ghost"
92+ size = "icon"
93+ onClick = { ( ) =>
94+ vscode . postMessage ( { type : "terminalOperation" , terminalOperation : "abort" } )
95+ } >
96+ < Skull />
97+ </ Button >
98+ </ div >
99+ ) }
100+ { status ?. status === "exited" && (
101+ < div className = "flex flex-row items-center gap-2 font-mono text-xs" >
102+ < div
103+ className = { cn (
104+ "rounded-full size-1.5" ,
105+ status . exitCode === 0 ? "bg-lime-400" : "bg-red-400" ,
106+ ) }
107+ />
108+ < div className = "whitespace-nowrap" > Exited ({ status . exitCode } )</ div >
109+ </ div >
110+ ) }
111+ { output . length > 0 && (
112+ < Button variant = "ghost" size = "icon" onClick = { ( ) => setIsExpanded ( ! isExpanded ) } >
113+ < ChevronDown
114+ className = { cn ( "size-4 transition-transform duration-300" , {
115+ "rotate-180" : isExpanded ,
116+ } ) }
117+ />
100118 </ Button >
101- </ div >
102- ) }
103- { status ?. status === "exited" && (
104- < div className = "flex flex-row items-center gap-2 font-mono text-xs" >
105- < div
106- className = { cn (
107- "rounded-full size-1.5" ,
108- status . exitCode === 0 ? "bg-lime-400" : "bg-red-400" ,
109- ) }
110- />
111- < div className = "whitespace-nowrap" > Exited ({ status . exitCode } )</ div >
112- </ div >
113- ) }
114- { output . length > 0 && (
115- < Button variant = "ghost" size = "icon" onClick = { ( ) => setIsExpanded ( ! isExpanded ) } >
116- < ChevronDown
117- className = { cn ( "size-4 transition-transform duration-300" , {
118- "rotate-180" : isExpanded ,
119- } ) }
120- />
121- </ Button >
122- ) }
119+ ) }
120+ </ div >
123121 </ div >
124122 </ div >
125- < MemoizedOutputContainer isExpanded = { isExpanded } output = { output } />
126- </ div >
123+
124+ < div className = "w-full bg-vscode-editor-background border border-vscode-border rounded-xs p-2" >
125+ < CodeBlock source = { command } language = "shell" />
126+ < OutputContainer isExpanded = { isExpanded } output = { output } />
127+ </ div >
128+ </ >
127129 )
128130}
129131
130132CommandExecution . displayName = "CommandExecution"
131133
132- const OutputContainer = ( { isExpanded, output } : { isExpanded : boolean ; output : string } ) => (
134+ const OutputContainerInternal = ( { isExpanded, output } : { isExpanded : boolean ; output : string } ) => (
133135 < div
134- className = { cn ( "mt-1 pt-1 border-t border-border/25 overflow-hidden transition-[max-height] duration-300 " , {
136+ className = { cn ( "overflow-hidden" , {
135137 "max-h-0" : ! isExpanded ,
136- "max-h-[100%]" : isExpanded ,
138+ "max-h-[100%] mt-1 pt-1 border-t border-border/25 " : isExpanded ,
137139 } ) } >
138140 { output . length > 0 && < CodeBlock source = { output } language = "log" /> }
139141 </ div >
140142)
141143
142- const MemoizedOutputContainer = memo ( OutputContainer )
144+ const OutputContainer = memo ( OutputContainerInternal )
145+
146+ const parseCommandAndOutput = ( text : string ) => {
147+ const index = text . indexOf ( COMMAND_OUTPUT_STRING )
148+
149+ if ( index === - 1 ) {
150+ return { command : text , output : "" }
151+ }
152+
153+ return {
154+ command : text . slice ( 0 , index ) ,
155+ output : text . slice ( index + COMMAND_OUTPUT_STRING . length ) ,
156+ }
157+ }
0 commit comments