@@ -3,10 +3,14 @@ import { useProjectStore } from "@/stores/project";
33import { useTerminalStore } from "@/stores/terminal" ;
44import { detectLineEnding , detectIndentation , detectEncoding , getLanguageFromExtension } from "@/stores/buffers/utils" ;
55import { Button } from "@/components/ui/button" ;
6- import { Terminal , Check } from "lucide-react" ;
6+ import { Terminal , Check , RefreshCw , AlertCircle , Loader2 } from "lucide-react" ;
77import React , { useState , useEffect , useRef } from "react" ;
88import { GitBranchSwitcher } from "./git-branch-switcher" ;
99import { Popover , PopoverTrigger , PopoverContent } from "@/components/ui/popover" ;
10+ import { typescriptLSPClient } from '@/services/typescript-lsp' ;
11+ // LSP status types
12+ type LSPStatus = 'running' | 'loading' | 'error' | 'stopped' ;
13+
1014import { Switch } from "@/components/ui/switch" ;
1115
1216
@@ -18,6 +22,54 @@ export function WorkspaceFooter() {
1822 const { buffers, activeBufferId } = useBufferStore ( ) ;
1923 const { isVisible : isTerminalVisible , setVisible : setTerminalVisible , tabs, createTab } = useTerminalStore ( ) ;
2024
25+ // LSP status state
26+ const [ lspStatus , setLspStatus ] = useState < LSPStatus > ( 'loading' ) ;
27+ const [ lspError , setLspError ] = useState < string | null > ( null ) ;
28+ const [ lspPopoverOpen , setLspPopoverOpen ] = useState ( false ) ;
29+ const [ lspRestarting , setLspRestarting ] = useState ( false ) ;
30+
31+ // Poll LSP status
32+ useEffect ( ( ) => {
33+ let mounted = true ;
34+ async function pollStatus ( ) {
35+ setLspStatus ( 'loading' ) ;
36+ setLspError ( null ) ;
37+ try {
38+ const status = await typescriptLSPClient . getStatus ( ) ;
39+ if ( ! mounted ) return ;
40+ if ( status . isRunning ) {
41+ setLspStatus ( 'running' ) ;
42+ } else {
43+ setLspStatus ( 'stopped' ) ;
44+ }
45+ } catch ( e : any ) {
46+ setLspStatus ( 'error' ) ;
47+ setLspError ( e ?. message || 'Unknown error' ) ;
48+ }
49+ }
50+ pollStatus ( ) ;
51+ const interval = setInterval ( pollStatus , 4000 ) ;
52+ return ( ) => { mounted = false ; clearInterval ( interval ) ; } ;
53+ } , [ ] ) ;
54+
55+ // Restart LSP
56+ const handleRestartLSP = async ( ) => {
57+ setLspRestarting ( true ) ;
58+ setLspError ( null ) ;
59+ setLspStatus ( 'loading' ) ;
60+ try {
61+ if ( currentProject ) {
62+ await typescriptLSPClient . initialize ( currentProject ) ;
63+ }
64+ setLspStatus ( 'running' ) ;
65+ } catch ( e : any ) {
66+ setLspStatus ( 'error' ) ;
67+ setLspError ( e ?. message || 'Failed to restart LSP' ) ;
68+ } finally {
69+ setLspRestarting ( false ) ;
70+ }
71+ } ;
72+
2173
2274 // Index status state
2375 const [ indexStatus , setIndexStatus ] = useState < {
@@ -271,6 +323,57 @@ export function WorkspaceFooter() {
271323 < div className = "flex items-center justify-between w-full text-xs text-muted-foreground" >
272324 < div className = "flex items-center gap-4" >
273325 < GitBranchSwitcher />
326+ { /* LSP Status Button */ }
327+ < Popover open = { lspPopoverOpen } onOpenChange = { setLspPopoverOpen } >
328+ < PopoverTrigger asChild >
329+ < Button
330+ variant = "ghost"
331+ size = "sm"
332+ className = "h-5 px-2 flex items-center gap-1 hover:bg-gray-700"
333+ onClick = { ( ) => setLspPopoverOpen ( true ) }
334+ aria-label = "LSP Status"
335+ >
336+ { lspStatus === 'loading' && < Loader2 className = "h-3 w-3 animate-spin text-yellow-400" /> }
337+ { lspStatus === 'running' && < Check className = "h-3 w-3 text-green-400" /> }
338+ { lspStatus === 'error' && < AlertCircle className = "h-3 w-3 text-red-400" /> }
339+ { lspStatus === 'stopped' && < AlertCircle className = "h-3 w-3 text-gray-400" /> }
340+ < span className = "hidden sm:inline text-xs" >
341+ { lspStatus === 'loading' && 'Typescript' }
342+ { lspStatus === 'running' && 'Typescript' }
343+ { lspStatus === 'error' && 'Typescript: Error' }
344+ { lspStatus === 'stopped' && 'Typescript: Stopped' }
345+ </ span >
346+ </ Button >
347+ </ PopoverTrigger >
348+ < PopoverContent className = "w-64 p-4" align = "start" >
349+ < div className = "flex flex-col gap-2" >
350+ < div className = "font-bold text-white text-xs mb-1" > TypeScript LSP Status</ div >
351+ < div className = "flex items-center gap-2" >
352+ { lspStatus === 'loading' && < Loader2 className = "h-4 w-4 animate-spin text-yellow-400" /> }
353+ { lspStatus === 'running' && < Check className = "h-4 w-4 text-green-400" /> }
354+ { lspStatus === 'error' && < AlertCircle className = "h-4 w-4 text-red-400" /> }
355+ { lspStatus === 'stopped' && < AlertCircle className = "h-4 w-4 text-gray-400" /> }
356+ < span className = "text-xs" >
357+ { lspStatus === 'loading' && 'Loading...' }
358+ { lspStatus === 'running' && 'Running' }
359+ { lspStatus === 'error' && 'Error' }
360+ { lspStatus === 'stopped' && 'Stopped' }
361+ </ span >
362+ </ div >
363+ { lspError && < div className = "text-xs text-red-400" > { lspError } </ div > }
364+ < Button
365+ size = "sm"
366+ variant = "secondary"
367+ className = "mt-2 flex items-center gap-1"
368+ onClick = { handleRestartLSP }
369+ disabled = { lspRestarting || lspStatus === 'loading' }
370+ >
371+ < RefreshCw className = { lspRestarting ? 'animate-spin h-3 w-3' : 'h-3 w-3' } />
372+ { lspRestarting ? 'Restarting...' : 'Restart LSP' }
373+ </ Button >
374+ </ div >
375+ </ PopoverContent >
376+ </ Popover >
274377 < Button
275378 variant = "ghost"
276379 size = "sm"
0 commit comments