@@ -5,13 +5,16 @@ import {
55 LanguageClientOptions ,
66 Executable ,
77 ServerOptions ,
8+ State ,
9+ StateChangeEvent ,
810} from "vscode-languageclient/node"
911
1012let client : LanguageClient | undefined
1113let log : Pick <
1214 vscode . LogOutputChannel ,
1315 "trace" | "debug" | "info" | "warn" | "error" | "show"
1416>
17+ const onClientStateChange = new vscode . EventEmitter < StateChangeEvent > ( )
1518
1619export async function activate ( context : vscode . ExtensionContext ) {
1720 log = vscode . window . createOutputChannel ( "Squawk Client" , {
@@ -41,11 +44,15 @@ export async function activate(context: vscode.ExtensionContext) {
4144 )
4245 return version
4346 } catch ( error ) {
44- vscode . window . showErrorMessage ( `Failed to get server version: ${ error } ` )
47+ vscode . window . showErrorMessage (
48+ `Failed to get server version: ${ String ( error ) } ` ,
49+ )
4550 }
4651 } ) ,
4752 )
4853
54+ setupStatusBarItem ( context )
55+
4956 context . subscriptions . push (
5057 vscode . commands . registerCommand ( "squawk.showLogs" , ( ) => {
5158 client ?. outputChannel ?. show ( )
@@ -58,15 +65,19 @@ export async function activate(context: vscode.ExtensionContext) {
5865 } ) ,
5966 )
6067
61- const statusBarItem = vscode . window . createStatusBarItem (
62- vscode . StatusBarAlignment . Right ,
63- 100 ,
68+ context . subscriptions . push (
69+ vscode . commands . registerCommand ( "squawk.startServer" , async ( ) => {
70+ await startServer ( context )
71+ } ) ,
6472 )
65- statusBarItem . text = "Squawk"
66- statusBarItem . tooltip = "Click to show Squawk Language Server logs"
67- statusBarItem . command = "squawk.showLogs"
68- statusBarItem . show ( )
69- context . subscriptions . push ( statusBarItem )
73+
74+ context . subscriptions . push (
75+ vscode . commands . registerCommand ( "squawk.stopServer" , async ( ) => {
76+ await stopServer ( )
77+ } ) ,
78+ )
79+
80+ context . subscriptions . push ( onClientStateChange )
7081
7182 await startServer ( context )
7283}
@@ -83,12 +94,88 @@ function isSqlEditor(editor: vscode.TextEditor): boolean {
8394 return isSqlDocument ( editor . document )
8495}
8596
97+ function setupStatusBarItem ( context : vscode . ExtensionContext ) {
98+ const statusBarItem = vscode . window . createStatusBarItem (
99+ vscode . StatusBarAlignment . Left ,
100+ )
101+ statusBarItem . text = "Squawk"
102+ statusBarItem . command = "squawk.showLogs"
103+ context . subscriptions . push ( statusBarItem )
104+
105+ const onDidChangeActiveTextEditor = (
106+ editor : vscode . TextEditor | undefined ,
107+ ) => {
108+ if ( editor && isSqlEditor ( editor ) ) {
109+ updateStatusBarItem ( statusBarItem )
110+ statusBarItem . show ( )
111+ } else {
112+ statusBarItem . hide ( )
113+ }
114+ }
115+
116+ onDidChangeActiveTextEditor ( vscode . window . activeTextEditor )
117+
118+ context . subscriptions . push (
119+ vscode . window . onDidChangeActiveTextEditor ( ( editor ) => {
120+ onDidChangeActiveTextEditor ( editor )
121+ } ) ,
122+ )
123+
124+ context . subscriptions . push (
125+ onClientStateChange . event ( ( ) => {
126+ updateStatusBarItem ( statusBarItem )
127+ } ) ,
128+ )
129+ }
130+
131+ function updateStatusBarItem ( statusBarItem : vscode . StatusBarItem ) {
132+ if ( ! client ) {
133+ return
134+ }
135+ let statusText : string
136+ let icon : string
137+ let backgroundColor : vscode . ThemeColor | undefined
138+ switch ( client . state ) {
139+ case State . Stopped :
140+ statusText = "Stopped"
141+ icon = "$(error) "
142+ backgroundColor = new vscode . ThemeColor ( "statusBarItem.warningBackground" )
143+ break
144+ case State . Starting :
145+ statusText = "Starting..."
146+ icon = "$(loading~spin) "
147+ backgroundColor = undefined
148+ break
149+ case State . Running :
150+ statusText = "Running"
151+ icon = ""
152+ backgroundColor = undefined
153+ break
154+ default :
155+ assertNever ( client . state )
156+ }
157+
158+ statusBarItem . text = `${ icon } Squawk`
159+ statusBarItem . backgroundColor = backgroundColor
160+ statusBarItem . tooltip = `Status: ${ statusText } \nClick to show server logs`
161+ }
162+
86163function getSquawkPath ( context : vscode . ExtensionContext ) : vscode . Uri {
87164 const ext = process . platform === "win32" ? ".exe" : ""
88165 return vscode . Uri . joinPath ( context . extensionUri , "server" , `squawk${ ext } ` )
89166}
90167
91168async function startServer ( context : vscode . ExtensionContext ) {
169+ if ( client ?. state === State . Running ) {
170+ log . info ( "Server is already running" )
171+ return
172+ }
173+
174+ if ( client ?. state === State . Starting ) {
175+ log . info ( "Server is already starting" )
176+ return
177+ }
178+
92179 log . info ( "Starting Squawk Language Server..." )
93180
94181 const squawkPath = getSquawkPath ( context )
@@ -120,9 +207,38 @@ async function startServer(context: vscode.ExtensionContext) {
120207 clientOptions ,
121208 )
122209
123- log . info ( "Language client created, starting..." )
124- client . start ( )
125- log . info ( "Language client started" )
210+ context . subscriptions . push (
211+ client . onDidChangeState ( ( event ) => {
212+ onClientStateChange . fire ( event )
213+ } ) ,
214+ )
215+
216+ log . info ( "server starting..." )
217+ try {
218+ await client . start ( )
219+ log . info ( "server started successfully" )
220+ } catch ( error ) {
221+ log . error ( `Failed to start server:` , error )
222+ vscode . window . showErrorMessage ( `Failed to start server: ${ String ( error ) } ` )
223+ }
224+ }
225+
226+ async function stopServer ( ) {
227+ if ( ! client ) {
228+ log . info ( "No client to stop server" )
229+ return
230+ }
231+
232+ if ( client . state === State . Stopped ) {
233+ log . info ( "Server is already stopped" )
234+ return
235+ }
236+
237+ log . info ( "Stopping server..." )
238+
239+ await client . stop ( )
240+
241+ log . info ( "server stopped" )
126242}
127243
128244// Based on rust-analyzer's SyntaxTree support:
@@ -181,18 +297,24 @@ class SyntaxTreeProvider implements vscode.TextDocumentContentProvider {
181297 if ( ! document ) {
182298 return "Error: no active editor found"
183299 }
300+ if ( ! client ) {
301+ return "Error: no client found"
302+ }
184303 const text = document . getText ( )
185304 const uri = document . uri . toString ( )
186305 log . info ( `Requesting syntax tree for: ${ uri } ` )
187- const response = await client ? .sendRequest ( "squawk/syntaxTree" , {
306+ const response = await client . sendRequest < string > ( "squawk/syntaxTree" , {
188307 textDocument : { uri } ,
189308 text,
190309 } )
191310 log . info ( "Syntax tree received" )
192- return response as string
311+ return response
193312 } catch ( error ) {
194- log . error ( `Failed to get syntax tree: ${ error } ` )
195- return `Error: Failed to get syntax tree: ${ error } `
313+ log . error ( `Failed to get syntax tree:` , error )
314+ return `Error: Failed to get syntax tree: ${ String ( error ) } `
196315 }
197316 }
198317}
318+ function assertNever ( param : never ) : never {
319+ throw new Error ( `should never get here, but got ${ String ( param ) } ` )
320+ }
0 commit comments