11import * as vscode from "vscode" ;
2- import * as path from "path" ;
3- import * as net from "net" ;
4- import * as child_process from "child_process" ;
5- import { LanguageClient , LanguageClientOptions , ServerOptions , State } from "vscode-languageclient/node" ;
6- import { LiquidJavaLogger , createLogger } from "./logging" ;
7- import { applyItalicOverlay } from "./decorators" ;
8- import { connectToPort , findJavaExecutable , getAvailablePort , killProcess } from "./utils" ;
9- import { SERVER_JAR , DEBUG_MODE , DEBUG_PORT } from "./constants" ;
10- import { LiquidJavaWebviewProvider } from "./webview/provider" ;
11- import { LJDiagnostic } from "./types" ;
12- import { createMermaidDiagram } from "./webview/fsm" ;
13- import { StateMachine } from "./types/fsm" ;
14-
15- let serverProcess : child_process . ChildProcess ;
16- let client : LanguageClient ;
17- let socket : net . Socket ;
18- let outputChannel : vscode . OutputChannel ;
19- let logger : LiquidJavaLogger ;
20- let statusBarItem : vscode . StatusBarItem ;
21- let currentDiagnostics : LJDiagnostic [ ] ;
22- let webviewProvider : LiquidJavaWebviewProvider ;
23- let currentFile : string | undefined ;
24- let currentStateMachine : StateMachine | undefined ;
2+ import { registerLogger } from "./services/logger" ;
3+ import { applyItalicOverlay } from "./services/decorators" ;
4+ import { findJavaExecutable } from "./utils/utils" ;
5+ import { extension } from "./state" ;
6+ import { registerCommands } from "./services/commands" ;
7+ import { registerStatusBar , updateStatusBar } from "./services/status-bar" ;
8+ import { registerWebview } from "./services/webview" ;
9+ import { registerHover } from "./services/hover" ;
10+ import { registerEvents } from "./services/events" ;
11+ import { runLanguageServer } from "./lsp/server" ;
12+ import { runClient , stopClient } from "./lsp/client" ;
2513
2614/**
2715 * Activates the LiquidJava extension
2816 * @param context The extension context
2917 */
3018export async function activate ( context : vscode . ExtensionContext ) {
31- initLogging ( context ) ;
32- initStatusBar ( context ) ;
33- initCommandPalette ( context ) ;
34- initWebview ( context ) ;
35- initFileEvents ( context ) ;
36- initHover ( ) ;
37-
38- logger . client . info ( "Activating LiquidJava extension..." ) ;
19+ registerLogger ( context ) ;
20+ registerStatusBar ( context ) ;
21+ registerCommands ( context ) ;
22+ registerWebview ( context ) ;
23+ registerEvents ( context ) ;
24+ registerHover ( ) ;
25+
26+ extension . logger . client . info ( "Activating LiquidJava extension..." ) ;
3927
4028 await applyItalicOverlay ( ) ;
4129
4230 // find java executable path
4331 const javaExecutablePath = findJavaExecutable ( ) ;
4432 if ( ! javaExecutablePath ) {
4533 vscode . window . showErrorMessage ( "LiquidJava - Java Runtime Not Found in JAVA_HOME or PATH" ) ;
46- logger . client . error ( "Java Runtime not found in JAVA_HOME or PATH - Not activating extension" ) ;
34+ extension . logger . client . error ( "Java Runtime not found in JAVA_HOME or PATH - Not activating extension" ) ;
4735 updateStatusBar ( "stopped" ) ;
4836 return ;
4937 }
50- logger . client . info ( "Using Java at: " + javaExecutablePath ) ;
38+ extension . logger . client . info ( "Using Java at: " + javaExecutablePath ) ;
5139
5240 // start server
53- logger . client . info ( "Starting LiquidJava language server..." ) ;
41+ extension . logger . client . info ( "Starting LiquidJava language server..." ) ;
5442 const port = await runLanguageServer ( context , javaExecutablePath ) ;
5543
5644 // start client
57- logger . client . info ( "Starting LiquidJava client..." ) ;
45+ extension . logger . client . info ( "Starting LiquidJava client..." ) ;
5846 await runClient ( context , port ) ;
5947}
6048
6149/**
6250 * Deactivates the LiquidJava extension
6351 */
6452export async function deactivate ( ) {
65- logger ?. client . info ( "Deactivating LiquidJava extension..." ) ;
66- await stopExtension ( "Extension was deactivated" ) ;
67- }
68-
69- /**
70- * Initializes logging for the extension with an output channel
71- * @param context The extension context
72- */
73- function initLogging ( context : vscode . ExtensionContext ) {
74- outputChannel = vscode . window . createOutputChannel ( "LiquidJava" ) ;
75- logger = createLogger ( outputChannel ) ;
76- context . subscriptions . push ( outputChannel ) ;
77- context . subscriptions . push ( logger ) ;
78- context . subscriptions . push ( vscode . commands . registerCommand ( "liquidjava.showLogs" , ( ) => outputChannel . show ( true ) ) ) ;
79- }
80-
81- /**
82- * Initializes the status bar for the extension
83- * @param context The extension context
84- */
85- function initStatusBar ( context : vscode . ExtensionContext ) {
86- statusBarItem = vscode . window . createStatusBarItem ( vscode . StatusBarAlignment . Left ) ;
87- statusBarItem . tooltip = "LiquidJava Commands" ;
88- statusBarItem . command = "liquidjava.showCommands" ;
89- updateStatusBar ( "loading" )
90- statusBarItem . show ( ) ;
91- context . subscriptions . push ( statusBarItem ) ;
92- }
93-
94- /**
95- * Initializes the command palette for the extension
96- * @param context The extension context
97- */
98- function initCommandPalette ( context : vscode . ExtensionContext ) {
99- context . subscriptions . push (
100- vscode . commands . registerCommand ( "liquidjava.showCommands" , async ( ) => {
101- const commands = [
102- { label : "$(output) Show Logs" , command : "liquidjava.showLogs" } ,
103- { label : "$(window) Show View" , command : "liquidjava.showView" } ,
104- // TODO: add more commands here, e.g., start, stop, restart, verify, etc.
105- ] ;
106- const placeHolder = "Select a LiquidJava Command" ;
107- const selected = await vscode . window . showQuickPick ( commands , { placeHolder } ) ;
108- if ( selected ) vscode . commands . executeCommand ( selected . command ) ;
109- } )
110- ) ;
111- }
112-
113- /**
114- * Initializes the webview panel for the extension
115- * @param context The extension context
116- */
117- function initWebview ( context : vscode . ExtensionContext ) {
118- webviewProvider = new LiquidJavaWebviewProvider ( context . extensionUri ) ;
119-
120- // webview provider
121- context . subscriptions . push (
122- vscode . window . registerWebviewViewProvider ( LiquidJavaWebviewProvider . viewType , webviewProvider )
123- ) ;
124- // show view command
125- context . subscriptions . push (
126- vscode . commands . registerCommand ( "liquidjava.showView" , ( ) => {
127- vscode . commands . executeCommand ( "liquidJavaView.focus" ) ;
128- } )
129- ) ;
130- // listen for messages from the webview
131- context . subscriptions . push (
132- webviewProvider . onDidReceiveMessage ( message => {
133- console . log ( "received message" , message ) ;
134- if ( message . type === "ready" ) {
135- webviewProvider . sendMessage ( { type : "file" , file : currentFile } ) ;
136- webviewProvider . sendMessage ( { type : "diagnostics" , diagnostics : currentDiagnostics } ) ;
137- if ( currentStateMachine ) webviewProvider . sendMessage ( { type : "fsm" , sm : currentStateMachine } ) ;
138- }
139- } )
140- ) ;
141- }
142-
143- /**
144- * Initializes hover provider for LiquidJava diagnostics
145- */
146- function initHover ( ) {
147- vscode . languages . registerHoverProvider ( 'java' , {
148- provideHover ( document , position ) {
149- // if webview is visible, do not show hover
150- if ( webviewProvider ?. isVisible ( ) ) return null ;
151-
152- // get lj diagnostic at the current position
153- const diagnostics = vscode . languages . getDiagnostics ( document . uri ) ;
154- const diagnostic = diagnostics . find ( d => d . range . contains ( position ) && d . source === 'liquidjava' ) ;
155- if ( ! diagnostic ) return null ;
156-
157- // create hover content with link to open webview
158- const hoverContent = new vscode . MarkdownString ( ) ;
159- hoverContent . isTrusted = true ;
160- hoverContent . appendMarkdown ( `\n\n[Open LiquidJava view](command:liquidjava.showView) for more details.` ) ;
161- return new vscode . Hover ( hoverContent ) ;
162- }
163- } ) ;
164- }
165-
166-
167- /**
168- * Initializes file system event listeners
169- * @param context The extension context
170- */
171- function initFileEvents ( context : vscode . ExtensionContext ) {
172- // listen for active text editor changes
173- context . subscriptions . push (
174- vscode . window . onDidChangeActiveTextEditor ( editor => {
175- if ( ! editor || editor . document . languageId !== "java" ) return ;
176- handleActiveFileChange ( editor ) ;
177-
178- } ) ,
179- vscode . workspace . onDidSaveTextDocument ( document => {
180- if ( document . uri . scheme !== 'file' || document . languageId !== "java" ) return ;
181- requestStateMachine ( document )
182- } )
183- ) ;
184- }
185-
186- /**
187- * Requests the state machine for the given document from the language server
188- * @param document The text document
189- */
190- async function requestStateMachine ( document : vscode . TextDocument ) {
191- const sm : StateMachine = await client ?. sendRequest ( "liquidjava/fsm" , { uri : document . uri . toString ( ) } ) ;
192- webviewProvider ?. sendMessage ( { type : "fsm" , sm } ) ;
193- currentStateMachine = sm ;
53+ extension . logger ?. client . info ( "Deactivating LiquidJava extension..." ) ;
54+ await stopClient ( "Extension was deactivated" ) ;
19455}
195-
196- /**
197- * Updates the status bar with the current state
198- * @param state The current state ("loading", "stopped", "passed", "failed")
199- */
200- function updateStatusBar ( state : "loading" | "stopped" | "passed" | "failed" ) {
201- const icons = {
202- loading : "$(sync~spin)" ,
203- stopped : "$(circle-slash)" ,
204- passed : "$(check)" ,
205- failed : "$(x)" ,
206- } ;
207- const color = state === "stopped" ? "errorForeground" : "statusBar.foreground" ;
208- statusBarItem . color = new vscode . ThemeColor ( color ) ;
209- statusBarItem . text = icons [ state ] + " LiquidJava" ;
210- }
211-
212- /**
213- * Runs the LiquidJava language server
214- * @param context The extension context
215- * @param javaExecutablePath The path to the Java executable
216- * @returns A promise to the port number the server is running on
217- */
218- async function runLanguageServer ( context : vscode . ExtensionContext , javaExecutablePath : string ) : Promise < number > {
219- const port = DEBUG_MODE ? DEBUG_PORT : await getAvailablePort ( ) ;
220- if ( DEBUG_MODE ) {
221- logger . client . info ( "DEBUG MODE: Using fixed port " + port ) ;
222- return port ;
223- }
224- logger . client . info ( "Running language server on port " + port ) ;
225-
226- const jarPath = path . resolve ( context . extensionPath , "dist" , "server" , SERVER_JAR ) ;
227- const args = [ "-jar" , jarPath , port . toString ( ) ] ;
228- const options = {
229- cwd : vscode . workspace . workspaceFolders [ 0 ] . uri . fsPath , // root path
230- } ;
231- logger . client . info ( "Creating language server process..." ) ;
232- serverProcess = child_process . spawn ( javaExecutablePath , args , options ) ;
233-
234- // listen to process events
235- serverProcess . stdout . on ( "data" , ( data ) => {
236- const message = data . toString ( ) . trim ( ) ;
237- logger . server . info ( message ) ;
238- } ) ;
239- serverProcess . stderr . on ( "data" , ( data ) => {
240- logger . server . error ( data . toString ( ) . trim ( ) )
241- } ) ;
242- serverProcess . on ( "error" , ( err ) => {
243- logger . server . error ( `Failed to start: ${ err } ` )
244- } ) ;
245- serverProcess . on ( "close" , ( code ) => {
246- logger . server . info ( `Process exited with code ${ code } ` ) ;
247- client ?. stop ( ) ;
248- } ) ;
249- return port ;
250- }
251-
252- /**
253- * Starts the client and connects it to the language server
254- * @param context The extension context
255- * @param port The port number the server is running on
256- */
257- async function runClient ( context : vscode . ExtensionContext , port : number ) {
258- const serverOptions : ServerOptions = ( ) => {
259- return new Promise ( async ( resolve , reject ) => {
260- try {
261- socket = await connectToPort ( port ) ;
262- resolve ( {
263- writer : socket ,
264- reader : socket ,
265- } ) ;
266- } catch ( error ) {
267- await stopExtension ( "Failed to connect to server" ) ;
268- reject ( error ) ;
269- }
270- } ) ;
271- } ;
272- const clientOptions : LanguageClientOptions = {
273- documentSelector : [ { language : "java" } ] ,
274- } ;
275- client = new LanguageClient ( "liquidJavaServer" , "LiquidJava Server" , serverOptions , clientOptions ) ;
276- client . onDidChangeState ( ( e ) => {
277- if ( e . newState === State . Stopped ) {
278- stopExtension ( "Extension stopped" ) ;
279- }
280- } ) ;
281-
282- context . subscriptions . push ( client ) ; // client teardown
283- context . subscriptions . push ( {
284- dispose : ( ) => stopExtension ( "Extension was disposed" ) , // server teardown
285- } ) ;
286-
287- try {
288- await client . start ( ) ;
289- logger . client . info ( "Extension is ready" ) ;
290-
291- client . onNotification ( "liquidjava/diagnostics" , ( diagnostics : LJDiagnostic [ ] ) => {
292- handleLJDiagnostics ( diagnostics ) ;
293- } ) ;
294-
295- const editor = vscode . window . activeTextEditor ;
296- if ( editor && editor . document . languageId === "java" ) {
297- handleActiveFileChange ( editor ) ;
298- }
299- } catch ( e ) {
300- vscode . window . showErrorMessage ( "LiquidJava failed to initialize: " + e . toString ( ) ) ;
301- logger . client . error ( "Failed to initialize: " + e . toString ( ) ) ;
302- await stopExtension ( "Failed to initialize" ) ;
303- }
304-
305- // update status bar on file save
306- context . subscriptions . push (
307- vscode . workspace . onDidSaveTextDocument ( ( ) => {
308- if ( client ) {
309- updateStatusBar ( "loading" ) ;
310- }
311- } )
312- ) ;
313- }
314-
315- /**
316- * Stops the LiquidJava extension
317- * @param reason The reason for stopping the extension
318- */
319- async function stopExtension ( reason : string ) {
320- if ( ! client && ! serverProcess && ! socket ) {
321- logger . client . info ( "Extension already stopped" ) ;
322- return ;
323- }
324- logger . client . info ( "Stopping LiquidJava extension: " + reason ) ;
325- updateStatusBar ( "stopped" ) ;
326-
327- // stop client
328- try {
329- await client ?. stop ( ) ;
330- } catch ( e ) {
331- logger . client . error ( "Error stopping client: " + e ) ;
332- } finally {
333- client = undefined ;
334- }
335-
336- // close socket
337- try {
338- socket ?. destroy ( ) ;
339- } catch ( e ) {
340- logger . client . error ( "Error closing socket: " + e ) ;
341- } finally {
342- socket = undefined ;
343- }
344-
345- // kill server process
346- await killProcess ( serverProcess ) ;
347- serverProcess = undefined ;
348- }
349-
350- /**
351- * Handles LiquidJava diagnostics received from the language server
352- * @param diagnostics The array of diagnostics received
353- */
354- function handleLJDiagnostics ( diagnostics : LJDiagnostic [ ] ) {
355- const containsError = diagnostics . some ( d => d . category === "error" ) ;
356- if ( containsError ) {
357- updateStatusBar ( "failed" ) ;
358- } else {
359- updateStatusBar ( "passed" ) ;
360- }
361- webviewProvider ?. sendMessage ( { type : "diagnostics" , diagnostics } ) ;
362- currentDiagnostics = diagnostics ;
363- }
364-
365- /**
366- * Handles active file change events
367- * @param editor The active text editor
368- */
369- function handleActiveFileChange ( editor : vscode . TextEditor ) {
370- currentFile = editor . document . uri . fsPath ;
371- webviewProvider ?. sendMessage ( { type : "file" , file : currentFile } ) ;
372- requestStateMachine ( editor . document ) ;
373- }
0 commit comments