Commit f139db7a authored by Mathieu's avatar Mathieu
Browse files

[GUI] Implements compile errors displaying in edtior

parent b25a1ead
......@@ -601,7 +601,7 @@ def guiClientDir = guiDir / "client"
lazy val clientGUI = OsgiProject(guiClientDir, "org.openmole.gui.client.core") enablePlugins (ExecNpmPlugin) dependsOn
(sharedGUI, clientToolGUI, market, dataGUI, extClientTool) settings(
libraryDependencies += Libraries.async,
npmDeps in Compile += Dep("ace-builds", "1.2.9", List("mode-scala.js", "theme-github.js", "ext-language_tools.js"), true),
npmDeps in Compile += Dep("ace-builds", "1.2.9", List("mode-scala.js", "theme-github.js", "ext-language_tools.js", "range.js"), true),
npmDeps in Compile += Dep("sortablejs", "1.7.0", List("Sortable.min.js")),
npmDeps in Compile += Dep("plotly.js", "1.31.0", List("plotly-basic.min.js"))
) settings (defaultSettings: _*)
......
......@@ -32,7 +32,9 @@ import org.openmole.gui.ext.data.{ Error ⇒ ExecError }
import org.openmole.gui.ext.data._
import org.openmole.gui.client.core.alert.BannerAlert
import org.openmole.gui.client.core.alert.BannerAlert.BannerMessage
import org.openmole.gui.client.core.files.TreeNodeTabs
import org.openmole.gui.ext.api.Api
import org.openmole.gui.ext.data.ExecutionInfo.Failed
import org.openmole.gui.ext.tool.client.Utils
import org.scalajs.dom.raw.{ HTMLElement, HTMLSpanElement }
import rx._
......@@ -168,6 +170,12 @@ class ExecutionPanel {
val (details, execStatus) = info match {
case f: ExecutionInfo.Failed
panels.treeNodeTabs.find(staticInfo.now(execID).path).foreach { tab
f.error match {
case ce: CompilationError tab.editor.foreach { _.setErrors(ce.errors) }
case _
}
}
addToBanner(execID, BannerAlert.div(failedDiv(execID)).critical)
(ExecutionDetails("0", 0, Some(f.error), f.environmentStates), info.state)
case f: ExecutionInfo.Finished
......
......@@ -7,7 +7,8 @@ import org.openmole.gui.ext.data.FileExtension._
import scala.scalajs.js
import scalatags.JsDom.all._
import scalatags.JsDom.tags
import scalatags.JsDom.{ TypedTag, tags }
import scala.async.Async.{ async, await }
import scaladget.ace._
import scaladget.bootstrapnative.bsn._
......@@ -15,7 +16,10 @@ import scaladget.tools._
import scala.scalajs.js.JSConverters._
import org.openmole.gui.ext.tool.client._
import js.Dynamic.{ literal lit }
import org.scalajs.dom.raw.{ Element, Event, HTMLDivElement, HTMLElement }
import scaladget.bootstrapnative.Popup
import scaladget.bootstrapnative.Popup.{ ClickPopup, Manual, PopupPosition }
import rx._
/*
* Copyright (C) 07/04/15 // mathieu.leclaire@openmole.org
......@@ -38,29 +42,89 @@ class EditorPanelUI(initCode: String, fileType: FileExtension, containerModifier
def save(onsave: () Unit) = {}
val editorDiv = tags.div(id := "editor")
val editor = ace.edit(editorDiv.render)
val editorDiv = tags.div(id := "editor").render
val editor = ace.edit(editorDiv)
val view = {
lazy val view = {
div(editorContainer +++ container +++ containerModifierSeq)(
div(panelClass +++ panelDefault)(
div(panelBody)(
editor.container
editor.container,
errorDiv
)
)
)
}
def sess = editor.getSession()
val errors: Var[Seq[ErrorWithLocation]] = Var(Seq())
def session = editor.getSession()
def aceDoc = sess.getDocument()
def aceDoc = session.getDocument()
def code: String = sess.getValue()
def code: String = session.getValue()
def setCode(content: String) = editor.getSession().setValue(content)
def setReadOnly(b: Boolean) = editor.setReadOnly(b)
val nbLines: Var[(Int, Int)] = Var((editor.getFirstVisibleRow.toInt, editor.getLastVisibleRow.toInt))
session.on("change", (x) {
nbLines() = (editor.getFirstVisibleRow.toInt, editor.getLastVisibleRow.toInt)
})
session.on("changeScrollTop", x {
Popover.current.now.foreach { p
Popover.toggle(p)
}
nbLines() = (editor.renderer.getScrollTopRow.toInt, editor.renderer.getScrollBottomRow.toInt)
})
def buildManualPopover(i: Int, title: String, position: PopupPosition) = {
lazy val pop1 = div(i)(`class` := "gutterError").popover(
title,
position,
Manual
)
lazy val pop1Render = pop1.render
pop1Render.onclick = { (e: Event)
if (Popover.current.now == pop1) Popover.hide
else {
Popover.current.now.foreach { p
Popover.toggle(p)
}
Popover.toggle(pop1)
}
e.stopPropagation
}
pop1Render
}
lazy val errorDiv: TypedTag[HTMLDivElement] = div(`class` := "gutterDecoration")(
Rx {
val range = (nbLines()._1 until nbLines()._2)
val topMargin = if (session.getScrollTop() > 0) marginTop := -8 else marginTop := 0
div(topMargin)(
for (
r range
) yield {
errors().find(e e.line == Some(r)).map { e
e.line.map { l
buildManualPopover(l, e.stackTrace, Popup.Left)
}.getOrElse(div(height := 15, opacity := 0).render)
}.getOrElse(div(height := 15, opacity := 0).render)
}
)
}
)
def setErrors(errorsWithLocation: Seq[ErrorWithLocation]) = {
nbLines() = (editor.getFirstVisibleRow.toInt, editor.getLastVisibleRow.toInt)
errors() = errorsWithLocation
}
def initEditor = {
fileType match {
case ef: HighlightedFile editor.getSession().setMode("ace/mode/" + ef.highlighter)
......
......@@ -59,6 +59,8 @@ sealed trait TreeNodeTab {
// Get the file content to be saved
def content: String
def editor: Option[EditorPanelUI]
def editable: Boolean
def editing: Boolean
......@@ -87,18 +89,19 @@ object TreeNodeTab {
lazy val safePathTab = Var(safePath)
val editor = EditorPanelUI(FileExtension.OMS, initialContent)
editor.initEditor
lazy val omsEditor = EditorPanelUI(FileExtension.OMS, initialContent)
def editor = Some(omsEditor)
omsEditor.initEditor
def editable = true
def editing = true
def content = editor.code
def content = omsEditor.code
def refresh(onsaved: () Unit) = save(safePathTab.now, editor, onsaved)
def refresh(onsaved: () Unit) = save(safePathTab.now, omsEditor, onsaved)
def resizeEditor = editor.editor.resize()
def resizeEditor = omsEditor.editor.resize()
lazy val controlElement = button("Run", btn_primary, onclick := { ()
refresh(()
......@@ -107,7 +110,7 @@ object TreeNodeTab {
})
})
lazy val block = editor.view
lazy val block = omsEditor.view
}
def html(safePath: SafePath, htmlContent: String) = new TreeNodeTab {
......@@ -115,6 +118,8 @@ object TreeNodeTab {
def content: String = htmlContent
def editor = None
def editable: Boolean = false
def editing: Boolean = false
......@@ -173,10 +178,10 @@ object TreeNodeTab {
lazy val isEditing = Var(initialEditing)
Rx {
editor.setReadOnly(!isEditing())
editableEditor.setReadOnly(!isEditing())
}
def content: String = editor.code
def content: String = editableEditor.code
val sequence = Var(initialSequence)
val nbColumns = sequence.now.header.length
......@@ -189,8 +194,9 @@ object TreeNodeTab {
case _ sequence.now.content
}
lazy val editor = EditorPanelUI(extension, initialContent, if (isCSV) paddingBottom := 80 else emptyMod)
editor.initEditor
lazy val editableEditor = EditorPanelUI(extension, initialContent, if (isCSV) paddingBottom := 80 else emptyMod)
def editor = Some(editableEditor)
editableEditor.initEditor
def editable = true
......@@ -201,7 +207,7 @@ object TreeNodeTab {
safePathTab.now,
(p: ProcessState) {},
(cont: String) {
editor.setCode(cont)
editableEditor.setCode(cont)
if (isCSV) {
post()[Api].sequence(safePathTab.now).call().foreach { seq
sequence() = seq
......@@ -214,7 +220,7 @@ object TreeNodeTab {
}
def refresh(afterRefresh: () Unit): Unit = {
def saveTab = TreeNodeTab.save(safePathTab.now, editor, afterRefresh)
def saveTab = TreeNodeTab.save(safePathTab.now, editableEditor, afterRefresh)
if (editing) {
if (isCSV) {
......@@ -227,7 +233,7 @@ object TreeNodeTab {
download(afterRefresh)
}
def resizeEditor = editor.editor.resize()
def resizeEditor = editableEditor.editor.resize()
lazy val controlElement: TypedTag[HTMLElement] =
div(
......@@ -242,7 +248,7 @@ object TreeNodeTab {
}
)
lazy val editorView = editor.view
lazy val editorView = editableEditor.view
val switchString = view match {
case Table Raw.toString
......@@ -430,6 +436,11 @@ class TreeNodeTabs() {
}
def switchEditableTo(tab: TreeNodeTab, sequence: SequenceData, editableView: EditableView, filter: RowFilter, editing: Boolean, axis: (Int, Int), plotMode: PlotMode) = {
val newTab = TreeNodeTab.editable(tab.safePathTab.now, tab.content, sequence, editableView, editing, filter, axis, plotMode)
switchTab(tab, newTab)
}
def switchTab(tab: TreeNodeTab, to: TreeNodeTab) = {
val index = {
val i = tabs.now.indexOf(tab)
if (i == -1) tabs.now.size
......@@ -437,11 +448,9 @@ class TreeNodeTabs() {
}
removeTab(tab)
tabs() = tabs.now.take(index) ++ Seq(to) ++ tabs.now.takeRight(tabs.now.size - index)
val newTab = TreeNodeTab.editable(tab.safePathTab.now, tab.content, sequence, editableView, editing, filter, axis, plotMode)
tabs() = tabs.now.take(index) ++ Seq(newTab) ++ tabs.now.takeRight(tabs.now.size - index)
setActive(newTab)
setActive(to)
}
def alterables: Seq[AlterableFileContent] = tabs.now.filter {
......
......@@ -270,23 +270,32 @@ case class TreeNodeData(
case class ScriptData(scriptPath: SafePath)
object Error {
def empty = Error("")
def empty = MessageError("")
}
case class Error(stackTrace: String) {
def +(error: Error) = Error(stackTrace + error.stackTrace)
case class ErrorWithLocation(stackTrace: String, line: Option[Int] = None, start: Option[Int], end: Option[Int] )
sealed trait Error {
def stackTrace: String
}
case class MessageError(stackTrace: String) extends Error {
def +(error: MessageError) = MessageError(stackTrace + error.stackTrace)
}
case class CompilationError(errors: Seq[ErrorWithLocation]) extends Error{
def stackTrace = ""
}
case class Token(token: String, duration: Long)
object ErrorBuilder {
def apply(t: Throwable): Error = {
def apply(t: Throwable): MessageError = {
val sw = new StringWriter()
t.printStackTrace(new PrintWriter(sw))
Error(sw.toString)
MessageError(sw.toString)
}
def apply(stackTrace: String) = Error(stackTrace)
def apply(stackTrace: String) = MessageError(stackTrace)
}
sealed trait ID {
......
package org.openmole.gui.plugin.authentication.egi
import org.openmole.gui.ext.data
import org.openmole.gui.ext.data.{ AuthenticationData, Error, Test }
import org.openmole.gui.ext.data.{ AuthenticationData, Error, MessageError, Test }
/*
* Copyright (C) 12/01/17 // mathieu.leclaire@openmole.org
......@@ -40,7 +40,7 @@ object EGIAuthenticationTest {
): Test = {
val all = Seq(password, proxy, dirac)
if (all.exists { t t == Test.pending }) Test.pending
else if (all.exists { t t.errorStack != Error.empty }) Test.error("failed", Error(s"${password.errorStack.stackTrace} \n\n ${proxy.errorStack.stackTrace} \n\n ${dirac.errorStack.stackTrace}"))
else if (all.exists { t t.errorStack != Error.empty }) Test.error("failed", MessageError(s"${password.errorStack.stackTrace} \n\n ${proxy.errorStack.stackTrace} \n\n ${dirac.errorStack.stackTrace}"))
else Test.passed(message)
}
}
\ No newline at end of file
......@@ -73,14 +73,14 @@ class EGIAuthenticationAPIImpl(s: Services) extends EGIAuthenticationAPI {
case Success(_) Test.passed()
case Failure(f) Test.error("Invalid Password", ErrorBuilder(f))
}
}.getOrElse(Test.error("Unknown error", Error("Unknown " + data.name)))
}.getOrElse(Test.error("Unknown error", MessageError("Unknown " + data.name)))
def test(data: EGIAuthenticationData, voName: String, test: (EGIAuthentication, String) Try[Boolean]): Test = coreObject(data).map { d
test(d, voName) match {
case Success(_) Test.passed(voName)
case Failure(f) Test.error("Invalid Password", ErrorBuilder(f))
}
}.getOrElse(Test.error("Unknown error", Error("Unknown " + data.name)))
}.getOrElse(Test.error("Unknown error", MessageError("Unknown " + data.name)))
val vos = services.preference(EGIAuthenticationAPIImpl.voTest)
......
......@@ -518,4 +518,39 @@ padding-bottom: 90px;
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/*
.markerDecoration {
position:absolute;
background:rgba(255,0,0,0.5);
z-index:20;
}
.gutterDecoration{
background:rgba(255,0,0,0.5);
color:white;
font-weight: bold;
}
*/
.gutterDecoration{
position:absolute;
z-index: 20;
font: 15px/normal 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;;
top:0;
width: 20px;
margin-left:20px;
text-align:center;
}
.gutterError {
background: rgba(255,128,128);
height: 15px;
cursor: pointer;
border-radius: 3px;
color: white;
font-weight: bold;
margin-left: -3px;
width: 25px;
cursor: pointer;
}
\ No newline at end of file
......@@ -13,6 +13,7 @@ import java.io._
import java.nio.file._
import au.com.bytecode.opencsv.CSVReader
import org.openmole.core.console.ScalaREPL
import org.openmole.core.market.{ MarketIndex, MarketIndexEntry }
import scala.util.{ Failure, Success, Try }
......@@ -317,9 +318,18 @@ class ApiImpl(s: Services, applicationControl: ApplicationControl) extends Api {
val compilationFuture =
threadProvider.submit(ThreadProvider.maxPriority) { ()
def error(t: Throwable): Unit = execution.addError(execId, Failed(Vector.empty, ErrorBuilder(t), Seq()))
def error(t: Throwable): Unit = {
t match {
case ce: ScalaREPL.CompilationError execution.addError(execId, Failed(Vector.empty, CompilationError(ce.errorMessages.map { em
val ewl = ErrorWithLocation(em.rawMessage, em.position.map { _.line }, em.position.map { _.start }, em.position.map { _.end })
println("EWL " + ewl)
ewl
}), Seq()))
case _ execution.addError(execId, Failed(Vector.empty, ErrorBuilder(t), Seq()))
}
}
def message(message: String): Unit = execution.addError(execId, Failed(Vector.empty, Error(message), Seq()))
def message(message: String): Unit = execution.addError(execId, Failed(Vector.empty, MessageError(message), Seq()))
try {
val project = Project(script.getParentFileSafe)
......@@ -440,6 +450,7 @@ class ApiImpl(s: Services, applicationControl: ApplicationControl) extends Api {
module.pluginDirectory.listFilesSafe.map(p Plugin(p.getName, new SimpleDateFormat("dd/MM/yyyy HH:mm").format(p.lastModified)))
def removePlugin(plugin: Plugin): Unit = org.openmole.gui.ext.tool.server.Utils.removePlugin(plugin)
//GUI OM PLUGINS
def getGUIPlugins(): AllPluginExtensionData = {
......
......@@ -196,7 +196,7 @@ class Execution {
info.environment.errors.map { ex
ex.exception match {
case fje: environment.FailedJobExecution EnvironmentError(environmentId, fje.message, ErrorBuilder(fje.cause) + Error(s"\nDETAILS:\n${fje.detail}"), ex.creationTime, Utils.javaLevelToErrorLevel(ex.level))
case fje: environment.FailedJobExecution EnvironmentError(environmentId, fje.message, ErrorBuilder(fje.cause) + MessageError(s"\nDETAILS:\n${fje.detail}"), ex.creationTime, Utils.javaLevelToErrorLevel(ex.level))
case _ EnvironmentError(environmentId, ex.exception.getMessage, ErrorBuilder(ex.exception), ex.creationTime, Utils.javaLevelToErrorLevel(ex.level))
}
}
......
......@@ -19,7 +19,7 @@ object Libraries {
lazy val boopickleVersion = "1.2.6"
lazy val scalaAutowireVersion = "0.2.6"
lazy val sourcecodeVersion = "0.1.3"
lazy val scaladgetVersion = "1.0.4"
lazy val scaladgetVersion = "1.1.0-SNAPSHOT"
lazy val sortableVersion = "0.2.1"
lazy val json4sVersion = "3.5.0"
lazy val circeVersion = "0.9.1"
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment