From fde6b0019b9112e960d82ff4119461d188463f3a Mon Sep 17 00:00:00 2001 From: orosmatthew Date: Tue, 5 Mar 2024 19:31:11 -0500 Subject: [PATCH] [extension] Format --- extension/bwcontest/package.json | 4 +- extension/bwcontest/src/SidebarProvider.ts | 133 +++++---- .../contestMonitorSharedTypes.ts | 42 +-- .../contestMonitor/contestStateSyncManager.ts | 271 +++++++++++------- .../src/contestMonitor/pollingService.ts | 28 +- extension/bwcontest/src/extension.ts | 22 +- extension/bwcontest/src/outputPanelLog.ts | 8 +- extension/bwcontest/src/problemPanel.ts | 35 ++- extension/bwcontest/src/run/cpp.ts | 12 +- extension/bwcontest/src/run/java.ts | 8 +- extension/bwcontest/src/sharedTypes.ts | 2 +- extension/bwcontest/src/submit.ts | 9 +- .../bwcontest/src/utilities/LiteEvent.ts | 38 +-- .../src/utilities/SimpleCancellationToken.ts | 16 +- extension/bwcontest/src/utilities/sleep.ts | 4 +- .../webviews/components/Sidebar.svelte | 42 ++- .../components/SidebarProblemStatus.svelte | 72 +++-- 17 files changed, 458 insertions(+), 288 deletions(-) diff --git a/extension/bwcontest/package.json b/extension/bwcontest/package.json index a7ad95f..9fe5c5c 100644 --- a/extension/bwcontest/package.json +++ b/extension/bwcontest/package.json @@ -9,7 +9,9 @@ "categories": [ "Other" ], - "activationEvents": ["onStartupFinished"], + "activationEvents": [ + "onStartupFinished" + ], "main": "./out/main.js", "contributes": { "configuration": { diff --git a/extension/bwcontest/src/SidebarProvider.ts b/extension/bwcontest/src/SidebarProvider.ts index 118dffb..259f676 100644 --- a/extension/bwcontest/src/SidebarProvider.ts +++ b/extension/bwcontest/src/SidebarProvider.ts @@ -4,15 +4,25 @@ import { cloneAndOpenRepo } from './extension'; import { BWPanel } from './problemPanel'; import urlJoin from 'url-join'; import outputPanelLog from './outputPanelLog'; -import { ContestStateForExtension, ProblemNameForExtension, SubmissionForExtension, SubmissionStateForExtension } from './contestMonitor/contestMonitorSharedTypes'; +import { + ContestStateForExtension, + ProblemNameForExtension, + SubmissionForExtension, + SubmissionStateForExtension +} from './contestMonitor/contestMonitorSharedTypes'; import { TeamData } from './sharedTypes'; -import { ContestTeamState, getCachedContestTeamState, clearCachedContestTeamState, submissionsListChanged } from './contestMonitor/contestStateSyncManager'; +import { + ContestTeamState, + getCachedContestTeamState, + clearCachedContestTeamState, + submissionsListChanged +} from './contestMonitor/contestStateSyncManager'; import { startTeamStatusPolling, stopTeamStatusPolling } from './contestMonitor/pollingService'; export type WebviewMessageType = - { msg: 'onLogin'; data: TeamData } | - { msg: 'onLogout' } | - { msg: 'teamStatusUpdated'; data: SidebarTeamStatus | null }; + | { msg: 'onLogin'; data: TeamData } + | { msg: 'onLogout' } + | { msg: 'teamStatusUpdated'; data: SidebarTeamStatus | null }; export type MessageType = | { msg: 'onTestAndSubmit' } @@ -27,14 +37,14 @@ export type SidebarTeamStatus = { processingProblems: SidebarProblemWithSubmissions[]; incorrectProblems: SidebarProblemWithSubmissions[]; notStartedProblems: SidebarProblemWithSubmissions[]; -} +}; export type SidebarProblemWithSubmissions = { problem: ProblemNameForExtension; overallState: SubmissionStateForExtension | null; submissions: SubmissionForExtension[]; modified: boolean; -} +}; export class SidebarProvider implements vscode.WebviewViewProvider { private webview: vscode.Webview | null = null; @@ -43,24 +53,28 @@ export class SidebarProvider implements vscode.WebviewViewProvider { private readonly extensionUri: vscode.Uri, private readonly context: vscode.ExtensionContext, private readonly webUrl: string - ) { - outputPanelLog.info("Constructing SidebarProvider"); + ) { + outputPanelLog.info('Constructing SidebarProvider'); const currentSubmissionsList = getCachedContestTeamState(); - outputPanelLog.info("When SidebarProvider constructed, cached submission list is: " + JSON.stringify(currentSubmissionsList)); + outputPanelLog.info( + 'When SidebarProvider constructed, cached submission list is: ' + + JSON.stringify(currentSubmissionsList) + ); this.updateTeamStatus(currentSubmissionsList); - submissionsListChanged.add(submissionsChangedEventArgs => { - outputPanelLog.trace("Sidebar submission list updating from submissionsListChanged event"); + submissionsListChanged.add((submissionsChangedEventArgs) => { + outputPanelLog.trace('Sidebar submission list updating from submissionsListChanged event'); if (!submissionsChangedEventArgs) { return; } this.updateTeamStatus( - submissionsChangedEventArgs.contestTeamState, + submissionsChangedEventArgs.contestTeamState, submissionsChangedEventArgs.changedProblemIds - )}); + ); + }); } private async handleLogin( @@ -84,7 +98,7 @@ export class SidebarProvider implements vscode.WebviewViewProvider { } if (resData.success !== true) { - outputPanelLog.error('Invalid Login attempt with message: ' + (resData.message ?? "")); + outputPanelLog.error('Invalid Login attempt with message: ' + (resData.message ?? '')); vscode.window.showErrorMessage('BWContest: Invalid Login'); return; } @@ -95,7 +109,9 @@ export class SidebarProvider implements vscode.WebviewViewProvider { }); const data2 = await teamRes.json(); if (!data2.success) { - outputPanelLog.error('Login attempt retrieved token but not team details. Staying logged out.'); + outputPanelLog.error( + 'Login attempt retrieved token but not team details. Staying logged out.' + ); vscode.window.showErrorMessage('BWContest: Invalid Login'); return; } @@ -109,21 +125,27 @@ export class SidebarProvider implements vscode.WebviewViewProvider { webviewPostMessage({ msg: 'onLogin', data: data2.data }); const currentSubmissionsList = getCachedContestTeamState(); - outputPanelLog.info("After login, cached submission list is: " + JSON.stringify(currentSubmissionsList)); + outputPanelLog.info( + 'After login, cached submission list is: ' + JSON.stringify(currentSubmissionsList) + ); this.updateTeamStatus(currentSubmissionsList); } private async handleLogout(webviewPostMessage: (m: WebviewMessageType) => void) { const sessionToken = this.context.globalState.get('token'); if (sessionToken === undefined) { - outputPanelLog.error("Team requested logout, but no token was stored locally. Switching to logged out state."); + outputPanelLog.error( + 'Team requested logout, but no token was stored locally. Switching to logged out state.' + ); this.clearLocalTeamDataAndFinishLogout(webviewPostMessage); return; } const teamData = this.context.globalState.get('teamData'); if (teamData === undefined) { - outputPanelLog.error("Team requested logout with a locally stored token but no teamData. Switching to logged out state."); + outputPanelLog.error( + 'Team requested logout with a locally stored token but no teamData. Switching to logged out state.' + ); this.clearLocalTeamDataAndFinishLogout(webviewPostMessage); return; } @@ -138,16 +160,21 @@ export class SidebarProvider implements vscode.WebviewViewProvider { }); if (res.status !== 200) { - outputPanelLog.error(`Team requested logout, failed with status code ${res.status}. Not modifying local state.`); + outputPanelLog.error( + `Team requested logout, failed with status code ${res.status}. Not modifying local state.` + ); vscode.window.showErrorMessage(`BWContest: Logout failed with code ${res.status}`); return; - }; + } const data2 = await res.json(); const responseMessage = data2.message ? `Message: ${data2.message}` : ''; if (data2.success !== true) { - outputPanelLog.error(`Team requested logout, failed with normal status code. Not modifying local state. ` + responseMessage); + outputPanelLog.error( + `Team requested logout, failed with normal status code. Not modifying local state. ` + + responseMessage + ); vscode.window.showErrorMessage(`BWContest: Logout failed.`); return; } @@ -166,44 +193,51 @@ export class SidebarProvider implements vscode.WebviewViewProvider { this.context.globalState.update('teamData', undefined); } - public updateTeamStatus(contestTeamState : ContestTeamState | null, changedProblemIds = new Set) { + public updateTeamStatus( + contestTeamState: ContestTeamState | null, + changedProblemIds = new Set() + ) { if (contestTeamState == null) { - outputPanelLog.trace("Not updating sidebar submission list because provided state is null"); + outputPanelLog.trace('Not updating sidebar submission list because provided state is null'); return; } if (this.webview == null) { - outputPanelLog.trace("Not updating sidebar submission list because webview is null"); + outputPanelLog.trace('Not updating sidebar submission list because webview is null'); return; } const contestState = contestTeamState.contestState; - const problemsWithSubmissions = contestState.problems.map(p => ({ - problem: p, - overallState: calculateOverallState(contestTeamState.submissionsList.get(p.id) ?? []), - submissions: contestTeamState.submissionsList.get(p.id) ?? [], - modified: changedProblemIds.has(p.id) - })); + const problemsWithSubmissions = contestState.problems.map( + (p) => ({ + problem: p, + overallState: calculateOverallState(contestTeamState.submissionsList.get(p.id) ?? []), + submissions: contestTeamState.submissionsList.get(p.id) ?? [], + modified: changedProblemIds.has(p.id) + }) + ); const teamStatus: SidebarTeamStatus = { contestState, - correctProblems: problemsWithSubmissions.filter(p => p.overallState === 'Correct'), - processingProblems: problemsWithSubmissions.filter(p => p.overallState === 'Processing'), - incorrectProblems: problemsWithSubmissions.filter(p => p.overallState === 'Incorrect'), - notStartedProblems: problemsWithSubmissions.filter(p => p.overallState === null), - } + correctProblems: problemsWithSubmissions.filter((p) => p.overallState === 'Correct'), + processingProblems: problemsWithSubmissions.filter((p) => p.overallState === 'Processing'), + incorrectProblems: problemsWithSubmissions.filter((p) => p.overallState === 'Incorrect'), + notStartedProblems: problemsWithSubmissions.filter((p) => p.overallState === null) + }; const message: WebviewMessageType = { msg: 'teamStatusUpdated', data: teamStatus }; - outputPanelLog.trace("Posting teamStatusUpdated to webview with message: " + JSON.stringify(message)); + outputPanelLog.trace( + 'Posting teamStatusUpdated to webview with message: ' + JSON.stringify(message) + ); this.webview.postMessage(message); } public resolveWebviewView(webviewView: vscode.WebviewView) { - outputPanelLog.trace("SidebarProvider resolveWebviewView"); + outputPanelLog.trace('SidebarProvider resolveWebviewView'); const webview = webviewView.webview; this.webview = webview; webview.options = { @@ -225,7 +259,7 @@ export class SidebarProvider implements vscode.WebviewViewProvider { break; } case 'onUIMount': { - outputPanelLog.trace("SidebarProvider onUIMount"); + outputPanelLog.trace('SidebarProvider onUIMount'); const token = this.context.globalState.get('token'); const teamData = this.context.globalState.get('teamData'); if (token !== undefined && teamData !== undefined) { @@ -235,7 +269,9 @@ export class SidebarProvider implements vscode.WebviewViewProvider { }); const currentSubmissionsList = getCachedContestTeamState(); - outputPanelLog.trace("onUIMount, currentSubmissionsList is " + JSON.stringify(currentSubmissionsList)); + outputPanelLog.trace( + 'onUIMount, currentSubmissionsList is ' + JSON.stringify(currentSubmissionsList) + ); this.updateTeamStatus(currentSubmissionsList); } break; @@ -297,17 +333,16 @@ export class SidebarProvider implements vscode.WebviewViewProvider { `; } } -function calculateOverallState(submissions: SubmissionForExtension[]): SubmissionStateForExtension | null { - if (submissions.find(s => s.state === 'Correct')) { +function calculateOverallState( + submissions: SubmissionForExtension[] +): SubmissionStateForExtension | null { + if (submissions.find((s) => s.state === 'Correct')) { return 'Correct'; - } - else if (submissions.find(s => s.state === 'Processing')) { + } else if (submissions.find((s) => s.state === 'Processing')) { return 'Processing'; - } - else if (submissions.find(s => s.state === 'Incorrect')) { + } else if (submissions.find((s) => s.state === 'Incorrect')) { return 'Incorrect'; - } - else { + } else { return null; } -} \ No newline at end of file +} diff --git a/extension/bwcontest/src/contestMonitor/contestMonitorSharedTypes.ts b/extension/bwcontest/src/contestMonitor/contestMonitorSharedTypes.ts index 4ec0829..2d6981f 100644 --- a/extension/bwcontest/src/contestMonitor/contestMonitorSharedTypes.ts +++ b/extension/bwcontest/src/contestMonitor/contestMonitorSharedTypes.ts @@ -1,29 +1,29 @@ export type FullStateForExtension = { - contestState: ContestStateForExtension, - submissions: SubmissionForExtension[] -} + contestState: ContestStateForExtension; + submissions: SubmissionForExtension[]; +}; export type ProblemNameForExtension = { - id: number, - friendlyName: string, -} + id: number; + friendlyName: string; +}; -export type ContestStateForExtension = { - startTime: Date | null, - endTime: Date | null, - problems: ProblemNameForExtension[], - isActive: boolean, - isScoreboardFrozen: boolean, -} +export type ContestStateForExtension = { + startTime: Date | null; + endTime: Date | null; + problems: ProblemNameForExtension[]; + isActive: boolean; + isScoreboardFrozen: boolean; +}; export type SubmissionStateForExtension = 'Processing' | 'Correct' | 'Incorrect'; export type SubmissionForExtension = { - id: number, - contestId: number, - teamId: number, - problemId: number, - createdAt: Date, - state: SubmissionStateForExtension - message: string | null -} \ No newline at end of file + id: number; + contestId: number; + teamId: number; + problemId: number; + createdAt: Date; + state: SubmissionStateForExtension; + message: string | null; +}; diff --git a/extension/bwcontest/src/contestMonitor/contestStateSyncManager.ts b/extension/bwcontest/src/contestMonitor/contestStateSyncManager.ts index ce9446f..21972b6 100644 --- a/extension/bwcontest/src/contestMonitor/contestStateSyncManager.ts +++ b/extension/bwcontest/src/contestMonitor/contestStateSyncManager.ts @@ -2,27 +2,32 @@ import * as vscode from 'vscode'; import urlJoin from 'url-join'; import outputPanelLog from '../outputPanelLog'; import { extensionSettings } from '../extension'; -import { ContestStateForExtension, ProblemNameForExtension, FullStateForExtension, SubmissionForExtension } from './contestMonitorSharedTypes'; +import { + ContestStateForExtension, + ProblemNameForExtension, + FullStateForExtension, + SubmissionForExtension +} from './contestMonitorSharedTypes'; import { LiteEvent } from '../utilities/LiteEvent'; export type ContestTeamState = { - contestState: ContestStateForExtension, - submissionsList: Map -} + contestState: ContestStateForExtension; + submissionsList: Map; +}; export type SubmissionListStateChangedEventArgs = { - contestTeamState: ContestTeamState, - changedProblemIds: Set -} + contestTeamState: ContestTeamState; + changedProblemIds: Set; +}; let latestContestTeamState: ContestTeamState | null = null; export function getCachedContestTeamState(): ContestTeamState | null { - return latestContestTeamState; + return latestContestTeamState; } export function clearCachedContestTeamState(): void { - latestContestTeamState = null; + latestContestTeamState = null; } const onSubmissionsListChanged = new LiteEvent(); @@ -30,141 +35,189 @@ export const submissionsListChanged = onSubmissionsListChanged.expose(); let latestPollNum = 0; export async function pollContestStatus(context: vscode.ExtensionContext) { - const pollNum = ++latestPollNum; - outputPanelLog.trace(`Polling contest status, poll #${pollNum}`); + const pollNum = ++latestPollNum; + outputPanelLog.trace(`Polling contest status, poll #${pollNum}`); const sessionToken = context.globalState.get('token'); if (!sessionToken) { - outputPanelLog.trace(` Ending poll #${pollNum}: No sessionToken`); + outputPanelLog.trace(` Ending poll #${pollNum}: No sessionToken`); return; } - const contestStateResponse = await fetch(urlJoin(extensionSettings().webUrl, `api/team/${sessionToken}/contestState`), { - method: 'GET' - }); + const contestStateResponse = await fetch( + urlJoin(extensionSettings().webUrl, `api/team/${sessionToken}/contestState`), + { + method: 'GET' + } + ); if (contestStateResponse.status != 200) { - outputPanelLog.trace(` Ending poll #${pollNum}: Status check API returned http status ${contestStateResponse.status}`); + outputPanelLog.trace( + ` Ending poll #${pollNum}: Status check API returned http status ${contestStateResponse.status}` + ); return; } const data = await contestStateResponse.json(); if (!data.success) { - outputPanelLog.trace(` Ending poll #${pollNum}: Status check returned OK but was not successful`); + outputPanelLog.trace( + ` Ending poll #${pollNum}: Status check returned OK but was not successful` + ); return; } - const fullState: FullStateForExtension = data.data; - outputPanelLog.trace(` Poll #${pollNum} succeeded. Submission count: ${fullState.submissions.length}. Diffing...`); + const fullState: FullStateForExtension = data.data; + outputPanelLog.trace( + ` Poll #${pollNum} succeeded. Submission count: ${fullState.submissions.length}. Diffing...` + ); - diffAndUpdateContestState(fullState); + diffAndUpdateContestState(fullState); } function diffAndUpdateContestState(fullState: FullStateForExtension) { - const contestState = fullState.contestState; - const currentSubmissionsList = createProblemSubmissionsLookup(contestState.problems, fullState.submissions); - const changedProblemIds = new Set(); + const contestState = fullState.contestState; + const currentSubmissionsList = createProblemSubmissionsLookup( + contestState.problems, + fullState.submissions + ); + const changedProblemIds = new Set(); - let anythingChanged = false; - if (latestContestTeamState == null) { - outputPanelLog.trace(` No previously cached data to diff`); - anythingChanged = true; - } - else { - for (const problem of contestState.problems) { - const problemId = problem.id; - const currentSubmissionsForProblem = currentSubmissionsList.get(problemId) ?? []; - const cachedSubmissionsForProblem = latestContestTeamState.submissionsList.get(problemId) ?? []; + let anythingChanged = false; + if (latestContestTeamState == null) { + outputPanelLog.trace(` No previously cached data to diff`); + anythingChanged = true; + } else { + for (const problem of contestState.problems) { + const problemId = problem.id; + const currentSubmissionsForProblem = currentSubmissionsList.get(problemId) ?? []; + const cachedSubmissionsForProblem = + latestContestTeamState.submissionsList.get(problemId) ?? []; - const currentSubmissionsAlreadyInCache = currentSubmissionsForProblem!.filter(s => cachedSubmissionsForProblem.find(ss => ss.id == s.id)); - const currentSubmissionsNotInCache = currentSubmissionsForProblem!.filter(s => !cachedSubmissionsForProblem.find(ss => ss.id == s.id)); - const cachedSubmissionsNotInCurrent = cachedSubmissionsForProblem.filter(s => !currentSubmissionsForProblem!.find(ss => ss.id == s.id)); + const currentSubmissionsAlreadyInCache = currentSubmissionsForProblem!.filter((s) => + cachedSubmissionsForProblem.find((ss) => ss.id == s.id) + ); + const currentSubmissionsNotInCache = currentSubmissionsForProblem!.filter( + (s) => !cachedSubmissionsForProblem.find((ss) => ss.id == s.id) + ); + const cachedSubmissionsNotInCurrent = cachedSubmissionsForProblem.filter( + (s) => !currentSubmissionsForProblem!.find((ss) => ss.id == s.id) + ); - for (const currentSubmission of currentSubmissionsAlreadyInCache ) { - const previousSubmission = cachedSubmissionsForProblem.find(s => s.id == currentSubmission.id)!; - if (currentSubmission.state != previousSubmission.state) { - anythingChanged = true; - changedProblemIds.add(problem.id); - outputPanelLog.trace(` Submission state for #${currentSubmission.id} changed from ${previousSubmission.state} (message '${previousSubmission.message}') to ${currentSubmission.state} (message '${currentSubmission.message}')`); - alertForNewState(problem, currentSubmission); - } else if (currentSubmission.message != previousSubmission.message) { - anythingChanged = true; - changedProblemIds.add(problem.id); - outputPanelLog.trace(` Submission message changed (with same state) for #${currentSubmission.id} from ${previousSubmission.message} to ${currentSubmission.message}`); - } - } + for (const currentSubmission of currentSubmissionsAlreadyInCache) { + const previousSubmission = cachedSubmissionsForProblem.find( + (s) => s.id == currentSubmission.id + )!; + if (currentSubmission.state != previousSubmission.state) { + anythingChanged = true; + changedProblemIds.add(problem.id); + outputPanelLog.trace( + ` Submission state for #${currentSubmission.id} changed from ${previousSubmission.state} (message '${previousSubmission.message}') to ${currentSubmission.state} (message '${currentSubmission.message}')` + ); + alertForNewState(problem, currentSubmission); + } else if (currentSubmission.message != previousSubmission.message) { + anythingChanged = true; + changedProblemIds.add(problem.id); + outputPanelLog.trace( + ` Submission message changed (with same state) for #${currentSubmission.id} from ${previousSubmission.message} to ${currentSubmission.message}` + ); + } + } - for (const currentSubmission of currentSubmissionsNotInCache ) { - anythingChanged = true; - changedProblemIds.add(problem.id); - outputPanelLog.trace(` Newly acknowledge submission #${currentSubmission.id} with state ${currentSubmission.state} and message ${currentSubmission.message}`); - alertForNewState(problem, currentSubmission); - } + for (const currentSubmission of currentSubmissionsNotInCache) { + anythingChanged = true; + changedProblemIds.add(problem.id); + outputPanelLog.trace( + ` Newly acknowledge submission #${currentSubmission.id} with state ${currentSubmission.state} and message ${currentSubmission.message}` + ); + alertForNewState(problem, currentSubmission); + } - for (const previousSubmission of cachedSubmissionsNotInCurrent ) { - anythingChanged = true; - outputPanelLog.trace(` Deleted submission #${previousSubmission.id}`); - } - } - } + for (const previousSubmission of cachedSubmissionsNotInCurrent) { + anythingChanged = true; + outputPanelLog.trace(` Deleted submission #${previousSubmission.id}`); + } + } + } - outputPanelLog.trace(anythingChanged ? " Diff has changes, triggering events" : " No changes found"); - - if (anythingChanged) { - latestContestTeamState = { contestState, submissionsList: currentSubmissionsList}; - onSubmissionsListChanged.trigger({ - contestTeamState: latestContestTeamState, - changedProblemIds - }); - } + outputPanelLog.trace( + anythingChanged ? ' Diff has changes, triggering events' : ' No changes found' + ); + + if (anythingChanged) { + latestContestTeamState = { contestState, submissionsList: currentSubmissionsList }; + onSubmissionsListChanged.trigger({ + contestTeamState: latestContestTeamState, + changedProblemIds + }); + } } -function createProblemSubmissionsLookup(problems: ProblemNameForExtension[], submissions: SubmissionForExtension[]): Map { - const orderedSubmissionsByProblemId = new Map(); - for (const problem of problems) { - orderedSubmissionsByProblemId.set(problem.id, []); - } +function createProblemSubmissionsLookup( + problems: ProblemNameForExtension[], + submissions: SubmissionForExtension[] +): Map { + const orderedSubmissionsByProblemId = new Map(); + for (const problem of problems) { + orderedSubmissionsByProblemId.set(problem.id, []); + } - for (const submission of submissions.sort(s => s.id)) { - orderedSubmissionsByProblemId.get(submission.problemId)!.push(submission); - } + for (const submission of submissions.sort((s) => s.id)) { + orderedSubmissionsByProblemId.get(submission.problemId)!.push(submission); + } - return orderedSubmissionsByProblemId; + return orderedSubmissionsByProblemId; } -function alertForNewState(problem: ProblemNameForExtension, currentSubmission: SubmissionForExtension) { - // Only alert on state changes team cares about - if (currentSubmission.state === 'Correct') { - vscode.window.showInformationMessage(`BWContest Judge: CORRECT Submission '${problem.friendlyName}'`); - } else if (currentSubmission.state === 'Incorrect') { - const messageDisplayText = currentSubmission.message ? `Message: ${currentSubmission.message}` : ''; - vscode.window.showInformationMessage(`BWContest Judge: INCORRECT Submission '${problem.friendlyName}' ${messageDisplayText}`); - } +function alertForNewState( + problem: ProblemNameForExtension, + currentSubmission: SubmissionForExtension +) { + // Only alert on state changes team cares about + if (currentSubmission.state === 'Correct') { + vscode.window.showInformationMessage( + `BWContest Judge: CORRECT Submission '${problem.friendlyName}'` + ); + } else if (currentSubmission.state === 'Incorrect') { + const messageDisplayText = currentSubmission.message + ? `Message: ${currentSubmission.message}` + : ''; + vscode.window.showInformationMessage( + `BWContest Judge: INCORRECT Submission '${problem.friendlyName}' ${messageDisplayText}` + ); + } } -export function recordInitialSubmission(submission: SubmissionForExtension): void { - outputPanelLog.trace("Server received new submission, #" + submission.id); +export function recordInitialSubmission(submission: SubmissionForExtension): void { + outputPanelLog.trace('Server received new submission, #' + submission.id); - if (!latestContestTeamState) { - outputPanelLog.trace(" No locally cached submission list state, the normal polling cycle will update the list"); - return; - } + if (!latestContestTeamState) { + outputPanelLog.trace( + ' No locally cached submission list state, the normal polling cycle will update the list' + ); + return; + } - const existingSubmissionListForProblem = latestContestTeamState.submissionsList.get(submission.problemId); - if (existingSubmissionListForProblem === undefined) { - outputPanelLog.trace(` The cached submission list does not know about problemId #${submission.problemId}. Next polling cycle should fix consistency.`); - return; - } + const existingSubmissionListForProblem = latestContestTeamState.submissionsList.get( + submission.problemId + ); + if (existingSubmissionListForProblem === undefined) { + outputPanelLog.trace( + ` The cached submission list does not know about problemId #${submission.problemId}. Next polling cycle should fix consistency.` + ); + return; + } - if (existingSubmissionListForProblem.find(s => s.id == submission.id)) { - outputPanelLog.trace(` The cached submission list already knows about submissionId #${submission.id}`); - return; - } + if (existingSubmissionListForProblem.find((s) => s.id == submission.id)) { + outputPanelLog.trace( + ` The cached submission list already knows about submissionId #${submission.id}` + ); + return; + } - outputPanelLog.trace(` New submission #${submission.id} added to cache, triggering events`); - existingSubmissionListForProblem.push(submission); - onSubmissionsListChanged.trigger({ - contestTeamState: latestContestTeamState, - changedProblemIds: new Set([submission.problemId]), - }); -} \ No newline at end of file + outputPanelLog.trace(` New submission #${submission.id} added to cache, triggering events`); + existingSubmissionListForProblem.push(submission); + onSubmissionsListChanged.trigger({ + contestTeamState: latestContestTeamState, + changedProblemIds: new Set([submission.problemId]) + }); +} diff --git a/extension/bwcontest/src/contestMonitor/pollingService.ts b/extension/bwcontest/src/contestMonitor/pollingService.ts index 2c122d4..2638c65 100644 --- a/extension/bwcontest/src/contestMonitor/pollingService.ts +++ b/extension/bwcontest/src/contestMonitor/pollingService.ts @@ -25,9 +25,8 @@ export async function startTeamStatusPolling() { if (currentlyPolling) { outputPanelLog.trace("Tried to start team status polling, but it's already running."); return; - } - else if (!extensionContext.globalState.get('token')) { - outputPanelLog.info("Tried to start team status polling, but team is not logged in."); + } else if (!extensionContext.globalState.get('token')) { + outputPanelLog.info('Tried to start team status polling, but team is not logged in.'); return; } @@ -38,14 +37,15 @@ export async function startTeamStatusPolling() { async function startPollingWorker(cancellationToken: SimpleCancellationToken) { const pollingLoopNum = ++debugPollingLoopNum; - outputPanelLog.trace(`Starting polling loop #${pollingLoopNum}, checking contest/team status every ${pollingIntervalSeconds} seconds`); + outputPanelLog.trace( + `Starting polling loop #${pollingLoopNum}, checking contest/team status every ${pollingIntervalSeconds} seconds` + ); while (!cancellationToken.isCancelled) { try { await pollContestStatus(extensionContext); - } - catch (error) { - outputPanelLog.error("Polling contest status failed: " + (error ?? "")); + } catch (error) { + outputPanelLog.error('Polling contest status failed: ' + (error ?? '')); } await sleep(pollingIntervalSeconds * 1000); @@ -55,12 +55,16 @@ async function startPollingWorker(cancellationToken: SimpleCancellationToken) { } export function stopTeamStatusPolling() { - outputPanelLog.trace("Stopping team status polling"); + outputPanelLog.trace('Stopping team status polling'); currentPollingCancellationToken?.cancel(); - currentlyPolling = false; + currentlyPolling = false; } export function useFastPolling(enabled: boolean): void { - pollingIntervalSeconds = enabled ? developerFastPollingIntervalSeconds : defaultPollingIntervalSeconds; - outputPanelLog.info(`Changed polling interval to ${pollingIntervalSeconds} seconds. Takes effect after current delay.`); -} \ No newline at end of file + pollingIntervalSeconds = enabled + ? developerFastPollingIntervalSeconds + : defaultPollingIntervalSeconds; + outputPanelLog.info( + `Changed polling interval to ${pollingIntervalSeconds} seconds. Takes effect after current delay.` + ); +} diff --git a/extension/bwcontest/src/extension.ts b/extension/bwcontest/src/extension.ts index bb62412..f11eb17 100644 --- a/extension/bwcontest/src/extension.ts +++ b/extension/bwcontest/src/extension.ts @@ -6,7 +6,11 @@ import git from 'isomorphic-git'; import path = require('path'); import http from 'isomorphic-git/http/node'; import outputPanelLog from './outputPanelLog'; -import { startTeamStatusPollingOnActivation, stopTeamStatusPolling, useFastPolling } from './contestMonitor/pollingService'; +import { + startTeamStatusPollingOnActivation, + stopTeamStatusPolling, + useFastPolling +} from './contestMonitor/pollingService'; export interface BWContestSettings { repoBaseUrl: string; @@ -83,13 +87,14 @@ export async function cloneAndOpenRepo(contestId: number, teamId: number) { outputPanelLog.info(`Running 'git clone' to directory: ${dir}`); try { await git.clone({ fs, http, dir, url: repoUrl }); - } - catch (error) { - outputPanelLog.error("Failed to 'git clone'. The git server might be incorrectly configured. Error: " + error); + } catch (error) { + outputPanelLog.error( + "Failed to 'git clone'. The git server might be incorrectly configured. Error: " + error + ); throw error; } - outputPanelLog.info("Closing workspaces..."); + outputPanelLog.info('Closing workspaces...'); closeAllWorkspaces(); const addedFolder = vscode.workspace.updateWorkspaceFolders( @@ -107,7 +112,7 @@ export async function cloneAndOpenRepo(contestId: number, teamId: number) { } export function activate(context: vscode.ExtensionContext) { - outputPanelLog.info("BWContest Extension Activated"); + outputPanelLog.info('BWContest Extension Activated'); const sidebarProvider = new SidebarProvider( context.extensionUri, @@ -122,7 +127,7 @@ export function activate(context: vscode.ExtensionContext) { vscode.window.registerWebviewViewProvider('bwcontest-sidebar', sidebarProvider), vscode.commands.registerCommand('bwcontest.toggleFastPolling', () => { if (!extensionSettings().debugFastPolling) { - outputPanelLog.trace("Tried to toggle fast polling, but not allowed."); + outputPanelLog.trace('Tried to toggle fast polling, but not allowed.'); return; } @@ -135,7 +140,6 @@ export function activate(context: vscode.ExtensionContext) { } export function deactivate() { - outputPanelLog.info("BWContest Extension Deactivated"); + outputPanelLog.info('BWContest Extension Deactivated'); stopTeamStatusPolling(); } - diff --git a/extension/bwcontest/src/outputPanelLog.ts b/extension/bwcontest/src/outputPanelLog.ts index 853904b..35335f8 100644 --- a/extension/bwcontest/src/outputPanelLog.ts +++ b/extension/bwcontest/src/outputPanelLog.ts @@ -1,7 +1,7 @@ -import { window } from "vscode"; +import { window } from 'vscode'; /** Logs to the Output panel of a team's VS Code instance. Useful for diagnosing issues. - * + * * Do NOT output anything secret here. */ -const outputPanelLog = window.createOutputChannel('BWContest Log', {log: true}); -export default outputPanelLog; \ No newline at end of file +const outputPanelLog = window.createOutputChannel('BWContest Log', { log: true }); +export default outputPanelLog; diff --git a/extension/bwcontest/src/problemPanel.ts b/extension/bwcontest/src/problemPanel.ts index 29f95d2..559418c 100644 --- a/extension/bwcontest/src/problemPanel.ts +++ b/extension/bwcontest/src/problemPanel.ts @@ -56,7 +56,7 @@ export class BWPanel { } public static show(context: vscode.ExtensionContext, webUrl: string) { - outputPanelLog.info("Showing BWPanel"); + outputPanelLog.info('Showing BWPanel'); const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined; @@ -129,15 +129,21 @@ export class BWPanel { } try { - const submissionResult = await submitProblem(sessionToken, teamData.contestId, teamData.teamId, problemId); + const submissionResult = await submitProblem( + sessionToken, + teamData.contestId, + teamData.teamId, + problemId + ); if (submissionResult.success === true) { recordInitialSubmission(submissionResult.submission); vscode.window.showInformationMessage(`Submitted '${problem.name}'!`); } else { - vscode.window.showErrorMessage(`Error submitting '${problem.name}': ${submissionResult.message}`); + vscode.window.showErrorMessage( + `Error submitting '${problem.name}': ${submissionResult.message}` + ); } - } - catch (error) { + } catch (error) { vscode.window.showErrorMessage(`Web error submitting '${problem.name}'`); outputPanelLog.error(`Web error submitting '${problem.name}': ${error}`); } @@ -195,7 +201,7 @@ export class BWPanel { res.runResult.then(() => { this.runningProgram = undefined; this.webviewPostMessage({ msg: 'onRunningDone' }); - }) + }); } else { this.runningProgram = undefined; this.webviewPostMessage({ @@ -206,7 +212,7 @@ export class BWPanel { } } else if (teamData.language === 'CSharp') { const res = await runCSharp({ - input, + input, srcDir: join( repoDir, 'BWContest', @@ -218,13 +224,13 @@ export class BWPanel { outputBuffer.push(data); this.webviewPostMessage({ msg: 'onRunningOutput', data: outputBuffer.join('') }); } - }) + }); if (res.success === true) { killFunc = res.killFunc; res.runResult.then(() => { this.runningProgram = undefined; this.webviewPostMessage({ msg: 'onRunningDone' }); - }) + }); } else { this.runningProgram = undefined; this.webviewPostMessage({ @@ -238,18 +244,23 @@ export class BWPanel { input, cppPlatform: process.platform === 'win32' ? 'VisualStudio' : 'GCC', problemName: problem.pascalName, - srcDir: join(repoDir, 'BWContest', teamData.contestId.toString(), teamData.teamId.toString()), + srcDir: join( + repoDir, + 'BWContest', + teamData.contestId.toString(), + teamData.teamId.toString() + ), outputCallback: (data) => { outputBuffer.push(data); this.webviewPostMessage({ msg: 'onRunningOutput', data: outputBuffer.join('') }); } - }) + }); if (res.success === true) { killFunc = res.killFunc; res.runResult.then(() => { this.runningProgram = undefined; this.webviewPostMessage({ msg: 'onRunningDone' }); - }) + }); } else { this.runningProgram = undefined; this.webviewPostMessage({ diff --git a/extension/bwcontest/src/run/cpp.ts b/extension/bwcontest/src/run/cpp.ts index 0b7a4cd..1294614 100644 --- a/extension/bwcontest/src/run/cpp.ts +++ b/extension/bwcontest/src/run/cpp.ts @@ -1,6 +1,12 @@ import { join } from 'path'; import { exec, spawn } from 'child_process'; -import { timeoutSeconds, type IRunner, type IRunnerParams, type IRunnerReturn, type RunResult } from './types'; +import { + timeoutSeconds, + type IRunner, + type IRunnerParams, + type IRunnerReturn, + type RunResult +} from './types'; import kill = require('tree-kill'); import * as os from 'os'; import * as fs from 'fs-extra'; @@ -68,12 +74,12 @@ export const runCpp: IRunner = async function ( child.stdout.setEncoding('utf8'); child.stdout.on('data', (data) => { outputBuffer += data.toString(); - params.outputCallback?.(data.toString()); + params.outputCallback?.(data.toString()); }); child.stderr.setEncoding('utf8'); child.stderr.on('data', (data) => { outputBuffer += data.toString(); - params.outputCallback?.(data.toString()); + params.outputCallback?.(data.toString()); }); const runStartTime = performance.now(); diff --git a/extension/bwcontest/src/run/java.ts b/extension/bwcontest/src/run/java.ts index d0e7798..3626488 100644 --- a/extension/bwcontest/src/run/java.ts +++ b/extension/bwcontest/src/run/java.ts @@ -1,7 +1,13 @@ import { join } from 'path'; import { exec, spawn } from 'child_process'; import * as util from 'util'; -import { timeoutSeconds, type IRunner, type IRunnerParams, type IRunnerReturn, type RunResult } from './types'; +import { + timeoutSeconds, + type IRunner, + type IRunnerParams, + type IRunnerReturn, + type RunResult +} from './types'; import kill = require('tree-kill'); const execPromise = util.promisify(exec); diff --git a/extension/bwcontest/src/sharedTypes.ts b/extension/bwcontest/src/sharedTypes.ts index 8c44cc3..24a53ce 100644 --- a/extension/bwcontest/src/sharedTypes.ts +++ b/extension/bwcontest/src/sharedTypes.ts @@ -6,4 +6,4 @@ export type TeamData = { contestId: number; contestName: string; language: ContestLanguage; -}; \ No newline at end of file +}; diff --git a/extension/bwcontest/src/submit.ts b/extension/bwcontest/src/submit.ts index 779f181..226be81 100644 --- a/extension/bwcontest/src/submit.ts +++ b/extension/bwcontest/src/submit.ts @@ -12,7 +12,9 @@ export async function submitProblem( contestId: number, teamId: number, problemId: number -): Promise<{ success: true; submission: SubmissionForExtension } | { success: false; message: string }> { +): Promise< + { success: true; submission: SubmissionForExtension } | { success: false; message: string } +> { outputPanelLog.info(`Submitting problem id #{${problemId}}...`); let hash: string; @@ -30,9 +32,8 @@ export async function submitProblem( author: { name: `Team ${teamId}` }, message: `Submit problem ${problemId}` }); - } - catch (error) { - outputPanelLog.error("Fail to make commit for submission: " + JSON.stringify(error)); + } catch (error) { + outputPanelLog.error('Fail to make commit for submission: ' + JSON.stringify(error)); throw error; } diff --git a/extension/bwcontest/src/utilities/LiteEvent.ts b/extension/bwcontest/src/utilities/LiteEvent.ts index 2ef4c74..1a1df69 100644 --- a/extension/bwcontest/src/utilities/LiteEvent.ts +++ b/extension/bwcontest/src/utilities/LiteEvent.ts @@ -1,30 +1,30 @@ // Modified from JasonKleban @ https://gist.github.com/JasonKleban/50cee44960c225ac1993c922563aa540 -export { ILiteEvent, LiteEvent } +export { ILiteEvent, LiteEvent }; interface ILiteEvent { - add(handler: { (data?: T): void }): void; - remove(handler: { (data?: T): void }): void; + add(handler: { (data?: T): void }): void; + remove(handler: { (data?: T): void }): void; } class LiteEvent implements ILiteEvent { - protected handlers: { (data?: T): void; }[] = []; + protected handlers: { (data?: T): void }[] = []; - public add(handler: { (data?: T): void }): void { - this.handlers.push(handler); - } + public add(handler: { (data?: T): void }): void { + this.handlers.push(handler); + } - public remove(handler: { (data?: T): void }): boolean { - const countBefore = this.handlers.length; - this.handlers = this.handlers.filter(h => h !== handler); - return countBefore != this.handlers.length; - } + public remove(handler: { (data?: T): void }): boolean { + const countBefore = this.handlers.length; + this.handlers = this.handlers.filter((h) => h !== handler); + return countBefore != this.handlers.length; + } - public trigger(data?: T) { - this.handlers.slice(0).forEach(h => h(data)); - } + public trigger(data?: T) { + this.handlers.slice(0).forEach((h) => h(data)); + } - public expose(): ILiteEvent { - return this; - } -} \ No newline at end of file + public expose(): ILiteEvent { + return this; + } +} diff --git a/extension/bwcontest/src/utilities/SimpleCancellationToken.ts b/extension/bwcontest/src/utilities/SimpleCancellationToken.ts index 4cf0130..ba9eccb 100644 --- a/extension/bwcontest/src/utilities/SimpleCancellationToken.ts +++ b/extension/bwcontest/src/utilities/SimpleCancellationToken.ts @@ -1,8 +1,10 @@ export class SimpleCancellationToken { - private _isCancelled: boolean = false; - get isCancelled() { return this._isCancelled; } - - cancel(): void { - this._isCancelled = true; - } -} \ No newline at end of file + private _isCancelled: boolean = false; + get isCancelled() { + return this._isCancelled; + } + + cancel(): void { + this._isCancelled = true; + } +} diff --git a/extension/bwcontest/src/utilities/sleep.ts b/extension/bwcontest/src/utilities/sleep.ts index e9d53bf..8b8bcd5 100644 --- a/extension/bwcontest/src/utilities/sleep.ts +++ b/extension/bwcontest/src/utilities/sleep.ts @@ -1,3 +1,3 @@ export function sleep(ms: number): Promise { - return new Promise(resolve => setTimeout(resolve, ms)); -} \ No newline at end of file + return new Promise((resolve) => setTimeout(resolve, ms)); +} diff --git a/extension/bwcontest/webviews/components/Sidebar.svelte b/extension/bwcontest/webviews/components/Sidebar.svelte index 73276f5..7e93024 100644 --- a/extension/bwcontest/webviews/components/Sidebar.svelte +++ b/extension/bwcontest/webviews/components/Sidebar.svelte @@ -2,7 +2,11 @@ import { onMount } from 'svelte'; import SidebarProblemStatus from './SidebarProblemStatus.svelte'; import type { TeamData } from '../../src/sharedTypes'; - import type { WebviewMessageType, MessageType, SidebarTeamStatus } from '../../src/SidebarProvider'; + import type { + WebviewMessageType, + MessageType, + SidebarTeamStatus + } from '../../src/SidebarProvider'; let teamname: string; let password: string; @@ -122,7 +126,10 @@ {#if teamStatus.processingProblems.length > 0} {#each teamStatus.processingProblems as inProgressProblem (JSON.stringify(inProgressProblem))} - + {/each} {:else}
No pending submissions
@@ -131,11 +138,16 @@
Correct - {teamStatus.correctProblems.length} of {totalProblems} + {teamStatus.correctProblems.length} of {totalProblems}
{#if teamStatus.correctProblems.length > 0} {#each teamStatus.correctProblems as correctProblem (JSON.stringify(correctProblem))} - + {/each} {:else}
Solved problems appear here
@@ -144,26 +156,34 @@
Incorrect - {teamStatus.incorrectProblems.length} of {totalProblems} + {teamStatus.incorrectProblems.length} of {totalProblems}
{#if teamStatus.incorrectProblems.length > 0} {#each teamStatus.incorrectProblems as incorrectProblem (JSON.stringify(incorrectProblem))} - + {/each} {:else} -
- Attempted problems appear here until solved -
+
Attempted problems appear here until solved
{/if}
{#if teamStatus.notStartedProblems.length > 0}
Not Attempted - {teamStatus.notStartedProblems.length} of {totalProblems} + {teamStatus.notStartedProblems.length} of {totalProblems}
{#each teamStatus.notStartedProblems as notStartedProblem (JSON.stringify(notStartedProblem))} - + {/each}
{/if} diff --git a/extension/bwcontest/webviews/components/SidebarProblemStatus.svelte b/extension/bwcontest/webviews/components/SidebarProblemStatus.svelte index b4cf38f..1bfb745 100644 --- a/extension/bwcontest/webviews/components/SidebarProblemStatus.svelte +++ b/extension/bwcontest/webviews/components/SidebarProblemStatus.svelte @@ -1,32 +1,48 @@ -
-
+
+
{problem.problem.friendlyName} {problem.submissions.length} - {pluralize(problem.submissions.length, 'attempt', 'attempts')} + {pluralize(problem.submissions.length, 'attempt', 'attempts')} {#if problem.submissions.filter((s) => s.state === 'Processing').length > 0} - ({problem.submissions.filter((s) => s.state === 'Processing').length} pending...) + ({problem.submissions.filter((s) => s.state === 'Processing').length} pending...) {/if} - {#if problem.overallState === "Correct"} - @ {getContestOffsetDisplay(problem.submissions.filter(s => s.state === "Correct")[0])} + {#if problem.overallState === 'Correct'} + + @ {getContestOffsetDisplay( + problem.submissions.filter((s) => s.state === 'Correct')[0] + )} {/if} {/if}
- {#if problem.overallState !== "Correct"} + {#if problem.overallState !== 'Correct'} {#each sortedSubmissions as submission, i}
Submit #{i + 1}: {submission.state}/ + alt={submission.state} + /> {submission.state} - @ {getContestOffsetDisplay(submission)} + + @ {getContestOffsetDisplay(submission)}
{#if submission.message}
@@ -127,7 +153,7 @@ .problemHeaderSubmitCount { margin-left: 4px; - font-style: italic; + font-style: italic; } .problemHeader {