This commit is contained in:
orosmatthew 2023-05-07 16:30:57 -04:00
commit 87dd37c6a6
9 changed files with 310 additions and 13 deletions

View File

@ -24,6 +24,11 @@
"type": "string", "type": "string",
"default": "", "default": "",
"description": "The path where the repos are cloned to" "description": "The path where the repos are cloned to"
},
"BWContest.javaPath": {
"type": "string",
"default": "",
"description": "Path of java bin folder"
} }
} }
}, },

View File

@ -1,6 +1,7 @@
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { getNonce } from './getNonce'; import { getNonce } from './getNonce';
import { cloneAndOpenRepo } from './extension'; import { cloneAndOpenRepo } from './extension';
import { BWPanel } from './problemPanel';
export class SidebarProvider implements vscode.WebviewViewProvider { export class SidebarProvider implements vscode.WebviewViewProvider {
_view?: vscode.WebviewView; _view?: vscode.WebviewView;
@ -24,6 +25,12 @@ export class SidebarProvider implements vscode.WebviewViewProvider {
webviewView.webview.onDidReceiveMessage(async (data) => { webviewView.webview.onDidReceiveMessage(async (data) => {
switch (data.type) { switch (data.type) {
case 'onTestAndSubmit': {
if (this._context) {
BWPanel.createOrShow(this._context?.extensionUri, this._context);
}
break;
}
case 'onStartup': { case 'onStartup': {
const token: string | undefined = this._context?.globalState.get('token'); const token: string | undefined = this._context?.globalState.get('token');
if (token) { if (token) {

View File

@ -2,10 +2,16 @@ import * as vscode from 'vscode';
import { SidebarProvider } from './SidebarProvider'; import { SidebarProvider } from './SidebarProvider';
import * as child_process from 'child_process'; import * as child_process from 'child_process';
import * as fs from 'fs-extra'; import * as fs from 'fs-extra';
import { BWPanel } from './problemPanel';
interface BWContestSettings { export interface BWContestSettings {
repoBaseUrl: string; repoBaseUrl: string;
repoClonePath: string; repoClonePath: string;
javaPath: string;
}
export function extensionSettings(): BWContestSettings {
return vscode.workspace.getConfiguration().get<BWContestSettings>('BWContest')!;
} }
function closeAllWorkspaces() { function closeAllWorkspaces() {
@ -42,7 +48,9 @@ export async function cloneAndOpenRepo(contestId: number, teamId: number) {
fs.mkdirSync(`${currentSettings.repoClonePath}/BWContest/${contestId.toString()}`); 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)) { if (fs.existsSync(clonedRepoPath)) {
const confirm = await vscode.window.showWarningMessage( const confirm = await vscode.window.showWarningMessage(
@ -88,7 +96,11 @@ export function activate(context: vscode.ExtensionContext) {
vscode.window.registerWebviewViewProvider('bwcontest-sidebar', sidebarProvider) 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( context.subscriptions.push(
vscode.commands.registerCommand('bwcontest.askQuestion', async () => { vscode.commands.registerCommand('bwcontest.askQuestion', async () => {

View File

@ -1,5 +1,8 @@
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { getNonce } from './getNonce'; import { getNonce } from './getNonce';
import { runJava } from './run/java';
import { extensionSettings } from './extension';
import { join } from 'path';
export class BWPanel { export class BWPanel {
/** /**
@ -12,8 +15,10 @@ export class BWPanel {
private readonly _panel: vscode.WebviewPanel; private readonly _panel: vscode.WebviewPanel;
private readonly _extensionUri: vscode.Uri; private readonly _extensionUri: vscode.Uri;
private _disposables: vscode.Disposable[] = []; 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 const column = vscode.window.activeTextEditor
? vscode.window.activeTextEditor.viewColumn ? vscode.window.activeTextEditor.viewColumn
: undefined; : undefined;
@ -34,6 +39,8 @@ export class BWPanel {
// Enable javascript in the webview // Enable javascript in the webview
enableScripts: true, enableScripts: true,
retainContextWhenHidden: true,
// And restrict the webview to only loading content from our extension's `media` directory. // And restrict the webview to only loading content from our extension's `media` directory.
localResourceRoots: [ localResourceRoots: [
vscode.Uri.joinPath(extensionUri, 'media'), vscode.Uri.joinPath(extensionUri, 'media'),
@ -99,6 +106,44 @@ export class BWPanel {
this._panel.webview.html = this._getHtmlForWebview(webview); this._panel.webview.html = this._getHtmlForWebview(webview);
webview.onDidReceiveMessage(async (data) => { webview.onDidReceiveMessage(async (data) => {
switch (data.type) { 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');
if (token) {
this._panel.webview.postMessage({
type: 'onSession',
value: token
});
}
break;
}
case 'onInfo': { case 'onInfo': {
if (!data.value) { if (!data.value) {
return; return;
@ -125,7 +170,7 @@ export class BWPanel {
private _getHtmlForWebview(webview: vscode.Webview) { private _getHtmlForWebview(webview: vscode.Webview) {
// // And the uri we use to load this script in the webview // // And the uri we use to load this script in the webview
const scriptUri = webview.asWebviewUri( 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 // Uri to load styles into webview
@ -135,9 +180,9 @@ export class BWPanel {
const stylesMainUri = webview.asWebviewUri( const stylesMainUri = webview.asWebviewUri(
vscode.Uri.joinPath(this._extensionUri, 'media', 'vscode.css') vscode.Uri.joinPath(this._extensionUri, 'media', 'vscode.css')
); );
// const cssUri = webview.asWebviewUri( const cssUri = webview.asWebviewUri(
// vscode.Uri.joinPath(this._extensionUri, 'out', 'compiled/swiper.css') vscode.Uri.joinPath(this._extensionUri, 'out/compiled', 'problemPanel.css')
// ); );
// // Use a nonce to only allow specific scripts to be run // // Use a nonce to only allow specific scripts to be run
const nonce = getNonce(); const nonce = getNonce();
@ -154,6 +199,10 @@ export class BWPanel {
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="${stylesResetUri}" rel="stylesheet"> <link href="${stylesResetUri}" rel="stylesheet">
<link href="${stylesMainUri}" rel="stylesheet"> <link href="${stylesMainUri}" rel="stylesheet">
<link href="${cssUri}" rel="stylesheet">
<script nonce="${nonce}">
const vscode = acquireVsCodeApi();
</script>
</head> </head>
<body> <body>
</body> </body>

View File

@ -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<string> {
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);
});
});
}

View File

@ -1,4 +0,0 @@
<script lang="ts">
</script>
<h1>Test!</h1>

View File

@ -0,0 +1,172 @@
<script lang="ts">
import { onMount } from "svelte";
function postMessage(message: any) {
vscode.postMessage(message);
}
type ProblemData = {
id: number,
name: string,
pascalName: string,
sampleInput: string,
sampleOutput: string
}[];
let savedInputs: Map<number, {input: string, output: string}> = 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);
if (first) {
activeProblem = first;
}
};
function resetInput() {
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;
}
}
}
window.addEventListener("message", async (event) => {
const message = (event as MessageEvent).data;
if (message.type === "onSession") {
if (message.value !== "") {
sessionToken = message.value;
await fetchProblemData();
updateTextBoxes();
}
} else if (message.type === 'onOutput') {
outputText.value = message.value;
running = false;
}
})
onMount(() => {
postMessage({type: "onStartup"});
})
</script>
<h1>Test & Submit Problems</h1>
{#if problemData}
<div class="tab-container">
{#each problemData as problem}
<button on:click={() => {
if (!running) {
savedInputs.set(activeProblem.id, {input: sampleInputText.value, output: outputText.value});
activeProblem = problem;
updateTextBoxes();
}
}} id={`problem_${problem.id}`} type="button" class={"tab " + (activeProblem.id == problem.id ? "active" : "")}>{problem.name}</button>
{/each}
</div>
{/if}
{#if activeProblem}
<h2>{activeProblem.name}</h2>
<div style="display:flex">
<div style="flex:1; margin-right:20px">
<h3>Sample Input (You can edit this!)</h3>
<textarea bind:this={sampleInputText} />
<button style="margin-top:5px" on:click={resetInput} type="button">Reset Input</button>
</div>
<div style="flex:1">
<div style="display:flex">
<h3 style="margin-right:5px">Output</h3>
{#if running}
<span class="loader"></span>
{/if}
</div>
<textarea bind:this={outputText} disabled />
<button style="margin-top:5px" on:click={onRun} type="button">Run</button>
</div>
</div>
{/if}
<style>
textarea {
resize: vertical;
height: 250px;
}
.tab-container {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
height: 30px;
margin-bottom: 10px;
}
.tab {
flex: 1;
border: none;
cursor: pointer;
text-align: center;
}
.tab.active {
background-color: rgb(95, 103, 118);
}
.loader {
width: 16px;
height: 16px;
border: 3px solid #FFF;
border-bottom-color: transparent;
border-radius: 50%;
display: inline-block;
box-sizing: border-box;
animation: rotation 1s linear infinite;
}
@keyframes rotation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>

View File

@ -86,6 +86,10 @@
} }
}) })
function onTestAndSubmit() {
postMessage({type: 'onTestAndSubmit'});
}
onMount(() => { onMount(() => {
postMessage({type: "onStartup"}); postMessage({type: "onStartup"});
}) })
@ -107,5 +111,6 @@
<p>TeamID: {teamData.teamId}</p> <p>TeamID: {teamData.teamId}</p>
<p>ContestID: {teamData.contestId}</p> <p>ContestID: {teamData.contestId}</p>
<button on:click={onClone}>Clone and Open Repo</button> <button on:click={onClone}>Clone and Open Repo</button>
<button on:click={onTestAndSubmit}>Test & Submit</button>
{/if} {/if}
{/if} {/if}

View File

@ -1,4 +1,4 @@
import App from '../components/HelloWorld.svelte'; import App from '../components/ProblemPanel.svelte';
const app = new App({ const app = new App({
target: document.body target: document.body