import * as vscode from 'vscode'; import { getNonce } from './getNonce'; import urlJoin from 'url-join'; import { extensionSettings } from './extension'; import { runJava } from './run/java'; import { join } from 'path'; import { TeamData } from './SidebarProvider'; import { submitProblem } from './submit'; export type ProblemData = { id: number; name: string; pascalName: string; sampleInput: string; sampleOutput: string; }[]; export type MessageType = | { msg: 'onRequestProblemData' } | { msg: 'onRun'; data: { problemId: number; input: string } } | { msg: 'onKill' } | { msg: 'onSubmit'; data: { problemId: number } }; export type WebviewMessageType = | { msg: 'onProblemData'; data: ProblemData } | { msg: 'onRunning' } | { msg: 'onRunningDone' } | { msg: 'onRunningOutput'; data: string }; type RunningProgram = { problemId: number; outputBuffer: string[]; kill: () => void; }; /** * Singleton class for problem panel */ export class BWPanel { public static currentPanel: BWPanel | undefined; private runningProgram: RunningProgram | undefined; private problemData: ProblemData | undefined; private constructor( private readonly context: vscode.ExtensionContext, private readonly panel: vscode.WebviewPanel, private readonly extensionUri: vscode.Uri, private readonly webUrl: string ) { this.update(); panel.onDidDispose(() => this.dispose()); } public static show(context: vscode.ExtensionContext, webUrl: string) { const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined; // Show panel if exists if (BWPanel.currentPanel !== undefined) { BWPanel.currentPanel.panel.reveal(column); return; } // Otherwise create new panel const panel = vscode.window.createWebviewPanel( 'bwpanel', 'BWContest', column || vscode.ViewColumn.One, { enableScripts: true, retainContextWhenHidden: true, localResourceRoots: [ vscode.Uri.joinPath(context.extensionUri, 'media'), vscode.Uri.joinPath(context.extensionUri, 'out/compiled') ] } ); BWPanel.currentPanel = new BWPanel(context, panel, context.extensionUri, webUrl); } public static kill() { BWPanel.currentPanel?.dispose(); BWPanel.currentPanel = undefined; } public dispose() { BWPanel.currentPanel = undefined; } private webviewPostMessage(m: WebviewMessageType) { this.panel.webview.postMessage(m); } private async handleSubmit(problemId: number) { if (this.problemData === undefined) { console.error('Problem data undefined'); return; } const problem = this.problemData.find((p) => p.id === problemId); if (problem === undefined) { console.error('Invalid problem Id'); return; } const sessionToken = this.context.globalState.get('token'); if (sessionToken === undefined) { console.error('No session token'); return; } const teamData = this.context.globalState.get('teamData'); if (teamData === undefined) { console.error('No team data'); return; } await vscode.workspace.saveAll(); const ans = await vscode.window.showInformationMessage( `Are you sure you want to submit ${problem.name}?`, 'Yes', 'No' ); if (ans !== 'Yes') { return; } submitProblem(sessionToken, teamData.contestId, teamData.teamId, problemId).then((result) => { if (result.success === true) { vscode.window.showInformationMessage('Submitted!'); } else { vscode.window.showErrorMessage(`Error: ${result.message}`); } }); } private async handleRun(problemId: number, input: string) { const teamData: TeamData | undefined = this.context.globalState.get('teamData'); if (teamData === undefined) { return; } if (this.problemData === undefined) { return; } if (this.runningProgram !== undefined) { vscode.window.showErrorMessage('Already Running'); return; } const problem = this.problemData.find((p) => (p.id === problemId)); if (problem === undefined) { return; } await vscode.workspace.saveAll(); const repoDir = extensionSettings().repoClonePath; const outputBuffer: string[] = []; this.webviewPostMessage({ msg: 'onRunning' }); this.webviewPostMessage({ msg: 'onRunningOutput', data: '[Compiling...]' }); const killFunc = await runJava( join( repoDir, 'BWContest', teamData.contestId.toString(), teamData.teamId.toString(), problem.pascalName ), join( repoDir, 'BWContest', teamData.contestId.toString(), teamData.teamId.toString(), problem.pascalName, `${problem.pascalName}.java` ), problem.pascalName, input, (data: string) => { outputBuffer.push(data); this.webviewPostMessage({ msg: 'onRunningOutput', data: outputBuffer.join('') }); }, () => { this.runningProgram = undefined; this.webviewPostMessage({ msg: 'onRunningDone' }); } ); if (killFunc !== undefined) { this.runningProgram = { problemId: problemId, outputBuffer: outputBuffer, kill: killFunc }; } else { this.webviewPostMessage({ msg: 'onRunningDone' }); } } private async handleRequestProblemData() { const token: string | undefined = this.context.globalState.get('token'); if (token !== undefined) { const res = await fetch(urlJoin(this.webUrl, `/api/contest/${token}`)); const data = await res.json(); if (data.success === true) { this.problemData = data.problems; this.webviewPostMessage({ msg: 'onProblemData', data: data.problems }); } } } private async update() { const webview = this.panel.webview; this.panel.webview.html = this._getHtmlForWebview(webview); webview.onDidReceiveMessage((m: MessageType) => { switch (m.msg) { case 'onKill': { if (this.runningProgram !== undefined) { this.runningProgram.kill(); return; } break; } case 'onSubmit': { this.handleSubmit(m.data.problemId); break; } case 'onRun': { this.handleRun(m.data.problemId, m.data.input); break; } case 'onRequestProblemData': { this.handleRequestProblemData(); break; } } }); } private _getHtmlForWebview(webview: vscode.Webview) { const scriptUri = webview.asWebviewUri( vscode.Uri.joinPath(this.extensionUri, 'out/compiled', 'problemPanel.js') ); const stylesResetUri = webview.asWebviewUri( vscode.Uri.joinPath(this.extensionUri, 'media', 'reset.css') ); const stylesMainUri = webview.asWebviewUri( vscode.Uri.joinPath(this.extensionUri, 'media', 'vscode.css') ); const cssUri = webview.asWebviewUri( vscode.Uri.joinPath(this.extensionUri, 'out/compiled', 'problemPanel.css') ); const nonce = getNonce(); return ` `; } }