Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"version": "0.2.0",
"configurations": [
{
"name": "Run Extension",
"name": "Run Extension (Dev)",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
Expand All @@ -15,6 +15,18 @@
},
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
"preLaunchTask": "npm: compile"
},
{
"name": "Run Extension",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": ["--extensionDevelopmentPath=${workspaceRoot}"],
"env": {
"MODE": "semidevelopment"
},
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
"preLaunchTask": "npm: compile"
}
]
}
4 changes: 3 additions & 1 deletion DEVELOPERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ cd jutge-vscode
make
```

3. Open the project in VSCode and press `F5` or select `Run > Start Debugging`
3. Open the project in VSCode and press `F5` or select `Run > Start Debugging`. There are two launch configurations, `Run Extension (Dev)` and `Run Extension`, both enable special debug options, but the difference is that they point to the pre-production and production environments **of the API**.

## Project Structure

Expand Down Expand Up @@ -58,6 +58,8 @@ jutge-vscode/

4. **Runners**: Executes code against test cases for different programming languages

5. **Checkers**: Determines the correctness of a program, comparing its output to an official solution.

## Contributing

We welcome contributions from the community! Here's how you can help:
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ The extension is still in its early stages, and we are actively working on impro
- Automatic installation of compilers and interpreters to ease new users' setup.
- Enhanced problem browsing and filtering.
- Copying with more types of problems in Jutge.org (problems with functions, quiz problems, graphical problems, ...).
- Support for custom test cases.
- Support for exams and contests.

The extension is built using TypeScript and leverages the VSCode API to provide a rich user experience. It uses webviews to display problem statements and test cases, and it integrates with [Jutge.org's API](https://api.jutge.org/) to manage authentication, problem retrieval, and submission handling.
Expand Down
14 changes: 14 additions & 0 deletions resources/dark/scored.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions resources/light/scored.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 11 additions & 3 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { JutgeExamsTreeProvider } from "./providers/exam-view/provider"
import { ProblemWebviewPanel } from "./providers/problem-webview/panel"
import { WebviewPanelRegistry } from "./providers/problem-webview/panel-registry"
import { ProblemWebviewPanelSerializer } from "./providers/problem-webview/panel-serializer"
import { CourseTreeElement } from "./providers/tree-view/element"
import { CourseTreeElement } from "./providers/course-view/element"
import { jutgeClient, JutgeService } from "./services/jutge"
import { SubmissionService } from "./services/submission"
import { findCodeFilenameForProblem, showCodeDocument } from "./utils"
Expand Down Expand Up @@ -195,6 +195,14 @@ const commandShowProblem = async (problemNm: string | undefined, order: number)
}
}

// Handle non-abstract problem_nms
let language = ""
if (problemNm.includes("_")) {
let split = problemNm.replaceAll(" ", "").split("_")
problemNm = split[0]
language = split[1]
}

// Check that the problem really exists
if (!(await JutgeService.problemExists(problemNm))) {
vscode.window.showErrorMessage(`Problem ${problemNm} does not exist`)
Expand All @@ -209,7 +217,7 @@ const commandShowProblem = async (problemNm: string | undefined, order: number)
}
})

await WebviewPanelRegistry.createOrReveal(problemNm, order)
await WebviewPanelRegistry.createOrReveal(problemNm, order, language)
// Force update on "Open Existing File" button + custom testcases
await WebviewPanelRegistry.notifyProblemFilesChanges(problemNm)
}
Expand Down Expand Up @@ -270,6 +278,6 @@ export async function activate(context: vscode.ExtensionContext) {
await vscode.commands.executeCommand(
"setContext",
"jutge-vscode.isDevMode",
process.env.MODE === "development"
["development", "semidevelopment"].includes(process.env.MODE || "production")
)
}
7 changes: 5 additions & 2 deletions src/providers/course-view/element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const ELEMENT_ID_SEPARATOR = "/"
export class CourseTreeElement {
public type: CourseItemType
public key: string
public nm: string
public label: string
public description: string
public iconStatus: IconStatus
Expand Down Expand Up @@ -54,7 +55,7 @@ export class CourseTreeElement {
}
break
}
case IconStatus.PRESENTATION_ERROR: {
case (IconStatus.SCORED, IconStatus.PRESENTATION_ERROR): {
if (status === SubmissionStatus.AC) {
this.iconStatus = IconStatus.ACCEPTED
}
Expand All @@ -68,10 +69,12 @@ export class CourseTreeElement {
key: string,
label: string,
iconStatus: IconStatus,
order: number,
description?: string
) {
this.type = type
this.key = key
this.key = type === "problem" ? order + "_" + key : key
this.nm = key
this.label = label
this.iconStatus = iconStatus
this.description = description || ""
Expand Down
2 changes: 1 addition & 1 deletion src/providers/course-view/item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class CourseTreeItem extends vscode.TreeItem {
light: getIconUri("light", `${icon}.svg`),
dark: getIconUri("dark", `${icon}.svg`),
}
this.tooltip = element.key
this.tooltip = element.nm
} else if (element.type === "separator") {
this.label = "⬩"
this.description = element.label
Expand Down
18 changes: 13 additions & 5 deletions src/providers/course-view/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ export class JutgeCourseTreeProvider
item.command = {
command: "jutge-vscode.showProblem",
title: "Open Problem",
arguments: [item.element.key, item.element.order],
arguments: [item.element.nm, item.element.order],
}
}
this.problemName2TreeItem.set(element.key, item) // keep the item in a map, by problemNm (itemKey)
this.problemName2TreeItem.set(element.nm, item) // keep the item in a map, by problemNm (itemKey)
return item
}

Expand Down Expand Up @@ -70,6 +70,7 @@ export class JutgeCourseTreeProvider
key,
label,
iconStatus,
options?.order || 0,
options?.description
)
if (options?.parent !== undefined) {
Expand Down Expand Up @@ -132,7 +133,11 @@ export class JutgeCourseTreeProvider
const elems: CourseTreeElement[] = []
let order: number = 1
for (const abstractProblem of abstractProblems) {
const problemElem = this.abstractProblemToElement_(abstractProblem, allStatuses)
const problemElem = this.abstractProblemToElement_(
abstractProblem,
order,
allStatuses
)
problemElem.parent = examElement
problemElem.order = order

Expand Down Expand Up @@ -212,6 +217,7 @@ export class JutgeCourseTreeProvider

private abstractProblemToElement_(
abstractProblem: AbstractProblem,
order: number,
allStatuses: Record<string, AbstractStatus>
): CourseTreeElement {
const { problem_nm, problems } = abstractProblem
Expand All @@ -227,7 +233,9 @@ export class JutgeCourseTreeProvider

// Get status for this problem
const iconStatus = (allStatuses[problem_nm]?.status || "none") as IconStatus
return this.makeTreeElement("problem", problem_nm, problem.title, iconStatus)
return this.makeTreeElement("problem", problem_nm, problem.title, iconStatus, {
order: order,
})
}

private async getProblemsFromListNm_(
Expand Down Expand Up @@ -263,7 +271,7 @@ export class JutgeCourseTreeProvider
sepIndex++
} else {
const problem_nm = problemOrSeparator
item = this.abstractProblemToElement_(problem_nm, allStatuses)
item = this.abstractProblemToElement_(problem_nm, order, allStatuses)
item.order = order
order++
}
Expand Down
28 changes: 22 additions & 6 deletions src/providers/problem-webview/html.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Testcase } from "@/jutge_api_client"
import { checkerInfoByName } from "@/services/runners/checkers"
import { CustomTestcase, ProblemHandler } from "@/types"
import { Button as htmlButton } from "@/webview/components/button"
import { chevronDown, icons, warningIcon } from "@/webview/components/icons"
import { chevronDown, icons, warningCard, warningIcon } from "@/webview/components/icons"
import { makeSpacesVisible } from "@/webview/utils"
import { Uri } from "vscode"

Expand Down Expand Up @@ -131,12 +132,17 @@ export function htmlForGraphicTestcase(testcase: Testcase, index: number): strin
}

export function htmlForCustomTestcase(customTestcase: CustomTestcase) {
const { index, input: text } = customTestcase
const { index, input: text, solution: text_solution } = customTestcase
const input = {
actual: text,
displayed: makeSpacesVisible(text),
}

const solution = {
actual: text_solution || "",
displayed: makeSpacesVisible(text_solution || ""),
}

return /*html*/ `
<div class="testcase custom" id="custom-testcase-${index}" data-type="custom-std">
${htmlTestcaseMetadata(
Expand All @@ -146,6 +152,10 @@ export function htmlForCustomTestcase(customTestcase: CustomTestcase) {
<div class="icon">${icons["edit"]()}</div>
<span>Edit</span>
</div>
<div id="edit-solution-testcase-${index}" class="small-button" title="Edit solution">
<div class="icon">${icons["edit"]()}</div>
<span>Edit sol</span>
</div>
<div id="run-custom-testcase-${index}" class="small-button" title="Run this testcase only">
<div class="icon">${icons["run"]()}</div>
<span>Run</span>
Expand All @@ -154,6 +164,8 @@ export function htmlForCustomTestcase(customTestcase: CustomTestcase) {
<div class="content">
<div class="two-column">
${htmlTestcaseRawTextContainer("Input", input, { copyToClipboard: true })}
${text_solution ? htmlTestcaseRawTextContainer("Expected Output:", solution, { copyToClipboard: true }) : ""}

<div class="container received">
<div class="title">Received Output:</div>
<div id="received" class="selectable textarea"><pre></pre></div>
Expand All @@ -171,15 +183,18 @@ export function htmlNotSupportedHandler() {
<h2 class="flex-grow-1">Testcases</h2>
</div>
<div class="panels">
<div class="warning">
${warningIcon()}
<span>Local testcase running is not supported for this problem.</span>
</div>
${warningCard("Local testcase running is not supported for this problem.")}
</div>
</div>
`
}

export function htmlNotSupportedChecker() {
return warningCard(
"Local testcase checking is not fully supported for this problem, the <i>expected output</i> may not be the only valid solution."
)
}

export function htmlTestcaseHeader(title: string) {
return `
<h2>
Expand Down Expand Up @@ -212,6 +227,7 @@ export function htmlTestcases(
</div>
</div>
<div class="panels">
${!checkerInfoByName(problemHandler?.checker).implemented ? htmlNotSupportedChecker() : ""}
${testcases.map(htmlForTestcase).join("") || "No testcases found."}
</div>
`
Expand Down
13 changes: 9 additions & 4 deletions src/providers/problem-webview/panel-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as vscode from "vscode"
import { getContext, getWebviewOptions } from "@/extension"
import { StaticLogger } from "@/loggers"
import { JutgeService } from "@/services/jutge"
import { VSCodeToWebviewMessage } from "@/types"
import { LanguageCode, VSCodeToWebviewMessage } from "@/types"
import { getProblemIdFromFilename, sourceFileExists } from "@/utils"
import { basename } from "path"
import { ProblemWebviewPanel } from "./panel"
Expand Down Expand Up @@ -32,7 +32,8 @@ export class WebviewPanelRegistry extends StaticLogger {
*/
static async createOrReveal(
problemNm: string,
order: number = -1
order: number = -1,
langId?: string
): Promise<ProblemWebviewPanel | null> {
this.log.debug(`Attempting to show panel for problem ${problemNm}`)

Expand All @@ -55,15 +56,19 @@ export class WebviewPanelRegistry extends StaticLogger {
return panel
}

this.log.debug(`Creating new panel for ${problemNm}`)
this.log.debug(`Creating new panel for ${problemNm} - l: ${langId}`)
const webviewPanel = vscode.window.createWebviewPanel(
ProblemWebviewPanel.viewType,
problemNm,
{ viewColumn, preserveFocus: true },
getWebviewOptions(context.extensionUri)
)

const panel = new ProblemWebviewPanel(webviewPanel, { problemNm, order })
const panel = new ProblemWebviewPanel(
webviewPanel,
{ problemNm, order },
langId as LanguageCode
)
this.createdPanels_.set(problemNm, panel)

return panel
Expand Down
Loading