-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #40 from DanielSchiavini/master
External compiler annotator
- Loading branch information
Showing
24 changed files
with
354 additions
and
209 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
123 changes: 77 additions & 46 deletions
123
src/main/kotlin/org/vyperlang/plugin/annotators/CompilerAnnotator.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,61 +1,92 @@ | ||
package org.vyperlang.plugin.annotators | ||
|
||
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer | ||
import com.intellij.lang.annotation.AnnotationHolder | ||
import com.intellij.lang.annotation.Annotator | ||
import com.intellij.lang.annotation.ExternalAnnotator | ||
import com.intellij.lang.annotation.HighlightSeverity | ||
import com.intellij.openapi.application.ApplicationManager | ||
import com.intellij.openapi.editor.Editor | ||
import com.intellij.openapi.progress.ProgressIndicator | ||
import com.intellij.openapi.project.DumbAware | ||
import com.intellij.psi.PsiFile | ||
import org.vyperlang.plugin.docker.StatusDocker | ||
import org.vyperlang.plugin.docker.VyperCompilerDocker | ||
import com.intellij.openapi.diagnostic.Logger | ||
import com.intellij.openapi.project.Project | ||
import com.intellij.openapi.util.TextRange | ||
import com.intellij.psi.PsiDocumentManager | ||
import com.intellij.psi.PsiElement | ||
import com.intellij.psi.PsiManager | ||
import org.vyperlang.plugin.compile.VyperCompiler | ||
import org.vyperlang.plugin.psi.VyperFile | ||
import java.beans.PropertyChangeEvent | ||
import java.beans.PropertyChangeListener | ||
|
||
class VyperCompilerListener(val project: Project) : PropertyChangeListener { | ||
override fun propertyChange(evt: PropertyChangeEvent?) { | ||
val data = evt!!.newValue as VyperCompiler.CompilerMessage | ||
|
||
ApplicationManager.getApplication().runReadAction { | ||
|
||
val psiFile = PsiManager.getInstance(project).findFile(data.file) | ||
//what if user picks another file? | ||
val document = PsiDocumentManager.getInstance(project).getDocument(psiFile!!) | ||
for (report in data.error) { | ||
val start = document!!.getLineStartOffset(report.line - 1) | ||
val end = document.getLineEndOffset(report.line - 1) | ||
val message = report.msg | ||
CompilerOutput.messages.add(CompilerMessage(TextRange(start, end), message)) | ||
} | ||
DaemonCodeAnalyzer.getInstance(project).restart() | ||
} | ||
} | ||
import com.intellij.openapi.vfs.VirtualFile | ||
import org.vyperlang.plugin.docker.CompilerMissingError | ||
|
||
data class FileInfo(val project: Project, val file: VirtualFile, val indicator: ProgressIndicator? = null) | ||
|
||
fun listenAnalysis() { | ||
VyperCompiler.addListener(this) | ||
} | ||
} | ||
val VYPER_ERROR_REGEX = listOf( | ||
// ErrorType: error message\n | ||
"(\\w+): ([^\\n]+)\\n+", | ||
// (hint: optional)\n | ||
"( +\\(hint: [^)]+\\)\\n+)?", | ||
// contract "x", function "y", line 1:1 | ||
" +(?:contract \"[^\"]+\", )?(?:function \"[^\"]+\", )?line (\\d+):(\\d+)" | ||
).joinToString("").toRegex(RegexOption.MULTILINE) | ||
|
||
val LOG: Logger = Logger.getInstance(CompilerAnnotator::class.java) | ||
|
||
/** | ||
* Annotator that listens to the compiler output and annotates the file accordingly | ||
* Annotator that calls the compiler and annotates the file accordingly. | ||
* By using the ExternalAnnotator, the compiler is only called when the file is saved and all background processes are finished. | ||
*/ | ||
class CompilerAnnotator : Annotator { | ||
override fun annotate(element: PsiElement, holder: AnnotationHolder) { | ||
if (element is VyperFile) { | ||
for (message in CompilerOutput.messages) { | ||
holder.newAnnotation(HighlightSeverity.ERROR, message.message) | ||
} | ||
CompilerOutput.messages = mutableListOf() | ||
class CompilerAnnotator : ExternalAnnotator<FileInfo, List<CompilerError>>(), DumbAware { | ||
|
||
/** 1st step of the external annotator: Collect information needed to run the compiler. */ | ||
override fun collectInformation(file: PsiFile) = FileInfo(file.project, file.virtualFile) | ||
|
||
/** 1st step of the external annotator: Collect information needed to run the compiler. */ | ||
override fun collectInformation(file: PsiFile, editor: Editor, hasErrors: Boolean) = collectInformation(file) | ||
|
||
/** 2nd step of the external annotator: Run the compiler and return the result. */ | ||
override fun doAnnotate(info: FileInfo?): List<CompilerError> { | ||
val result = try { | ||
VyperCompilerDocker(info!!.project, info.file, info.indicator).run() | ||
} catch (e: CompilerMissingError) { | ||
LOG.error("Error while running compiler annotator", e) | ||
null | ||
} | ||
if (result?.statusDocker == StatusDocker.FAILED) { | ||
return parseErrors(result.stderr); | ||
} | ||
return emptyList() | ||
} | ||
} | ||
|
||
object CompilerOutput { | ||
var messages: MutableList<CompilerMessage> = mutableListOf() | ||
/** 3rd step of the external annotator: Apply the annotations to the file. */ | ||
override fun apply(file: PsiFile, annotationResult: List<CompilerError>, holder: AnnotationHolder) { | ||
annotationResult.forEach { | ||
val offset = file.text.lines().take(it.line - 1).sumOf { it.length + 1 } + it.column | ||
val element = file.findReferenceAt(file.textOffset + offset)?.element | ||
?: file.findElementAt(offset) | ||
?: file.findElementAt(offset - 1) | ||
?: file.findElementAt(offset - 2) | ||
?: file | ||
holder.newAnnotation(HighlightSeverity.ERROR, it.message) | ||
.range(element.textRange) | ||
.tooltip("${it.message} (${if (it.hint.isNullOrBlank()) it.errorType else it.hint})") | ||
.create() | ||
} | ||
} | ||
|
||
/** Parse the compiler stderr, return list of errors. */ | ||
private fun parseErrors(stderr: String): List<CompilerError> { | ||
val messages = VYPER_ERROR_REGEX.findAll(stderr).map { | ||
val (errorType, message, hint, line, column) = it.destructured | ||
CompilerError(errorType, message.trim(), hint, line.toInt(), column.toInt()) | ||
}.toList() | ||
if (messages.isEmpty()) { | ||
LOG.warn("No error messages found in compiler output: $stderr") | ||
} | ||
return messages | ||
} | ||
} | ||
|
||
data class CompilerMessage(val range: TextRange, val message: String) | ||
/** Data class to store compiler error information. */ | ||
data class CompilerError( | ||
val errorType: String, | ||
val message: String, | ||
val hint: String?, | ||
val line: Int, | ||
val column: Int | ||
) |
Oops, something went wrong.