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 = (