diff --git a/src/commands/showPanel.tsx b/src/commands/showPanel.tsx
index 02e9311..2d7e26f 100644
--- a/src/commands/showPanel.tsx
+++ b/src/commands/showPanel.tsx
@@ -33,48 +33,58 @@ export async function showPanel(
retainContextWhenHidden: true,
},
);
+ // Get a reference to the active editor (before the focus is switched to our newly created webview)
+ // firstEditor = vscode.window.activeTextEditor!;
messageHandler.panel.webview.onDidReceiveMessage(
(message: MessageType) => messageHandler.handleMessage(context, message),
undefined,
context.subscriptions,
);
- }
- const iframeUrl = new URL(route ?? "/playground", config.frontendBaseUrl)
- .href;
+ messageHandler.panel.onDidDispose(() => {
+ console.log("Panel disposed, clearing message handler panel");
+ messageHandler.panel = null;
+ });
+
+ const iframeUrl = new URL(route ?? "/playground", config.frontendBaseUrl)
+ .href;
- setWebviewContent(
- messageHandler.panel,
- context,
- // NOTE: This is not React code, but our FakeReact!
-
-
-
,
- );
+ setWebviewContent(
+ messageHandler.panel,
+ context,
+ // NOTE: This is not React code, but our FakeReact!
+
+
+
,
+ );
- messageHandler.panel.iconPath = SOURCE_ACADEMY_ICON_URI;
+ messageHandler.panel.iconPath = SOURCE_ACADEMY_ICON_URI;
+ }
}
-export async function sendToFrontendWrapped(message: MessageType) {
- sendToFrontend(messageHandler.panel, message);
- // TODO: This returning of status code shouldn't be necessary after refactor
+export function sendToFrontendWrapped(message: MessageType): boolean {
if (!messageHandler.panel) {
- console.error("ERROR: panel is not set");
return false;
}
- sendToFrontend(messageHandler.panel, message);
- return true;
+ try {
+ sendToFrontend(messageHandler.panel, message);
+ return true;
+ } catch (err) {
+ console.error("Failed to send message to webview", err);
+ messageHandler.panel = null;
+ return false;
+ }
}
diff --git a/src/extension.ts b/src/extension.ts
index 1b0f2a9..e1b3ea2 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -14,22 +14,51 @@ export let client: LanguageClient;
export let SOURCE_ACADEMY_ICON_URI: vscode.Uri;
+// Shared output channel for logging diagnostic messages
+let outputChannel: vscode.OutputChannel;
+
// This method is called when your extension is activated
// Your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) {
+ // Initialise output channel early so all parts can use it
+ outputChannel = vscode.window.createOutputChannel("Source Academy");
+ context.subscriptions.push(outputChannel);
+
+ // Validate user-provided settings up-front
+ try {
+ new URL(config.frontendBaseUrl);
+ } catch {
+ vscode.window.showErrorMessage(
+ "Source Academy: Invalid Frontend URL in settings (source-academy.frontendBaseUrl).",
+ );
+ }
setupTreeView(context);
registerAllCommands(context);
context.subscriptions.push(setupStatusBar(context));
- client = activateLspClient(context);
+ try {
+ client = activateLspClient(context);
+ } catch (e: any) {
+ vscode.window.showErrorMessage(
+ "Source Academy: Failed to start language server – " + (e?.message ?? e),
+ );
+ outputChannel.appendLine(String(e?.stack ?? e));
+ throw e; // rethrow so VS Code knows activation failed
+ }
// const info = {
// "file:///home/heyzec/.sourceacademy/playground_1.js": { chapter: 4 },
// };
const info = context.globalState.get("info") ?? {};
- client.sendRequest("source/publishInfo", info);
+ try {
+ client.sendRequest("source/publishInfo", info);
+ } catch (e: any) {
+ outputChannel.appendLine(
+ "Error sending initial info to language server: " + String(e),
+ );
+ }
SOURCE_ACADEMY_ICON_URI = vscode.Uri.joinPath(
context.extensionUri,
diff --git a/src/utils/messageHandler.tsx b/src/utils/messageHandler.tsx
index 3dd25a9..1f45b5f 100644
--- a/src/utils/messageHandler.tsx
+++ b/src/utils/messageHandler.tsx
@@ -49,7 +49,7 @@ export class MessageHandler {
this.mcqPanel = vscode.window.createWebviewPanel(
"mcq-question-panel",
`MCQ`,
- vscode.ViewColumn.One,
+ vscode.ViewColumn.Two,
{ enableScripts: true, retainContextWhenHidden: true },
);
@@ -88,7 +88,7 @@ export class MessageHandler {
{ReactDOMServer.renderToString(activePanel)}
,
);
- this.mcqPanel.reveal(vscode.ViewColumn.One);
+ this.mcqPanel.reveal(vscode.ViewColumn.Two);
break;
}
@@ -173,9 +173,9 @@ export class MessageHandler {
this.panel?.reveal(vscode.ViewColumn.Two);
}
break;
- case MessageTypeNames.LoginWithBrowser:
- const { route } = message;
- vscode.env.openExternal(vscode.Uri.parse(route));
+ case MessageTypeNames.LoginWithBrowser:
+ const { route } = message;
+ vscode.env.openExternal(vscode.Uri.parse(route));
}
console.log(`${Date.now()} Finish handleMessage: ${message.type}`);
}
diff --git a/src/webview/components/SourceAcademy.tsx b/src/webview/components/SourceAcademy.tsx
index 6a8c22b..ad96e80 100644
--- a/src/webview/components/SourceAcademy.tsx
+++ b/src/webview/components/SourceAcademy.tsx
@@ -3,7 +3,7 @@
* It simply relays messages between the VSC Extension context and the Frontend iframe context.
*/
//
-import React, { useEffect } from "react";
+import React, { useEffect, useState, useCallback } from "react";
import Messages, { MessageType, MessageTypeNames } from "../../utils/messages";
import { FRONTEND_ELEMENT_ID } from "../../constants";
@@ -32,6 +32,7 @@ function initialListener(event: MessageEvent) {
event.origin === message.frontendOrigin
) {
frontendBaseUrl = event.origin;
+
relayToExtension(message);
window.addEventListener("message", messageListener);
return;
@@ -51,14 +52,6 @@ function messageListener(event: MessageEvent) {
}
const SourceAcademy: React.FC = () => {
- useEffect(() => {
- window.addEventListener("message", initialListener);
- return () => {
- window.removeEventListener("message", initialListener);
- window.removeEventListener("message", messageListener);
- };
- }, []);
-
useEffect(() => {
// TODO: Hacky way to update mcq panel, standard onClick handlers don't work
const highlightSelection = (