From aceea260cb9b53bf8938fe8981563f6faea94dd1 Mon Sep 17 00:00:00 2001 From: Shirshajit Sen Gupta Date: Sat, 28 Jun 2025 23:43:14 +0800 Subject: [PATCH 1/5] Add connection error message --- src/extension.ts | 33 +++++++++++- src/webview/components/SourceAcademy.tsx | 65 ++++++++++++++++++++++-- 2 files changed, 91 insertions(+), 7 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 4873bd3..847ff93 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/webview/components/SourceAcademy.tsx b/src/webview/components/SourceAcademy.tsx index 6a8c22b..816848c 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,13 +52,32 @@ function messageListener(event: MessageEvent) { } const SourceAcademy: React.FC = () => { + const [error, setError] = useState(null); + const [retryCount, setRetryCount] = useState(0); + + const handleRetry = useCallback(() => { + setError(null); + setRetryCount((c) => c + 1); + }, [retryCount]); + + // Show overlay if frontend handshake never completes within 5 s. + // Re-runs whenever retryCount increments. + useEffect(() => { + const timer = window.setTimeout(() => { + setError( + "Can’t reach Source Academy. Please check your internet connection and retry.", + ); + }, 5000); + return () => window.clearTimeout(timer); + }, [retryCount]); + useEffect(() => { window.addEventListener("message", initialListener); return () => { window.removeEventListener("message", initialListener); window.removeEventListener("message", messageListener); }; - }, []); + }, [retryCount]); useEffect(() => { // TODO: Hacky way to update mcq panel, standard onClick handlers don't work @@ -155,8 +175,43 @@ const SourceAcademy: React.FC = () => { restore(); return () => document.removeEventListener("change", handleChoiceChange); - }, []); - - return <>; + }, [retryCount]); + + return ( + <> + {error && ( +
+

{error}

+ +
+ )} + + ); }; export default SourceAcademy; From 9805c105353aa6341d8c04eb330c8ba72496aba1 Mon Sep 17 00:00:00 2001 From: Shirshajit Sen Gupta Date: Sat, 28 Jun 2025 23:45:01 +0800 Subject: [PATCH 2/5] Fix: webview dispose error --- src/commands/showPanel.tsx | 20 ++++++++++++++------ src/webview/components/SourceAcademy.tsx | 1 - 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/commands/showPanel.tsx b/src/commands/showPanel.tsx index 77997bf..5a1531c 100644 --- a/src/commands/showPanel.tsx +++ b/src/commands/showPanel.tsx @@ -35,6 +35,11 @@ export async function showPanel( }, ); + // Reset stored panel when the user closes it + messageHandler.panel.onDidDispose(() => { + messageHandler.panel = null; + }); + messageHandler.panel.webview.onDidReceiveMessage( (message: MessageType) => messageHandler.handleMessage(context, message), undefined, @@ -68,13 +73,16 @@ export async function showPanel( 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/webview/components/SourceAcademy.tsx b/src/webview/components/SourceAcademy.tsx index 816848c..e3a5e01 100644 --- a/src/webview/components/SourceAcademy.tsx +++ b/src/webview/components/SourceAcademy.tsx @@ -60,7 +60,6 @@ const SourceAcademy: React.FC = () => { setRetryCount((c) => c + 1); }, [retryCount]); - // Show overlay if frontend handshake never completes within 5 s. // Re-runs whenever retryCount increments. useEffect(() => { const timer = window.setTimeout(() => { From 14ae9b3af2f2d3b0e86d3e9afe233a815f58f087 Mon Sep 17 00:00:00 2001 From: Shirshajit Sen Gupta Date: Thu, 3 Jul 2025 03:50:40 +0800 Subject: [PATCH 3/5] Remove broken timeout --- src/webview/components/SourceAcademy.tsx | 67 ++---------------------- 1 file changed, 3 insertions(+), 64 deletions(-) diff --git a/src/webview/components/SourceAcademy.tsx b/src/webview/components/SourceAcademy.tsx index e3a5e01..ad96e80 100644 --- a/src/webview/components/SourceAcademy.tsx +++ b/src/webview/components/SourceAcademy.tsx @@ -52,32 +52,6 @@ function messageListener(event: MessageEvent) { } const SourceAcademy: React.FC = () => { - const [error, setError] = useState(null); - const [retryCount, setRetryCount] = useState(0); - - const handleRetry = useCallback(() => { - setError(null); - setRetryCount((c) => c + 1); - }, [retryCount]); - - // Re-runs whenever retryCount increments. - useEffect(() => { - const timer = window.setTimeout(() => { - setError( - "Can’t reach Source Academy. Please check your internet connection and retry.", - ); - }, 5000); - return () => window.clearTimeout(timer); - }, [retryCount]); - - useEffect(() => { - window.addEventListener("message", initialListener); - return () => { - window.removeEventListener("message", initialListener); - window.removeEventListener("message", messageListener); - }; - }, [retryCount]); - useEffect(() => { // TODO: Hacky way to update mcq panel, standard onClick handlers don't work const highlightSelection = ( @@ -174,43 +148,8 @@ const SourceAcademy: React.FC = () => { restore(); return () => document.removeEventListener("change", handleChoiceChange); - }, [retryCount]); - - return ( - <> - {error && ( -
-

{error}

- -
- )} - - ); + }, []); + + return <>; }; export default SourceAcademy; From 1bd52c90ee2f09d1510200278b3cad539d417648 Mon Sep 17 00:00:00 2001 From: Shirshajit Sen Gupta Date: Thu, 3 Jul 2025 03:52:24 +0800 Subject: [PATCH 4/5] Fix: mcq panel view column --- src/utils/messageHandler.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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}`); } From cfcc8a8c543e06e98193b3bb310e602330492771 Mon Sep 17 00:00:00 2001 From: Shirshajit Sen Gupta Date: Thu, 3 Jul 2025 04:04:20 +0800 Subject: [PATCH 5/5] Fix: multi-panel --- src/commands/showPanel.tsx | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/src/commands/showPanel.tsx b/src/commands/showPanel.tsx index 9f320a5..2d7e26f 100644 --- a/src/commands/showPanel.tsx +++ b/src/commands/showPanel.tsx @@ -36,27 +36,17 @@ export async function showPanel( // Get a reference to the active editor (before the focus is switched to our newly created webview) // firstEditor = vscode.window.activeTextEditor!; - messageHandler.panel = vscode.window.createWebviewPanel( - "source-academy-panel", - "Source Academy", - vscode.ViewColumn.Beside, - { - enableScripts: true, // Enable scripts in the webview - retainContextWhenHidden: true, - }, - ); - - // Reset stored panel when the user closes it - messageHandler.panel.onDidDispose(() => { - messageHandler.panel = null; - }); - messageHandler.panel.webview.onDidReceiveMessage( (message: MessageType) => messageHandler.handleMessage(context, message), undefined, context.subscriptions, ); + messageHandler.panel.onDidDispose(() => { + console.log("Panel disposed, clearing message handler panel"); + messageHandler.panel = null; + }); + const iframeUrl = new URL(route ?? "/playground", config.frontendBaseUrl) .href;