feat: detect language of submitted file

This commit is contained in:
Alexander Klee 2024-05-16 12:45:36 +02:00
parent d991bef7ab
commit de8e4282f5
6 changed files with 99 additions and 38 deletions

View File

@ -6,9 +6,10 @@ It isn't a full replacement for the web interface yet, but already matches or su
- Simple, one-click submissions and feedback (no need for File Explorer or reloading!)
- View problems and problem statements without leaving your IDE
- Unobtrusive and integrated
- Detects the language of submitted files
## Potential future features
- Support detecting the language of files (don't just force C++)
- Display how much time is left in a contest or whether it's already over
- Implement reading detailed results
- Show scoreboards
- Maybe even download test cases and scaffold sources

View File

@ -1,6 +1,8 @@
package io.gitlab.jfronny.sdom
import com.intellij.ide.util.PropertiesComponent
import com.intellij.notification.NotificationGroupManager
import com.intellij.notification.NotificationType
import com.intellij.openapi.actionSystem.*
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.LogLevel
@ -8,6 +10,8 @@ import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import io.gitlab.jfronny.sdom.actions.SDGetContestsAction
import io.gitlab.jfronny.sdom.actions.SDGetProblemsAction
import io.gitlab.jfronny.sdom.actions.SDProblemSelectionNotificationAction
import io.gitlab.jfronny.sdom.actions.SDSubmitAction
import io.gitlab.jfronny.sdom.model.*
import io.ktor.client.*
import io.ktor.client.call.*
@ -23,7 +27,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.serialization.json.Json
import java.io.ByteArrayOutputStream
import java.util.Base64
import java.util.*
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
@ -127,6 +131,7 @@ object SDom {
var problems: List<Problem> = listOf()
var currentProblem: Problem? = null
var judgementTypes: Map<String, SDJudgementType>? = null
private var languages: Map<String, SDLanguage>? = null
suspend fun getContests() {
val result = authenticatedClient.get(SDCredentials.url) {
@ -146,22 +151,13 @@ object SDom {
val contestId = propertiesComponent?.getValue(CONTEST_ID_PROPERTY, "")?.takeIf { it != "" }
val contest = contests.find { it.id == contestId }
if (contestId != null && contest != null) {
currentContest = contest
SDGetProblemsAction().actionPerformed(
AnActionEvent(
null,
DataContext.EMPTY_CONTEXT,
ActionPlaces.UNKNOWN,
Presentation(),
ActionManager.getInstance(),
0
)
)
CoroutineScope(Job() + Dispatchers.IO).launch {
selectContest(contest, project)
}
}
}
suspend fun getProblems() {
suspend fun fetchProblems() {
val contest = currentContest ?: return
val result = authenticatedClient.get(SDCredentials.url) {
url {
@ -175,13 +171,50 @@ object SDom {
currentProblem = null
}
}
judgementTypes = authenticatedClient.get(SDCredentials.url) {
url {
appendPathSegments("contests", contest.id, "judgement-types")
}
}
suspend fun selectContest(contest: Contest, context: Project?) {
currentContest = contest
SDGetProblemsAction().actionPerformed(
AnActionEvent(
null,
DataContext.EMPTY_CONTEXT,
ActionPlaces.UNKNOWN,
Presentation(),
ActionManager.getInstance(),
0
)
)
try {
judgementTypes = authenticatedClient.get(SDCredentials.url) {
url {
appendPathSegments("contests", contest.id, "judgement-types")
}
}
.body<List<SDJudgementType>>()
.associateBy { it.id }
languages = authenticatedClient.get(SDCredentials.url) {
url {
appendPathSegments("contests", contest.id, "languages")
}
contentType(ContentType.Application.Json)
}
.body<List<SDLanguage>>()
.flatMap { l -> l.extensions.map { it to l } }
.toMap()
} catch (e: Exception) {
NotificationGroupManager.getInstance()
.getNotificationGroup("sdom.notifications")
.createNotification("You have to select a problem", NotificationType.ERROR)
.setTitle("No problem selected")
.addAction(SDProblemSelectionNotificationAction("Select Problem"))
.addAction(SDSubmitAction("Retry"))
.notify(context)
return
}
.body<List<SDJudgementType>>()
.associateBy { it.id }
}
suspend fun downloadProblemStatement(contest: Contest, problem: Problem): ByteArray? {
@ -195,10 +228,15 @@ object SDom {
return response
}
fun identifyLanguage(extension: String): SDLanguage? {
return languages?.get(extension)
}
suspend fun submitSolution(
contest: Contest, problem: Problem, solution: String, fileName: String
contest: Contest, problem: Problem, solution: String, fileName: String, language: SDLanguage
): SharedFlow<Result<SDJudgement>> = coroutineScope {
val resultFlow: MutableSharedFlow<Result<SDJudgement>> = MutableSharedFlow()
launch {
try {
val response: SDSubmission = authenticatedClient.post(SDCredentials.url) {
@ -210,8 +248,8 @@ object SDom {
SDAddSubmission(
problem = problem.id,
problemId = problem.id,
language = "cpp",
languageId = "cpp",
language = language.id,
languageId = language.id,
entryPoint = null,
files = listOf(SDAddSubmissionFile(zip(fileName, solution)))
)

View File

@ -12,6 +12,10 @@ import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.openapi.util.Condition
import io.gitlab.jfronny.sdom.SDom
import io.gitlab.jfronny.sdom.model.Contest
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import java.awt.Dimension
import javax.swing.JComponent
@ -70,17 +74,9 @@ class SDContestSelectionComboBoxAction : ComboBoxAction(), DumbAware {
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
override fun actionPerformed(e: AnActionEvent) {
SDom.currentContest = contest
SDGetProblemsAction().actionPerformed(
AnActionEvent(
null,
DataContext.EMPTY_CONTEXT,
ActionPlaces.UNKNOWN,
Presentation(),
ActionManager.getInstance(),
0
)
)
CoroutineScope(Job() + Dispatchers.IO).launch {
SDom.selectContest(contest, e.project)
}
}
}
}

View File

@ -14,7 +14,7 @@ class SDGetProblemsAction : DumbAwareAction() {
override fun actionPerformed(e: AnActionEvent) {
CoroutineScope(Job() + Dispatchers.IO).launch {
SDom.getProblems()
SDom.fetchProblems()
}
}
}

View File

@ -1,6 +1,5 @@
package io.gitlab.jfronny.sdom.actions
import com.intellij.icons.AllIcons
import com.intellij.notification.Notification
import com.intellij.notification.NotificationAction
import com.intellij.notification.NotificationGroupManager
@ -40,6 +39,16 @@ class SDSubmitAction(text: String) : NotificationAction(text) {
}
val currentFile = virtualFile.readText()
val fileName = virtualFile.name
val language = virtualFile.extension?.let { SDom.identifyLanguage(it) }
if (language == null) {
NotificationGroupManager.getInstance()
.getNotificationGroup("sdom.notifications")
.createNotification("Could not identify the language of that file", NotificationType.ERROR)
.setTitle("Unknown file extension")
.notify(e.project)
return
}
val contest = SDom.currentContest
if (contest == null) {
@ -64,7 +73,7 @@ class SDSubmitAction(text: String) : NotificationAction(text) {
return
}
CoroutineScope(Job() + Dispatchers.IO).launch {
val result = SDom.submitSolution(contest, problem, currentFile, fileName)
val result = SDom.submitSolution(contest, problem, currentFile, fileName, language)
println(result.first())
}
}

View File

@ -0,0 +1,17 @@
package io.gitlab.jfronny.sdom.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class SDLanguage(
@SerialName("compile_executable_hash") val compileExecutableHash: String?,
val id: String,
val name: String,
val extensions: List<String>,
@SerialName("filter_compiler_files") val filterCompilerFiles: Boolean,
@SerialName("allow_judge") val allowJudge: Boolean,
@SerialName("time_factor") val timeFactor: Double,
@SerialName("entry_point_required") val entryPointRequired: Boolean,
@SerialName("entry_point_name") val entryPointName: String?,
)