From 234ce83d9dc094804627d0c31fb5b6df5bfb3cec Mon Sep 17 00:00:00 2001 From: orosmatthew Date: Sun, 7 May 2023 11:01:27 -0400 Subject: [PATCH 1/2] [extension] Problem panel --- extension/bwcontest/src/SidebarProvider.ts | 7 ++ extension/bwcontest/src/extension.ts | 11 +- .../src/{BWPanel.ts => problemPanel.ts} | 27 ++++- .../webviews/components/HelloWorld.svelte | 4 - .../webviews/components/ProblemPanel.svelte | 110 ++++++++++++++++++ .../webviews/components/Sidebar.svelte | 5 + .../pages/{HelloWorld.ts => problemPanel.ts} | 2 +- 7 files changed, 154 insertions(+), 12 deletions(-) rename extension/bwcontest/src/{BWPanel.ts => problemPanel.ts} (85%) delete mode 100644 extension/bwcontest/webviews/components/HelloWorld.svelte create mode 100644 extension/bwcontest/webviews/components/ProblemPanel.svelte rename extension/bwcontest/webviews/pages/{HelloWorld.ts => problemPanel.ts} (57%) diff --git a/extension/bwcontest/src/SidebarProvider.ts b/extension/bwcontest/src/SidebarProvider.ts index 2cc9422..4631dfb 100644 --- a/extension/bwcontest/src/SidebarProvider.ts +++ b/extension/bwcontest/src/SidebarProvider.ts @@ -1,6 +1,7 @@ import * as vscode from 'vscode'; import { getNonce } from './getNonce'; import { cloneAndOpenRepo } from './extension'; +import { BWPanel } from './problemPanel'; export class SidebarProvider implements vscode.WebviewViewProvider { _view?: vscode.WebviewView; @@ -24,6 +25,12 @@ export class SidebarProvider implements vscode.WebviewViewProvider { webviewView.webview.onDidReceiveMessage(async (data) => { switch (data.type) { + case 'onTestAndSubmit': { + if (this._context) { + BWPanel.createOrShow(this._context?.extensionUri, this._context); + } + break; + } case 'onStartup': { const token: string | undefined = this._context?.globalState.get('token'); if (token) { diff --git a/extension/bwcontest/src/extension.ts b/extension/bwcontest/src/extension.ts index 2e546f2..787570b 100644 --- a/extension/bwcontest/src/extension.ts +++ b/extension/bwcontest/src/extension.ts @@ -2,6 +2,7 @@ import * as vscode from 'vscode'; import { SidebarProvider } from './SidebarProvider'; import * as child_process from 'child_process'; import * as fs from 'fs-extra'; +import { BWPanel } from './problemPanel'; interface BWContestSettings { repoBaseUrl: string; @@ -42,7 +43,9 @@ export async function cloneAndOpenRepo(contestId: number, teamId: number) { fs.mkdirSync(`${currentSettings.repoClonePath}/BWContest/${contestId.toString()}`); } - const clonedRepoPath = `${currentSettings.repoClonePath}/BWContest/${contestId.toString()}/${repoName}`; + const clonedRepoPath = `${ + currentSettings.repoClonePath + }/BWContest/${contestId.toString()}/${repoName}`; if (fs.existsSync(clonedRepoPath)) { const confirm = await vscode.window.showWarningMessage( @@ -88,7 +91,11 @@ export function activate(context: vscode.ExtensionContext) { vscode.window.registerWebviewViewProvider('bwcontest-sidebar', sidebarProvider) ); - context.subscriptions.push(vscode.commands.registerCommand('bwcontest.helloWorld', () => {})); + context.subscriptions.push( + vscode.commands.registerCommand('bwcontest.helloWorld', () => { + BWPanel.createOrShow(context.extensionUri, context); + }) + ); context.subscriptions.push( vscode.commands.registerCommand('bwcontest.askQuestion', async () => { diff --git a/extension/bwcontest/src/BWPanel.ts b/extension/bwcontest/src/problemPanel.ts similarity index 85% rename from extension/bwcontest/src/BWPanel.ts rename to extension/bwcontest/src/problemPanel.ts index 5dcf8ee..8c2d13b 100644 --- a/extension/bwcontest/src/BWPanel.ts +++ b/extension/bwcontest/src/problemPanel.ts @@ -12,8 +12,10 @@ export class BWPanel { private readonly _panel: vscode.WebviewPanel; private readonly _extensionUri: vscode.Uri; private _disposables: vscode.Disposable[] = []; + private static _context?: vscode.ExtensionContext; - public static createOrShow(extensionUri: vscode.Uri) { + public static createOrShow(extensionUri: vscode.Uri, context: vscode.ExtensionContext) { + this._context = context; const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined; @@ -99,6 +101,17 @@ export class BWPanel { this._panel.webview.html = this._getHtmlForWebview(webview); webview.onDidReceiveMessage(async (data) => { switch (data.type) { + case 'onStartup': { + const token: string | undefined = BWPanel._context?.globalState.get('token'); + + if (token) { + this._panel.webview.postMessage({ + type: 'onSession', + value: token + }); + } + break; + } case 'onInfo': { if (!data.value) { return; @@ -125,7 +138,7 @@ export class BWPanel { private _getHtmlForWebview(webview: vscode.Webview) { // // And the uri we use to load this script in the webview const scriptUri = webview.asWebviewUri( - vscode.Uri.joinPath(this._extensionUri, 'out/compiled', 'HelloWorld.js') + vscode.Uri.joinPath(this._extensionUri, 'out/compiled', 'problemPanel.js') ); // Uri to load styles into webview @@ -135,9 +148,9 @@ export class BWPanel { const stylesMainUri = webview.asWebviewUri( vscode.Uri.joinPath(this._extensionUri, 'media', 'vscode.css') ); - // const cssUri = webview.asWebviewUri( - // vscode.Uri.joinPath(this._extensionUri, 'out', 'compiled/swiper.css') - // ); + const cssUri = webview.asWebviewUri( + vscode.Uri.joinPath(this._extensionUri, 'out/compiled', 'problemPanel.css') + ); // // Use a nonce to only allow specific scripts to be run const nonce = getNonce(); @@ -154,6 +167,10 @@ export class BWPanel { + + diff --git a/extension/bwcontest/webviews/components/HelloWorld.svelte b/extension/bwcontest/webviews/components/HelloWorld.svelte deleted file mode 100644 index 1ff133f..0000000 --- a/extension/bwcontest/webviews/components/HelloWorld.svelte +++ /dev/null @@ -1,4 +0,0 @@ - - -

Test!

\ No newline at end of file diff --git a/extension/bwcontest/webviews/components/ProblemPanel.svelte b/extension/bwcontest/webviews/components/ProblemPanel.svelte new file mode 100644 index 0000000..5cf45eb --- /dev/null +++ b/extension/bwcontest/webviews/components/ProblemPanel.svelte @@ -0,0 +1,110 @@ + + +

Test & Submit Problems

+ +{#if problemData} +
+ {#each problemData as problem} + + {/each} +
+{/if} + +{#if activeProblem} +

{activeProblem.name}

+
+
+

Sample Input (You can edit this!)

+ + +
+
+

Output

+ +
+
+{/if} + + \ No newline at end of file diff --git a/extension/bwcontest/webviews/components/Sidebar.svelte b/extension/bwcontest/webviews/components/Sidebar.svelte index 6b10df5..4b9e153 100644 --- a/extension/bwcontest/webviews/components/Sidebar.svelte +++ b/extension/bwcontest/webviews/components/Sidebar.svelte @@ -86,6 +86,10 @@ } }) + function onTestAndSubmit() { + postMessage({type: 'onTestAndSubmit'}); + } + onMount(() => { postMessage({type: "onStartup"}); }) @@ -107,5 +111,6 @@

TeamID: {teamData.teamId}

ContestID: {teamData.contestId}

+ {/if} {/if} \ No newline at end of file diff --git a/extension/bwcontest/webviews/pages/HelloWorld.ts b/extension/bwcontest/webviews/pages/problemPanel.ts similarity index 57% rename from extension/bwcontest/webviews/pages/HelloWorld.ts rename to extension/bwcontest/webviews/pages/problemPanel.ts index 15a9682..4e57701 100644 --- a/extension/bwcontest/webviews/pages/HelloWorld.ts +++ b/extension/bwcontest/webviews/pages/problemPanel.ts @@ -1,4 +1,4 @@ -import App from '../components/HelloWorld.svelte'; +import App from '../components/ProblemPanel.svelte'; const app = new App({ target: document.body From 8ace5a006149ef1d5ef801064b8f0aa8f74dd704 Mon Sep 17 00:00:00 2001 From: orosmatthew Date: Sun, 7 May 2023 16:30:42 -0400 Subject: [PATCH 2/2] [extension] Run java files --- extension/bwcontest/package.json | 5 ++ extension/bwcontest/src/extension.ts | 7 +- extension/bwcontest/src/problemPanel.ts | 32 +++++++++ extension/bwcontest/src/run/java.ts | 51 ++++++++++++++ .../webviews/components/ProblemPanel.svelte | 70 +++++++++++++++++-- 5 files changed, 160 insertions(+), 5 deletions(-) create mode 100644 extension/bwcontest/src/run/java.ts diff --git a/extension/bwcontest/package.json b/extension/bwcontest/package.json index 74a1419..cfa80d1 100644 --- a/extension/bwcontest/package.json +++ b/extension/bwcontest/package.json @@ -24,6 +24,11 @@ "type": "string", "default": "", "description": "The path where the repos are cloned to" + }, + "BWContest.javaPath": { + "type": "string", + "default": "", + "description": "Path of java bin folder" } } }, diff --git a/extension/bwcontest/src/extension.ts b/extension/bwcontest/src/extension.ts index 787570b..fc1fd1a 100644 --- a/extension/bwcontest/src/extension.ts +++ b/extension/bwcontest/src/extension.ts @@ -4,9 +4,14 @@ import * as child_process from 'child_process'; import * as fs from 'fs-extra'; import { BWPanel } from './problemPanel'; -interface BWContestSettings { +export interface BWContestSettings { repoBaseUrl: string; repoClonePath: string; + javaPath: string; +} + +export function extensionSettings(): BWContestSettings { + return vscode.workspace.getConfiguration().get('BWContest')!; } function closeAllWorkspaces() { diff --git a/extension/bwcontest/src/problemPanel.ts b/extension/bwcontest/src/problemPanel.ts index 8c2d13b..ac4dffe 100644 --- a/extension/bwcontest/src/problemPanel.ts +++ b/extension/bwcontest/src/problemPanel.ts @@ -1,5 +1,8 @@ import * as vscode from 'vscode'; import { getNonce } from './getNonce'; +import { runJava } from './run/java'; +import { extensionSettings } from './extension'; +import { join } from 'path'; export class BWPanel { /** @@ -36,6 +39,8 @@ export class BWPanel { // Enable javascript in the webview enableScripts: true, + retainContextWhenHidden: true, + // And restrict the webview to only loading content from our extension's `media` directory. localResourceRoots: [ vscode.Uri.joinPath(extensionUri, 'media'), @@ -101,6 +106,33 @@ export class BWPanel { this._panel.webview.html = this._getHtmlForWebview(webview); webview.onDidReceiveMessage(async (data) => { switch (data.type) { + case 'onRun': { + if (!data.value) { + return; + } + const repoDir = extensionSettings().repoClonePath; + const output = await runJava( + join( + repoDir, + 'BWContest', + data.value.contestId.toString(), + data.value.teamId.toString(), + data.value.problemPascalName.toString() + ), + join( + repoDir, + 'BWContest', + data.value.contestId.toString(), + data.value.teamId.toString(), + data.value.problemPascalName.toString(), + `${data.value.problemPascalName}.java` + ), + data.value.problemPascalName, + data.value.input + ); + this._panel.webview.postMessage({ type: 'onOutput', value: output }); + break; + } case 'onStartup': { const token: string | undefined = BWPanel._context?.globalState.get('token'); diff --git a/extension/bwcontest/src/run/java.ts b/extension/bwcontest/src/run/java.ts new file mode 100644 index 0000000..b03fc57 --- /dev/null +++ b/extension/bwcontest/src/run/java.ts @@ -0,0 +1,51 @@ +import * as fs from 'fs-extra'; +import { join } from 'path'; +import os = require('os'); +import { exec, spawn } from 'child_process'; +import { extensionSettings } from '../extension'; +import { error } from 'console'; +import util = require('node:util'); + +const execPromise = util.promisify(exec); + +export async function runJava( + srcDir: string, + mainFile: string, + mainClass: string, + input: string +): Promise { + const javaPath = extensionSettings().javaPath; + if (javaPath == '') { + throw error('Java path not set'); + } + const tempDir = os.tmpdir(); + const buildDir = join(tempDir, 'bwcontest_java'); + if (fs.existsSync(buildDir)) { + fs.removeSync(buildDir); + } + fs.mkdirSync(buildDir); + + const compileCommand = `${join(javaPath, 'javac')} -cp ${srcDir} ${mainFile} -d ${buildDir}`; + await execPromise(compileCommand); + + const runCommand = `${join(javaPath, 'java')} -cp "${buildDir}" ${mainClass}`; + + return new Promise((resolve) => { + let outputBuffer = ''; + const child = spawn(runCommand, { shell: true }); + child.stdout.setEncoding('utf8'); + child.stdout.on('data', (data) => { + outputBuffer += data.toString(); + }); + child.stderr.setEncoding('utf8'); + child.stderr.on('data', (data) => { + outputBuffer += data.toString(); + }); + child.stdin.write(input); + child.stdin.end(); + + child.on('close', () => { + resolve(outputBuffer); + }); + }); +} diff --git a/extension/bwcontest/webviews/components/ProblemPanel.svelte b/extension/bwcontest/webviews/components/ProblemPanel.svelte index 5cf45eb..4c15cd2 100644 --- a/extension/bwcontest/webviews/components/ProblemPanel.svelte +++ b/extension/bwcontest/webviews/components/ProblemPanel.svelte @@ -8,15 +8,21 @@ type ProblemData = { id: number, name: string, + pascalName: string, sampleInput: string, sampleOutput: string }[]; + let savedInputs: Map = new Map(); + let activeProblem: ProblemData[0]; let sessionToken: string | undefined; let problemData: ProblemData | undefined; let sampleInputText: HTMLTextAreaElement; + let outputText: HTMLTextAreaElement; + + let running = false; $: if (problemData && problemData.length !== 0) { let first = problemData.at(0); @@ -29,12 +35,34 @@ sampleInputText.value = activeProblem.sampleInput; } + let contestId: number | undefined; + let teamId: number | undefined; + + function onRun() { + if (!running && contestId && teamId) { + postMessage({type: 'onRun', value: {problemPascalName: activeProblem.pascalName, contestId: contestId, teamId: teamId, input: sampleInputText.value}}); + running = true; + } + } + + function updateTextBoxes() { + if (savedInputs.has(activeProblem.id)) { + sampleInputText.value = savedInputs.get(activeProblem.id)!.input; + outputText.value = savedInputs.get(activeProblem.id)!.output; + } else { + sampleInputText.value = activeProblem.sampleInput; + outputText.value = "[Run to get output]"; + } + } + async function fetchProblemData() { if (sessionToken) { const res = await fetch(`http://localhost:5173/api/contest/${sessionToken}`); const data = await res.json(); if (data.success === true) { problemData = data.problems as ProblemData; + contestId = data.contestId; + teamId = data.teamId; } } } @@ -45,7 +73,11 @@ if (message.value !== "") { sessionToken = message.value; await fetchProblemData(); + updateTextBoxes(); } + } else if (message.type === 'onOutput') { + outputText.value = message.value; + running = false; } }) @@ -61,7 +93,11 @@
{#each problemData as problem} {/each}
@@ -72,12 +108,18 @@

Sample Input (You can edit this!)

- + +
+

Output

+ {#if running} + + {/if} +
+