Skip to content

Commit 968d05a

Browse files
authored
Add New Commands (#52)
1 parent 7c65a0b commit 968d05a

File tree

12 files changed

+201
-66
lines changed

12 files changed

+201
-66
lines changed

client/package.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,26 @@
100100
"command": "liquidjava.showView",
101101
"title": "Show View",
102102
"category": "LiquidJava"
103+
},
104+
{
105+
"command": "liquidjava.start",
106+
"title": "Start",
107+
"category": "LiquidJava"
108+
},
109+
{
110+
"command": "liquidjava.stop",
111+
"title": "Stop",
112+
"category": "LiquidJava"
113+
},
114+
{
115+
"command": "liquidjava.restart",
116+
"title": "Restart",
117+
"category": "LiquidJava"
118+
},
119+
{
120+
"command": "liquidjava.verify",
121+
"title": "Verify",
122+
"category": "LiquidJava"
103123
}
104124
],
105125
"menus": {},
72 Bytes
Binary file not shown.

client/src/extension.ts

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { registerStatusBar, updateStatusBar } from "./services/status-bar";
88
import { registerWebview } from "./services/webview";
99
import { registerHover } from "./services/hover";
1010
import { registerEvents } from "./services/events";
11-
import { runLanguageServer } from "./lsp/server";
11+
import { runLanguageServer, stopLanguageServer } from "./lsp/server";
1212
import { runClient, stopClient } from "./lsp/client";
1313

1414
/**
@@ -26,12 +26,35 @@ export async function activate(context: vscode.ExtensionContext) {
2626
extension.logger.client.info("Activating LiquidJava extension...");
2727

2828
await applyItalicOverlay();
29+
await startExtension(context);
30+
}
31+
32+
/**
33+
* Deactivates the LiquidJava extension
34+
*/
35+
export async function deactivate() {
36+
extension.logger?.client.info("Deactivating LiquidJava extension...");
37+
await stopClient("Extension was deactivated");
38+
await stopLanguageServer();
39+
}
40+
41+
/**
42+
* Starts the LiquidJava language server and client
43+
* @param context The extension context
44+
*/
45+
export async function startExtension(context: vscode.ExtensionContext) {
46+
// check if already running
47+
if (extension.client || extension.serverProcess) {
48+
extension.logger.client.info("LiquidJava is already running");
49+
return;
50+
}
51+
extension.logger.client.info("Starting LiquidJava...");
2952

3053
// find java executable path
3154
const javaExecutablePath = findJavaExecutable();
3255
if (!javaExecutablePath) {
3356
vscode.window.showErrorMessage("LiquidJava - Java Runtime Not Found in JAVA_HOME or PATH");
34-
extension.logger.client.error("Java Runtime not found in JAVA_HOME or PATH - Not activating extension");
57+
extension.logger.client.error("Java Runtime not found in JAVA_HOME or PATH");
3558
updateStatusBar("stopped");
3659
return;
3760
}
@@ -47,9 +70,33 @@ export async function activate(context: vscode.ExtensionContext) {
4770
}
4871

4972
/**
50-
* Deactivates the LiquidJava extension
73+
* Stops the LiquidJava language server and client
5174
*/
52-
export async function deactivate() {
53-
extension.logger?.client.info("Deactivating LiquidJava extension...");
54-
await stopClient("Extension was deactivated");
75+
export async function stopExtension() {
76+
if (!extension.client && !extension.serverProcess) {
77+
extension.logger?.client.info("LiquidJava is not running");
78+
return;
79+
}
80+
extension.logger?.client.info("Stopping LiquidJava...");
81+
await stopClient("Extension stop command");
82+
await stopLanguageServer();
5583
}
84+
85+
/**
86+
* Restarts the LiquidJava language server and client
87+
* @param context The extension context
88+
*/
89+
export async function restartExtension(context: vscode.ExtensionContext) {
90+
extension.logger?.client.info("Restarting LiquidJava...");
91+
92+
// stop if running
93+
if (extension.client || extension.serverProcess) {
94+
await stopExtension();
95+
// ensure clean shutdown
96+
await new Promise(resolve => setTimeout(resolve, 500));
97+
}
98+
99+
// start again
100+
await startExtension(context);
101+
}
102+

client/src/lsp/client.ts

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import * as vscode from 'vscode';
2-
import { LanguageClient, LanguageClientOptions, ServerOptions, State } from 'vscode-languageclient/node';
2+
import { LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient/node';
33
import { connectToPort } from '../utils/utils';
4-
import { killProcess } from '../utils/utils';
54
import { extension } from '../state';
65
import { updateStatusBar } from '../services/status-bar';
7-
import { handleLJDiagnostics } from '../services/webview';
6+
import { handleLJDiagnostics } from '../services/diagnostics';
87
import { onActiveFileChange } from '../services/events';
98
import type { LJDiagnostic } from "../types/diagnostics";
109

@@ -32,16 +31,8 @@ export async function runClient(context: vscode.ExtensionContext, port: number)
3231
documentSelector: [{ language: "java" }],
3332
};
3433
extension.client = new LanguageClient("liquidJavaServer", "LiquidJava Server", serverOptions, clientOptions);
35-
extension.client.onDidChangeState((e) => {
36-
if (e.newState === State.Stopped) {
37-
stopClient("Client stopped");
38-
}
39-
});
4034

41-
context.subscriptions.push(extension.client); // client teardown
42-
context.subscriptions.push({
43-
dispose: () => stopClient("Client was disposed"), // server teardown
44-
});
35+
context.subscriptions.push(extension.client); // disposed on deactivation
4536

4637
try {
4738
await extension.client.start();
@@ -100,8 +91,4 @@ export async function stopClient(reason: string) {
10091
} finally {
10192
extension.socket = undefined;
10293
}
103-
104-
// kill server process
105-
await killProcess(extension.serverProcess);
106-
extension.serverProcess = undefined;
10794
}

client/src/lsp/server.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as vscode from 'vscode';
22
import * as child_process from 'child_process';
33
import * as path from 'path';
4-
import { getAvailablePort } from '../utils/utils';
4+
import { getAvailablePort, killProcess } from '../utils/utils';
55
import { extension } from '../state';
66
import { DEBUG_MODE, DEBUG_PORT, SERVER_JAR } from '../utils/constants';
77

@@ -40,7 +40,16 @@ export async function runLanguageServer(context: vscode.ExtensionContext, javaEx
4040
});
4141
extension.serverProcess.on("close", (code) => {
4242
extension.logger.server.info(`Process exited with code ${code}`);
43-
extension.client?.stop();
43+
extension.serverProcess = undefined;
4444
});
4545
return port;
46+
}
47+
48+
/**
49+
* Stops the LiquidJava language server
50+
* @returns A promise that resolves when the server is stopped
51+
*/
52+
export async function stopLanguageServer() {
53+
await killProcess(extension.serverProcess);
54+
extension.serverProcess = undefined;
4655
}

client/src/services/commands.ts

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,54 @@
11
import * as vscode from "vscode";
2+
import { startExtension, stopExtension, restartExtension } from "../extension";
3+
import { verify } from "./diagnostics";
4+
5+
const commandIcons: Record<string, string> = {
6+
"liquidjava.showLogs": "$(output)",
7+
"liquidjava.showView": "$(window)",
8+
"liquidjava.start": "$(play)",
9+
"liquidjava.stop": "$(debug-stop)",
10+
"liquidjava.restart": "$(debug-restart)",
11+
"liquidjava.verify": "$(check)",
12+
}
13+
14+
const commandHandlers: Record<string, (context: vscode.ExtensionContext) => Promise<void>> = {
15+
"liquidjava.start": async (context) => await startExtension(context),
16+
"liquidjava.stop": async () => await stopExtension(),
17+
"liquidjava.restart": async (context) => await restartExtension(context),
18+
"liquidjava.verify": async () => await verify(),
19+
}
220

321
/**
4-
* Initializes the command palette for the extension
22+
* Registers all commands for the LiquidJava extension
523
* @param context The extension context
624
*/
725
export function registerCommands(context: vscode.ExtensionContext) {
26+
const packageJson = context.extension.packageJSON;
27+
const commands = (packageJson.contributes?.commands || []) as vscode.Command[];
28+
29+
// register commands
30+
commands.forEach(cmd => {
31+
const handler = commandHandlers[cmd.command];
32+
if (handler) {
33+
context.subscriptions.push(
34+
vscode.commands.registerCommand(cmd.command, () => handler(context))
35+
);
36+
}
37+
});
38+
39+
// register command to show all commands
840
context.subscriptions.push(
941
vscode.commands.registerCommand("liquidjava.showCommands", async () => {
10-
const commands = [
11-
{ label: "$(output) Show Logs", command: "liquidjava.showLogs" },
12-
{ label: "$(window) Show View", command: "liquidjava.showView" },
13-
// TODO: add more commands here, e.g., start, stop, restart, verify, etc.
14-
];
42+
const quickPickItems = commands
43+
.filter(cmd => cmd.command !== "liquidjava.showCommands")
44+
.map(cmd => ({
45+
label: `${commandIcons[cmd.command] || "$(symbol-misc)"} ${cmd.title}`,
46+
command: cmd.command,
47+
}));
48+
1549
const placeHolder = "Select a LiquidJava Command";
16-
const selected = await vscode.window.showQuickPick(commands, { placeHolder });
50+
const selected = await vscode.window.showQuickPick(quickPickItems, { placeHolder });
1751
if (selected) vscode.commands.executeCommand(selected.command);
1852
})
19-
);
53+
);
2054
}

client/src/services/diagnostics.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import * as vscode from "vscode";
2+
import { extension } from "../state";
3+
import { LJDiagnostic } from "../types/diagnostics";
4+
import { StatusBarState, updateStatusBar } from "./status-bar";
5+
6+
/**
7+
* Handles LiquidJava diagnostics received from the language server
8+
* @param diagnostics The array of diagnostics received
9+
*/
10+
export function handleLJDiagnostics(diagnostics: LJDiagnostic[]) {
11+
const containsError = diagnostics.some(d => d.category === "error");
12+
const statusBarState: StatusBarState = containsError ? "failed" : "passed";
13+
updateStatusBar(statusBarState);
14+
extension.webview?.sendMessage({ type: "diagnostics", diagnostics });
15+
extension.diagnostics = diagnostics;
16+
}
17+
18+
/**
19+
* Triggers the LiquidJava verification manually
20+
*/
21+
export async function verify() {
22+
const editor = vscode.window.activeTextEditor;
23+
if (!editor || editor.document.languageId !== "java") {
24+
vscode.window.showWarningMessage("LiquidJava: No Java file is currently open");
25+
return;
26+
}
27+
28+
if (!extension.client) {
29+
vscode.window.showWarningMessage("LiquidJava: Extension is not running. Use 'LiquidJava: Start' first.");
30+
return;
31+
}
32+
33+
const uri = editor.document.uri.toString();
34+
extension.logger?.client.info("Verify command — checking diagnostics");
35+
updateStatusBar("loading");
36+
37+
extension.client.sendNotification("liquidjava/verify", { uri });
38+
}

client/src/services/events.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as vscode from 'vscode';
22
import { extension } from '../state';
3-
import { updateStateMachine } from './webview';
3+
import { updateStateMachine } from './state-machine';
44

55
/**
66
* Initializes file system event listeners
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import * as vscode from "vscode";
2+
import { extension } from "../state";
3+
import { StateMachine } from "../types/fsm";
4+
5+
/**
6+
* Requests the state machine for the given document from the language server
7+
* @param document The text document
8+
*/
9+
export async function updateStateMachine(document: vscode.TextDocument) {
10+
const sm: StateMachine = await extension.client?.sendRequest("liquidjava/fsm", { uri: document.uri.toString() });
11+
extension.webview?.sendMessage({ type: "fsm", sm });
12+
extension.stateMachine = sm;
13+
}

client/src/services/status-bar.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,26 @@ import { extension } from "../state";
33

44
export type StatusBarState = "loading" | "stopped" | "passed" | "failed";
55

6+
const icons = {
7+
loading: "$(sync~spin)",
8+
stopped: "$(circle-slash)",
9+
passed: "$(check)",
10+
failed: "$(x)",
11+
};
12+
13+
const statusText = {
14+
loading: "Loading",
15+
stopped: "Stopped",
16+
passed: "Verification passed",
17+
failed: "Verification failed",
18+
};
19+
620
/**
721
* Initializes the status bar for the extension
822
* @param context The extension context
923
*/
1024
export function registerStatusBar(context: vscode.ExtensionContext) {
1125
extension.statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
12-
extension.statusBar.tooltip = "LiquidJava Commands";
1326
extension.statusBar.command = "liquidjava.showCommands";
1427
updateStatusBar("loading");
1528
extension.statusBar.show();
@@ -21,13 +34,8 @@ export function registerStatusBar(context: vscode.ExtensionContext) {
2134
* @param state The current state ("loading", "stopped", "passed", "failed")
2235
*/
2336
export function updateStatusBar(state: StatusBarState) {
24-
const icons = {
25-
loading: "$(sync~spin)",
26-
stopped: "$(circle-slash)",
27-
passed: "$(check)",
28-
failed: "$(x)",
29-
};
3037
const color = state === "stopped" ? "errorForeground" : "statusBar.foreground";
3138
extension.statusBar.color = new vscode.ThemeColor(color);
3239
extension.statusBar.text = icons[state] + " LiquidJava";
40+
extension.statusBar.tooltip = statusText[state];
3341
}

0 commit comments

Comments
 (0)