From 031942cdc56459d0614f2e249f681f8ff8d969c7 Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Tue, 13 Apr 2021 15:57:08 +0300 Subject: [PATCH 01/13] feat: visualization failed notification --- .../protocol-language-server.md | 55 ++++++++++++++----- .../binary/BinaryConnectionController.scala | 9 +-- .../binary/factory/ErrorFactory.scala | 11 ---- .../json/JsonConnectionController.scala | 17 ++++++ .../protocol/json/JsonRpc.scala | 1 + .../runtime/ContextEventsListener.scala | 14 ++++- .../runtime/ContextRegistryProtocol.scala | 10 +++- .../languageserver/runtime/ExecutionApi.scala | 20 ++++++- .../org/enso/polyglot/runtime/Runtime.scala | 4 ++ .../job/ProgramExecutionSupport.scala | 9 ++- 10 files changed, 111 insertions(+), 39 deletions(-) diff --git a/docs/language-server/protocol-language-server.md b/docs/language-server/protocol-language-server.md index f6ce89bc8c07..bb7b83d57440 100644 --- a/docs/language-server/protocol-language-server.md +++ b/docs/language-server/protocol-language-server.md @@ -119,6 +119,7 @@ transport formats, please look [here](./protocol-architecture). - [`executionContext/detachVisualisation`](#executioncontextdetachvisualisation) - [`executionContext/modifyVisualisation`](#executioncontextmodifyvisualisation) - [`executionContext/visualisationUpdate`](#executioncontextvisualisationupdate) + - [`executionContext/visualisationEvaluationFailed`](#executioncontextvisualisationevaluationfailed) - [Search Operations](#search-operations) - [Suggestions Database Example](#suggestions-database-example) - [`search/getSuggestionsDatabase`](#searchgetsuggestionsdatabase) @@ -151,7 +152,6 @@ transport formats, please look [here](./protocol-architecture). - [`ModuleNotFoundError`](#modulenotfounderror) - [`VisualisationNotFoundError`](#visualisationnotfounderror) - [`VisualisationExpressionError`](#visualisationexpressionerror) - - [`VisualisationEvaluationError`](#visualisationevaluationerror) - [`FileNotOpenedError`](#filenotopenederror) - [`TextEditValidationError`](#texteditvalidationerror) - [`InvalidVersionError`](#invalidversionerror) @@ -1284,6 +1284,7 @@ destroying the context. - [`executionContext/modifyVisualisation`](#executioncontextmodifyvisualisation) - [`executionContext/detachVisualisation`](#executioncontextdetachvisualisation) - [`executionContext/visualisationUpdate`](#executioncontextvisualisationupdate) +- [`executionContext/visualisationEvaluationFailed`](#executioncontextvisualisationevaluationfailed) #### Disables @@ -2912,6 +2913,46 @@ root_type VisualisationUpdate; N/A +### `executionContext/visualisationEvaluationFailed` + +Signals that an evaluation of a visualisation expression on the computed value +has failed. + +- **Type:** Notification +- **Direction:** Server -> Client +- **Connection:** Protocol +- **Visibility:** Public + +#### Parameters + +```typescript +interface VisualisationEvaluationFailed { + /** + * An execution context identifier. + */ + contextId: ContextId; + + /** + * A visualisation identifier. + */ + visualisationId: UUID; + + /** + * An identifier of a visualised expression. + */ + expressionId: UUID; + + /** + * An error message. + */ + message: string; +} +``` + +#### Errors + +N/A + ## Search Operations Search operations allow requesting for the autocomplete suggestions and search @@ -3612,18 +3653,6 @@ cannot be evaluated. } ``` -### `VisualisationEvaluationError` - -It is a push message. It signals that an evaluation of a code responsible for -generating visualisation data failed. - -```typescript -"error" : { - "code" : 2008, - "message" : "Evaluation of the visualisation failed [cannot execute foo]" -} -``` - ### `FileNotOpenedError` Signals that a file wasn't opened. diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/protocol/binary/BinaryConnectionController.scala b/engine/language-server/src/main/scala/org/enso/languageserver/protocol/binary/BinaryConnectionController.scala index 2eb4b2d85a41..5aab235913c4 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/protocol/binary/BinaryConnectionController.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/protocol/binary/BinaryConnectionController.scala @@ -31,10 +31,7 @@ import org.enso.languageserver.requesthandler.file.{ ReadBinaryFileHandler, WriteBinaryFileHandler } -import org.enso.languageserver.runtime.ContextRegistryProtocol.{ - VisualisationEvaluationFailed, - VisualisationUpdate -} +import org.enso.languageserver.runtime.ContextRegistryProtocol.VisualisationUpdate import org.enso.languageserver.session.BinarySession import org.enso.languageserver.util.UnhandledLogging import org.enso.languageserver.util.binary.DecodingFailure @@ -121,10 +118,6 @@ class BinaryConnectionController( case update: VisualisationUpdate => val updatePacket = convertVisualisationUpdateToOutPacket(update) outboundChannel ! updatePacket - - case VisualisationEvaluationFailed(_, msg) => - val errorPacket = ErrorFactory.createVisualisationEvaluationError(msg) - outboundChannel ! errorPacket } private def connectionEndHandler( diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/protocol/binary/factory/ErrorFactory.scala b/engine/language-server/src/main/scala/org/enso/languageserver/protocol/binary/factory/ErrorFactory.scala index 9df5e22b628f..0438f3c5ba47 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/protocol/binary/factory/ErrorFactory.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/protocol/binary/factory/ErrorFactory.scala @@ -32,17 +32,6 @@ object ErrorFactory { ): ByteBuffer = createGenericError(0, "Service error", maybeCorrelationId) - /** Creates a visualisation expression error as a binary packet. - * - * @param msg an error message - * @return an FlatBuffer representation of the created error - */ - def createVisualisationEvaluationError(msg: String): ByteBuffer = - createGenericError( - 2008, - s"Evaluation of the visualisation failed [$msg]" - ) - /** Creates a generic error inside a [[FlatBufferBuilder]]. * * @param code an error code diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionController.scala b/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionController.scala index 9580d7d7ce6c..9bece901e1f9 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionController.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionController.scala @@ -222,6 +222,22 @@ class JsonConnectionController( ExecutionContextExecutionStatus.Params(contextId, diagnostics) ) + case ContextRegistryProtocol.VisualisationEvaluationFailed( + contextId, + visualisationId, + expressionId, + message + ) => + webActor ! Notification( + VisualisationEvaluationFailed, + VisualisationEvaluationFailed.Params( + contextId, + visualisationId, + expressionId, + message + ) + ) + case SearchProtocol.SuggestionsDatabaseUpdateNotification( version, updates @@ -230,6 +246,7 @@ class JsonConnectionController( SearchApi.SuggestionsDatabaseUpdates, SearchApi.SuggestionsDatabaseUpdates.Params(updates, version) ) + case InputOutputProtocol.OutputAppended(output, outputKind) => outputKind match { case StandardOutput => diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonRpc.scala b/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonRpc.scala index 38ed795184b3..3ef78769fbcb 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonRpc.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonRpc.scala @@ -72,5 +72,6 @@ object JsonRpc { .registerNotification(StandardErrorAppended) .registerNotification(WaitingForStandardInput) .registerNotification(SuggestionsDatabaseUpdates) + .registerNotification(VisualisationEvaluationFailed) } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextEventsListener.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextEventsListener.scala index 8bafaeccc913..459ef4112e11 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextEventsListener.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextEventsListener.scala @@ -98,9 +98,19 @@ final class ContextEventsListener( ) sessionRouter ! DeliverToJsonController(rpcSession.clientId, payload) - case Api.VisualisationEvaluationFailed(`contextId`, msg) => + case Api.VisualisationEvaluationFailed( + `contextId`, + visualisationId, + expressionId, + msg + ) => val payload = - ContextRegistryProtocol.VisualisationEvaluationFailed(contextId, msg) + ContextRegistryProtocol.VisualisationEvaluationFailed( + contextId, + visualisationId, + expressionId, + msg + ) sessionRouter ! DeliverToJsonController(rpcSession.clientId, payload) case RunExpressionUpdates if expressionUpdates.nonEmpty => diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala index 5675159e9fa6..4f9e4a5a5c67 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala @@ -396,8 +396,14 @@ object ContextRegistryProtocol { * visualisation data failed. * * @param contextId a context identifier + * @param visualisationId a visualisation identifier + * @param expressionId an identifier of a visualised expression * @param message the reason of the failure */ - case class VisualisationEvaluationFailed(contextId: UUID, message: String) - + case class VisualisationEvaluationFailed( + contextId: UUID, + visualisationId: UUID, + expressionId: UUID, + message: String + ) } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ExecutionApi.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ExecutionApi.scala index 1a585c3fc358..10eb6c0fda56 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ExecutionApi.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ExecutionApi.scala @@ -13,8 +13,9 @@ import org.enso.languageserver.runtime.ContextRegistryProtocol.ExecutionDiagnost */ object ExecutionApi { - type ContextId = UUID - type ExpressionId = UUID + type ContextId = UUID + type ExpressionId = UUID + type VisualisationId = UUID case object ExecutionContextCreate extends Method("executionContext/create") { @@ -125,6 +126,21 @@ object ExecutionApi { } } + case object VisualisationEvaluationFailed + extends Method("executionContext/visualisationEvaluationFailed") { + + case class Params( + contextId: ContextId, + visualisationId: VisualisationId, + expressionId: ExpressionId, + message: String + ) + + implicit val hasParams = new HasParams[this.type] { + type Params = VisualisationEvaluationFailed.Params + } + } + case object StackItemNotFoundError extends Error(2001, "Stack item not found") case object ContextNotFoundError extends Error(2002, "Context not found") diff --git a/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala b/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala index ad3461ca9219..ec26b4ae17ff 100644 --- a/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala +++ b/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala @@ -898,10 +898,14 @@ object Runtime { * visualisation data failed. * * @param contextId the context's id. + * @param visualisationId the visualisation identifier + * @param expressionId the identifier of a visualised expression * @param message the reason of the failure */ case class VisualisationEvaluationFailed( contextId: ContextId, + visualisationId: VisualisationId, + expressionId: ExpressionId, message: String ) extends ApiNotification diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala b/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala index 8a7bab64da8b..0b28bccfba9d 100644 --- a/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala +++ b/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala @@ -436,7 +436,14 @@ trait ProgramExecutionSupport { errorMsgOrVisualisationData match { case Left(msg) => ctx.endpoint.sendToClient( - Api.Response(Api.VisualisationEvaluationFailed(contextId, msg)) + Api.Response( + Api.VisualisationEvaluationFailed( + contextId, + visualisation.id, + expressionId, + msg + ) + ) ) case Right(data) => From 29ef47c905b64a9e11c94763e351ba986d10e171 Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Tue, 13 Apr 2021 17:53:31 +0300 Subject: [PATCH 02/13] test: language-server --- .../runtime/ContextEventsListenerSpec.scala | 18 +- .../websocket/json/ContextRegistryTest.scala | 317 ++++++++++++++++++ .../json/ExecutionContextJsonMessages.scala | 107 ++++++ 3 files changed, 439 insertions(+), 3 deletions(-) diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/runtime/ContextEventsListenerSpec.scala b/engine/language-server/src/test/scala/org/enso/languageserver/runtime/ContextEventsListenerSpec.scala index e703a2ae5cf0..d40e5c7d6dae 100644 --- a/engine/language-server/src/test/scala/org/enso/languageserver/runtime/ContextEventsListenerSpec.scala +++ b/engine/language-server/src/test/scala/org/enso/languageserver/runtime/ContextEventsListenerSpec.scala @@ -344,13 +344,25 @@ class ContextEventsListenerSpec "send visualisation evaluation failed notification" taggedAs Retry in withDb { (clientId, contextId, _, router, listener) => - val message = "Test visualisation evaluation failed" - listener ! Api.VisualisationEvaluationFailed(contextId, message) + val message = "Test visualisation evaluation failed" + val visualisationId = UUID.randomUUID() + val expressionId = UUID.randomUUID() + listener ! Api.VisualisationEvaluationFailed( + contextId, + visualisationId, + expressionId, + message + ) router.expectMsg( DeliverToJsonController( clientId, - VisualisationEvaluationFailed(contextId, message) + VisualisationEvaluationFailed( + contextId, + visualisationId, + expressionId, + message + ) ) ) } diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/ContextRegistryTest.scala b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/ContextRegistryTest.scala index a366c17c29a3..82334e6aaa0a 100644 --- a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/ContextRegistryTest.scala +++ b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/ContextRegistryTest.scala @@ -3,6 +3,7 @@ package org.enso.languageserver.websocket.json import java.util.UUID import io.circe.literal._ +import org.enso.languageserver.runtime.VisualisationConfiguration import org.enso.languageserver.websocket.json.{ ExecutionContextJsonMessages => json } @@ -451,6 +452,322 @@ class ContextRegistryTest extends BaseServerTest { ) client.expectJson(json.ok(3)) } + + "successfully attach visualisation" in { + val client = getInitialisedWsClient() + + // create context + client.send(json.executionContextCreateRequest(1)) + val (requestId, contextId) = + runtimeConnectorProbe.receiveN(1).head match { + case Api.Request(requestId, Api.CreateContextRequest(contextId)) => + (requestId, contextId) + case msg => + fail(s"Unexpected message: $msg") + } + runtimeConnectorProbe.lastSender ! Api.Response( + requestId, + Api.CreateContextResponse(contextId) + ) + client.expectJson(json.executionContextCreateResponse(1, contextId)) + + // attach visualisation + val visualisationId = UUID.randomUUID() + val expressionId = UUID.randomUUID() + val config = + VisualisationConfiguration(contextId, "Test.Main", ".to_json.to_text") + client.send( + json.executionContextAttachVisualisationRequest( + 2, + visualisationId, + expressionId, + config + ) + ) + val requestId2 = + runtimeConnectorProbe.receiveN(1).head match { + case Api.Request( + requestId, + Api.AttachVisualisation( + `visualisationId`, + `expressionId`, + _ + ) + ) => + requestId + case msg => + fail(s"Unexpected message: $msg") + } + runtimeConnectorProbe.lastSender ! Api.Response( + requestId2, + Api.VisualisationAttached() + ) + client.expectJson(json.ok(2)) + } + + "return ModuleNotFound error when attaching visualisation" in { + val client = getInitialisedWsClient() + + // create context + client.send(json.executionContextCreateRequest(1)) + val (requestId, contextId) = + runtimeConnectorProbe.receiveN(1).head match { + case Api.Request(requestId, Api.CreateContextRequest(contextId)) => + (requestId, contextId) + case msg => + fail(s"Unexpected message: $msg") + } + runtimeConnectorProbe.lastSender ! Api.Response( + requestId, + Api.CreateContextResponse(contextId) + ) + client.expectJson(json.executionContextCreateResponse(1, contextId)) + + // attach visualisation + val visualisationId = UUID.randomUUID() + val expressionId = UUID.randomUUID() + val config = + VisualisationConfiguration(contextId, "Test.Main", ".to_json.to_text") + client.send( + json.executionContextAttachVisualisationRequest( + 2, + visualisationId, + expressionId, + config + ) + ) + val requestId2 = + runtimeConnectorProbe.receiveN(1).head match { + case Api.Request( + requestId, + Api.AttachVisualisation( + `visualisationId`, + `expressionId`, + _ + ) + ) => + requestId + case msg => + fail(s"Unexpected message: $msg") + } + runtimeConnectorProbe.lastSender ! Api.Response( + requestId2, + Api.ModuleNotFound(config.visualisationModule) + ) + client.expectJson( + json.executionContextModuleNotFound( + 2, + config.visualisationModule + ) + ) + } + + "return VisualisationExpressionFailed error when attaching visualisation" in { + val client = getInitialisedWsClient() + + // create context + client.send(json.executionContextCreateRequest(1)) + val (requestId, contextId) = + runtimeConnectorProbe.receiveN(1).head match { + case Api.Request(requestId, Api.CreateContextRequest(contextId)) => + (requestId, contextId) + case msg => + fail(s"Unexpected message: $msg") + } + runtimeConnectorProbe.lastSender ! Api.Response( + requestId, + Api.CreateContextResponse(contextId) + ) + client.expectJson(json.executionContextCreateResponse(1, contextId)) + + // attach visualisation + val visualisationId = UUID.randomUUID() + val expressionId = UUID.randomUUID() + val config = + VisualisationConfiguration(contextId, "Test.Main", ".to_json.to_text") + val expressionFailureMessage = "Method `to_json` could not be found." + client.send( + json.executionContextAttachVisualisationRequest( + 2, + visualisationId, + expressionId, + config + ) + ) + val requestId2 = + runtimeConnectorProbe.receiveN(1).head match { + case Api.Request( + requestId, + Api.AttachVisualisation( + `visualisationId`, + `expressionId`, + _ + ) + ) => + requestId + case msg => + fail(s"Unexpected message: $msg") + } + runtimeConnectorProbe.lastSender ! Api.Response( + requestId2, + Api.VisualisationExpressionFailed(expressionFailureMessage) + ) + client.expectJson( + json.executionContextVisualisationExpressionFailed( + 2, + expressionFailureMessage + ) + ) + } + + "successfully detach visualisation" in { + val client = getInitialisedWsClient() + + // create context + client.send(json.executionContextCreateRequest(1)) + val (requestId, contextId) = + runtimeConnectorProbe.receiveN(1).head match { + case Api.Request(requestId, Api.CreateContextRequest(contextId)) => + (requestId, contextId) + case msg => + fail(s"Unexpected message: $msg") + } + runtimeConnectorProbe.lastSender ! Api.Response( + requestId, + Api.CreateContextResponse(contextId) + ) + client.expectJson(json.executionContextCreateResponse(1, contextId)) + + // detach visualisation + val visualisationId = UUID.randomUUID() + val expressionId = UUID.randomUUID() + client.send( + json.executionContextDetachVisualisationRequest( + 2, + contextId, + visualisationId, + expressionId + ) + ) + val requestId2 = + runtimeConnectorProbe.receiveN(1).head match { + case Api.Request( + requestId, + Api.DetachVisualisation( + `contextId`, + `visualisationId`, + `expressionId` + ) + ) => + requestId + case msg => + fail(s"Unexpected message: $msg") + } + runtimeConnectorProbe.lastSender ! Api.Response( + requestId2, + Api.VisualisationDetached() + ) + client.expectJson(json.ok(2)) + } + + "successfully modify visualisation" in { + val client = getInitialisedWsClient() + + // create context + client.send(json.executionContextCreateRequest(1)) + val (requestId, contextId) = + runtimeConnectorProbe.receiveN(1).head match { + case Api.Request(requestId, Api.CreateContextRequest(contextId)) => + (requestId, contextId) + case msg => + fail(s"Unexpected message: $msg") + } + runtimeConnectorProbe.lastSender ! Api.Response( + requestId, + Api.CreateContextResponse(contextId) + ) + client.expectJson(json.executionContextCreateResponse(1, contextId)) + + // modify visualisation + val visualisationId = UUID.randomUUID() + val config = + VisualisationConfiguration(contextId, "Test.Main", ".to_json.to_text") + client.send( + json.executionContextModifyVisualisationRequest( + 2, + visualisationId, + config + ) + ) + val requestId2 = + runtimeConnectorProbe.receiveN(1).head match { + case Api.Request( + requestId, + Api.ModifyVisualisation( + `visualisationId`, + _ + ) + ) => + requestId + case msg => + fail(s"Unexpected message: $msg") + } + runtimeConnectorProbe.lastSender ! Api.Response( + requestId2, + Api.VisualisationModified() + ) + client.expectJson(json.ok(2)) + } + + "return VisualisationNotFound error when modifying visualisation" in { + val client = getInitialisedWsClient() + + // create context + client.send(json.executionContextCreateRequest(1)) + val (requestId, contextId) = + runtimeConnectorProbe.receiveN(1).head match { + case Api.Request(requestId, Api.CreateContextRequest(contextId)) => + (requestId, contextId) + case msg => + fail(s"Unexpected message: $msg") + } + runtimeConnectorProbe.lastSender ! Api.Response( + requestId, + Api.CreateContextResponse(contextId) + ) + client.expectJson(json.executionContextCreateResponse(1, contextId)) + + // modify visualisation + val visualisationId = UUID.randomUUID() + val config = + VisualisationConfiguration(contextId, "Test.Main", ".to_json.to_text") + client.send( + json.executionContextModifyVisualisationRequest( + 2, + visualisationId, + config + ) + ) + val requestId2 = + runtimeConnectorProbe.receiveN(1).head match { + case Api.Request( + requestId, + Api.ModifyVisualisation( + `visualisationId`, + _ + ) + ) => + requestId + case msg => + fail(s"Unexpected message: $msg") + } + runtimeConnectorProbe.lastSender ! Api.Response( + requestId2, + Api.VisualisationNotFound() + ) + client.expectJson(json.executionContextVisualisationNotFound(2)) + } + } } diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/ExecutionContextJsonMessages.scala b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/ExecutionContextJsonMessages.scala index a33ed3dfd686..76e9518825d5 100644 --- a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/ExecutionContextJsonMessages.scala +++ b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/ExecutionContextJsonMessages.scala @@ -2,6 +2,7 @@ package org.enso.languageserver.websocket.json import org.enso.polyglot.runtime.Runtime.Api import io.circe.literal._ +import org.enso.languageserver.runtime.VisualisationConfiguration object ExecutionContextJsonMessages { @@ -91,4 +92,110 @@ object ExecutionContextJsonMessages { } } """ + + def executionContextAttachVisualisationRequest( + reqId: Int, + visualisationId: Api.VisualisationId, + expressionId: Api.ExpressionId, + configuration: VisualisationConfiguration + ) = + json""" + { "jsonrpc": "2.0", + "method": "executionContext/attachVisualisation", + "id": $reqId, + "params": { + "visualisationId": $visualisationId, + "expressionId": $expressionId, + "visualisationConfig": { + "executionContextId": ${configuration.executionContextId}, + "visualisationModule": ${configuration.visualisationModule}, + "expression": ${configuration.expression} + } + } + } + """ + + def executionContextModuleNotFound( + reqId: Int, + module: String + ) = { + val errorMessage = + s"Module not found [$module]" + json""" + { "jsonrpc": "2.0", + "id": $reqId, + "error": { + "code": 2005, + "message": $errorMessage + } + } + """ + } + + def executionContextVisualisationNotFound(reqId: Int) = + json""" + { "jsonrpc": "2.0", + "id": $reqId, + "error": { + "code": 2006, + "message": "Visualisation not found" + } + } + """ + + def executionContextVisualisationExpressionFailed( + reqId: Int, + message: String + ) = { + val errorMessage = + s"Evaluation of the visualisation expression failed [$message]" + json""" + { "jsonrpc": "2.0", + "id": $reqId, + "error": { + "code": 2007, + "message": $errorMessage + } + } + """ + } + + def executionContextDetachVisualisationRequest( + reqId: Int, + contextId: Api.ContextId, + visualisationId: Api.VisualisationId, + expressionId: Api.ExpressionId + ) = + json""" + { "jsonrpc": "2.0", + "method": "executionContext/detachVisualisation", + "id": $reqId, + "params": { + "contextId": $contextId, + "visualisationId": $visualisationId, + "expressionId": $expressionId + } + } + """ + + def executionContextModifyVisualisationRequest( + reqId: Int, + visualisationId: Api.VisualisationId, + configuration: VisualisationConfiguration + ) = + json""" + { "jsonrpc": "2.0", + "method": "executionContext/modifyVisualisation", + "id": $reqId, + "params": { + "visualisationId": $visualisationId, + "visualisationConfig": { + "executionContextId": ${configuration.executionContextId}, + "visualisationModule": ${configuration.visualisationModule}, + "expression": ${configuration.expression} + } + } + } + """ + } From 1d3033deed10107a773073aea735a3c1a2657323 Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Wed, 14 Apr 2021 11:55:55 +0300 Subject: [PATCH 03/13] test: engine --- .../test/instrument/RuntimeServerTest.scala | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala b/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala index 9bf11dc6f9d2..f1549bc1278e 100644 --- a/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala +++ b/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala @@ -3761,6 +3761,75 @@ class RuntimeServerTest dataAfterModification.sameElements("7".getBytes) shouldBe true } + it should "return visualisation evaluation errors" in { + val idMain = context.Main.metadata.addItem(78, 1) + val contents = context.Main.code + val mainFile = context.writeMain(context.Main.code) + val moduleName = "Test.Main" + + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + val visualisationId = UUID.randomUUID() + + // create context + context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) + context.receive shouldEqual Some( + Api.Response(requestId, Api.CreateContextResponse(contextId)) + ) + + // Open the new file + context.send( + Api.Request(Api.OpenFileNotification(mainFile, contents, true)) + ) + context.receiveNone shouldEqual None + + // push main + val item1 = Api.StackItem.ExplicitCall( + Api.MethodPointer(moduleName, "Test.Main", "main"), + None, + Vector() + ) + context.send( + Api.Request(requestId, Api.PushContextRequest(contextId, item1)) + ) + context.receive(6) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + context.Main.Update.mainX(contextId), + context.Main.Update.mainY(contextId), + context.Main.Update.mainZ(contextId), + TestMessages.update(contextId, idMain, Constants.INTEGER), + context.executionComplete(contextId) + ) + + // attach visualisation + context.send( + Api.Request( + requestId, + Api.AttachVisualisation( + visualisationId, + idMain, + Api.VisualisationConfiguration( + contextId, + moduleName, + "x -> x.visualise_me" + ) + ) + ) + ) + context.receive(3) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.VisualisationAttached()), + Api.Response( + Api.VisualisationEvaluationFailed( + contextId, + visualisationId, + idMain, + "Method `visualise_me` of 50 (Integer) could not be found." + ) + ), + context.executionComplete(contextId) + ) + } + it should "rename a project" in { val contents = context.Main.code val mainFile = context.writeMain(contents) From 30fe31b14cb817d707f3bfec11b93628406221a6 Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Wed, 14 Apr 2021 15:33:42 +0300 Subject: [PATCH 04/13] feat: visualization diagnostic --- .../protocol-language-server.md | 5 + .../json/JsonConnectionController.scala | 6 +- .../runtime/ContextEventsListener.scala | 8 +- .../runtime/ContextRegistryProtocol.scala | 4 +- .../languageserver/runtime/ExecutionApi.scala | 3 +- .../runtime/ContextEventsListenerSpec.scala | 6 +- .../org/enso/polyglot/runtime/Runtime.scala | 3 +- .../service/error/VisualisationException.java | 14 ++ .../job/ProgramExecutionSupport.scala | 15 +- .../test/instrument/RuntimeServerTest.scala | 196 +++++++++++++++++- 10 files changed, 244 insertions(+), 16 deletions(-) create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/service/error/VisualisationException.java diff --git a/docs/language-server/protocol-language-server.md b/docs/language-server/protocol-language-server.md index bb7b83d57440..f4b6a5a04d8b 100644 --- a/docs/language-server/protocol-language-server.md +++ b/docs/language-server/protocol-language-server.md @@ -2946,6 +2946,11 @@ interface VisualisationEvaluationFailed { * An error message. */ message: string; + + /** + * Detailed information about the error. + */ + diagnostic?: Diagnostic; } ``` diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionController.scala b/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionController.scala index 9bece901e1f9..1752cdab83e3 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionController.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionController.scala @@ -226,7 +226,8 @@ class JsonConnectionController( contextId, visualisationId, expressionId, - message + message, + diagnostic ) => webActor ! Notification( VisualisationEvaluationFailed, @@ -234,7 +235,8 @@ class JsonConnectionController( contextId, visualisationId, expressionId, - message + message, + diagnostic ) ) diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextEventsListener.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextEventsListener.scala index 459ef4112e11..a7ec263b0b2d 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextEventsListener.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextEventsListener.scala @@ -102,14 +102,18 @@ final class ContextEventsListener( `contextId`, visualisationId, expressionId, - msg + message, + diagnostic ) => val payload = ContextRegistryProtocol.VisualisationEvaluationFailed( contextId, visualisationId, expressionId, - msg + message, + diagnostic.collect { case m: Api.ExecutionResult.Diagnostic => + toProtocolDiagnostic(m) + } ) sessionRouter ! DeliverToJsonController(rpcSession.clientId, payload) diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala index 4f9e4a5a5c67..07c5f523ded7 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala @@ -399,11 +399,13 @@ object ContextRegistryProtocol { * @param visualisationId a visualisation identifier * @param expressionId an identifier of a visualised expression * @param message the reason of the failure + * @param diagnostic detailed information about the error */ case class VisualisationEvaluationFailed( contextId: UUID, visualisationId: UUID, expressionId: UUID, - message: String + message: String, + diagnostic: Option[ExecutionDiagnostic] ) } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ExecutionApi.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ExecutionApi.scala index 10eb6c0fda56..eaaf78b322c6 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ExecutionApi.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ExecutionApi.scala @@ -133,7 +133,8 @@ object ExecutionApi { contextId: ContextId, visualisationId: VisualisationId, expressionId: ExpressionId, - message: String + message: String, + diagnostic: Option[ExecutionDiagnostic] ) implicit val hasParams = new HasParams[this.type] { diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/runtime/ContextEventsListenerSpec.scala b/engine/language-server/src/test/scala/org/enso/languageserver/runtime/ContextEventsListenerSpec.scala index d40e5c7d6dae..56dac6f52c86 100644 --- a/engine/language-server/src/test/scala/org/enso/languageserver/runtime/ContextEventsListenerSpec.scala +++ b/engine/language-server/src/test/scala/org/enso/languageserver/runtime/ContextEventsListenerSpec.scala @@ -351,7 +351,8 @@ class ContextEventsListenerSpec contextId, visualisationId, expressionId, - message + message, + None ) router.expectMsg( @@ -361,7 +362,8 @@ class ContextEventsListenerSpec contextId, visualisationId, expressionId, - message + message, + None ) ) ) diff --git a/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala b/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala index ec26b4ae17ff..e30b55b1f51e 100644 --- a/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala +++ b/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala @@ -906,7 +906,8 @@ object Runtime { contextId: ContextId, visualisationId: VisualisationId, expressionId: ExpressionId, - message: String + message: String, + diagnostic: Option[ExecutionResult] ) extends ApiNotification /** Signals that visualisation cannot be found. diff --git a/engine/runtime/src/main/java/org/enso/interpreter/service/error/VisualisationException.java b/engine/runtime/src/main/java/org/enso/interpreter/service/error/VisualisationException.java new file mode 100644 index 000000000000..a716f18d8be3 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/service/error/VisualisationException.java @@ -0,0 +1,14 @@ +package org.enso.interpreter.service.error; + +/** Thrown when the execution of the visualisation expression fails. */ +public class VisualisationException extends RuntimeException implements ServiceException { + + /** + * Create new instance of this error. + * + * @param message the error message. + */ + public VisualisationException(String message) { + super(message); + } +} diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala b/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala index 0b28bccfba9d..77448110a4d1 100644 --- a/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala +++ b/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala @@ -35,7 +35,8 @@ import org.enso.interpreter.service.error.{ ConstructorNotFoundException, MethodNotFoundException, ModuleNotFoundForExpressionIdException, - ServiceException + ServiceException, + VisualisationException } import org.enso.polyglot.LanguageInfo import org.enso.polyglot.runtime.Runtime.Api @@ -421,7 +422,6 @@ trait ProgramExecutionSupport { expressionValue ) } - .leftMap(_.getMessage) .flatMap { case text: String => Right(text.getBytes("UTF-8")) @@ -430,18 +430,23 @@ trait ProgramExecutionSupport { case bytes: Array[Byte] => Right(bytes) case other => - Left(s"Cannot encode ${other.getClass} to byte array") + Left( + new VisualisationException( + s"Cannot encode ${other.getClass} to byte array" + ) + ) } errorMsgOrVisualisationData match { - case Left(msg) => + case Left(error) => ctx.endpoint.sendToClient( Api.Response( Api.VisualisationEvaluationFailed( contextId, visualisation.id, expressionId, - msg + error.getMessage, + getExecutionOutcome(error) ) ) ) diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala b/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala index f1549bc1278e..66ce4c651524 100644 --- a/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala +++ b/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala @@ -3761,7 +3761,67 @@ class RuntimeServerTest dataAfterModification.sameElements("7".getBytes) shouldBe true } - it should "return visualisation evaluation errors" in { + it should "return ModuleNotFound error when attaching visualisation" in { + val idMain = context.Main.metadata.addItem(78, 1) + val contents = context.Main.code + val mainFile = context.writeMain(context.Main.code) + val moduleName = "Test.Main" + + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + val visualisationId = UUID.randomUUID() + + // create context + context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) + context.receive shouldEqual Some( + Api.Response(requestId, Api.CreateContextResponse(contextId)) + ) + + // Open the new file + context.send( + Api.Request(Api.OpenFileNotification(mainFile, contents, true)) + ) + context.receiveNone shouldEqual None + + // push main + val item1 = Api.StackItem.ExplicitCall( + Api.MethodPointer(moduleName, "Test.Main", "main"), + None, + Vector() + ) + context.send( + Api.Request(requestId, Api.PushContextRequest(contextId, item1)) + ) + context.receive(6) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + context.Main.Update.mainX(contextId), + context.Main.Update.mainY(contextId), + context.Main.Update.mainZ(contextId), + TestMessages.update(contextId, idMain, Constants.INTEGER), + context.executionComplete(contextId) + ) + + // attach visualisation + context.send( + Api.Request( + requestId, + Api.AttachVisualisation( + visualisationId, + idMain, + Api.VisualisationConfiguration( + contextId, + "Test.Undefined", + "x -> x" + ) + ) + ) + ) + context.receive(1) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.ModuleNotFound("Test.Undefined")) + ) + } + + it should "return visualisation evaluation errors with diagnostic info" in { val idMain = context.Main.metadata.addItem(78, 1) val contents = context.Main.code val mainFile = context.writeMain(context.Main.code) @@ -3823,7 +3883,139 @@ class RuntimeServerTest contextId, visualisationId, idMain, - "Method `visualise_me` of 50 (Integer) could not be found." + "Method `visualise_me` of 50 (Integer) could not be found.", + Some( + Api.ExecutionResult.Diagnostic.error( + "Method `visualise_me` of 50 (Integer) could not be found.", + None, + Some(model.Range(model.Position(0, 5), model.Position(0, 19))), + None, + Vector( + Api.StackTraceElement( + "", + None, + Some( + model.Range(model.Position(0, 5), model.Position(0, 19)) + ), + None + ) + ) + ) + ) + ) + ), + context.executionComplete(contextId) + ) + } + + it should "return visualisation error with a stack trace" in { + val idMain = context.Main.metadata.addItem(78, 1) + val contents = context.Main.code + val mainFile = context.writeMain(context.Main.code) + val moduleName = "Test.Main" + val visualisationCode = + """ + |encode x = x.visualise_me + | + |inc_and_encode x = here.encode x+1 + |""".stripMargin + + val visualisationFile = + context.writeInSrcDir("Visualisation", visualisationCode) + + context.send( + Api.Request( + Api.OpenFileNotification( + visualisationFile, + visualisationCode, + true + ) + ) + ) + + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + val visualisationId = UUID.randomUUID() + + // create context + context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) + context.receive shouldEqual Some( + Api.Response(requestId, Api.CreateContextResponse(contextId)) + ) + + // Open the new file + context.send( + Api.Request(Api.OpenFileNotification(mainFile, contents, true)) + ) + context.receiveNone shouldEqual None + + // push main + val item1 = Api.StackItem.ExplicitCall( + Api.MethodPointer(moduleName, "Test.Main", "main"), + None, + Vector() + ) + context.send( + Api.Request(requestId, Api.PushContextRequest(contextId, item1)) + ) + context.receive(6) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + context.Main.Update.mainX(contextId), + context.Main.Update.mainY(contextId), + context.Main.Update.mainZ(contextId), + TestMessages.update(contextId, idMain, Constants.INTEGER), + context.executionComplete(contextId) + ) + + // attach visualisation + context.send( + Api.Request( + requestId, + Api.AttachVisualisation( + visualisationId, + idMain, + Api.VisualisationConfiguration( + contextId, + "Test.Visualisation", + "here.inc_and_encode" + ) + ) + ) + ) + context.receive(3) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.VisualisationAttached()), + Api.Response( + Api.VisualisationEvaluationFailed( + contextId, + visualisationId, + idMain, + "Method `visualise_me` of 51 (Integer) could not be found.", + Some( + Api.ExecutionResult.Diagnostic.error( + "Method `visualise_me` of 51 (Integer) could not be found.", + Some(visualisationFile), + Some(model.Range(model.Position(1, 11), model.Position(1, 25))), + None, + Vector( + Api.StackTraceElement( + "Visualisation.encode", + Some(visualisationFile), + Some( + model.Range(model.Position(1, 11), model.Position(1, 25)) + ), + None + ), + Api.StackTraceElement( + "Visualisation.inc_and_encode", + Some(visualisationFile), + Some( + model.Range(model.Position(3, 19), model.Position(3, 34)) + ), + None + ) + ) + ) + ) ) ), context.executionComplete(contextId) From 318942ccbe11ab78e64061cfc5760c74f9a7434d Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Thu, 15 Apr 2021 11:04:22 +0300 Subject: [PATCH 05/13] feat: add failure info in visualisation response --- .../runtime/ContextEventsListener.scala | 4 +- .../runtime/ContextRegistry.scala | 37 +++--- .../runtime/ContextRegistryProtocol.scala | 8 +- .../languageserver/runtime/ExecutionApi.scala | 14 ++- .../runtime/RuntimeFailureMapper.scala | 78 +++++++++---- .../handler/AttachVisualisationHandler.scala | 10 +- .../handler/CreateContextHandler.scala | 10 +- .../handler/DestroyContextHandler.scala | 10 +- .../handler/DetachVisualisationHandler.scala | 10 +- .../handler/ModifyVisualisationHandler.scala | 10 +- .../runtime/handler/PopContextHandler.scala | 10 +- .../runtime/handler/PushContextHandler.scala | 10 +- .../handler/RecomputeContextHandler.scala | 10 +- .../search/SuggestionsHandler.scala | 6 +- .../search/handler/ImportModuleHandler.scala | 10 +- .../InvalidateModulesIndexHandler.scala | 10 +- .../websocket/json/ContextRegistryTest.scala | 2 +- .../org/enso/polyglot/runtime/Runtime.scala | 9 +- .../job/DetachVisualisationJob.scala | 3 +- .../instrument/job/ExecuteJob.scala | 5 +- .../job/ProgramExecutionSupport.scala | 108 ++++++++++-------- .../job/UpsertVisualisationJob.scala | 39 ++++--- 22 files changed, 262 insertions(+), 151 deletions(-) diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextEventsListener.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextEventsListener.scala index a7ec263b0b2d..0b9893ad300f 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextEventsListener.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextEventsListener.scala @@ -111,9 +111,7 @@ final class ContextEventsListener( visualisationId, expressionId, message, - diagnostic.collect { case m: Api.ExecutionResult.Diagnostic => - toProtocolDiagnostic(m) - } + diagnostic.map(toProtocolDiagnostic) ) sessionRouter ! DeliverToJsonController(rpcSession.clientId, payload) diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistry.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistry.scala index ecf6540775c9..f663236a356f 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistry.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistry.scala @@ -108,7 +108,7 @@ final class ContextRegistry( case CreateContextRequest(client) => val contextId = UUID.randomUUID() val handler = - context.actorOf(CreateContextHandler.props(timeout, runtime)) + context.actorOf(CreateContextHandler.props(config, timeout, runtime)) val listener = context.actorOf( ContextEventsListener.props( @@ -129,7 +129,7 @@ final class ContextRegistry( case DestroyContextRequest(client, contextId) => if (store.hasContext(client.clientId, contextId)) { val handler = - context.actorOf(DestroyContextHandler.props(timeout, runtime)) + context.actorOf(DestroyContextHandler.props(config, timeout, runtime)) store.getListener(contextId).foreach(context.stop) handler.forward(Api.DestroyContextRequest(contextId)) context.become( @@ -145,7 +145,7 @@ final class ContextRegistry( if (store.hasContext(client.clientId, contextId)) { val item = getRuntimeStackItem(stackItem) val handler = - context.actorOf(PushContextHandler.props(timeout, runtime)) + context.actorOf(PushContextHandler.props(config, timeout, runtime)) handler.forward(Api.PushContextRequest(contextId, item)) } else { @@ -154,7 +154,8 @@ final class ContextRegistry( case PopContextRequest(client, contextId) => if (store.hasContext(client.clientId, contextId)) { - val handler = context.actorOf(PopContextHandler.props(timeout, runtime)) + val handler = + context.actorOf(PopContextHandler.props(config, timeout, runtime)) handler.forward(Api.PopContextRequest(contextId)) } else { sender() ! AccessDenied @@ -163,7 +164,9 @@ final class ContextRegistry( case RecomputeContextRequest(client, contextId, expressions) => if (store.hasContext(client.clientId, contextId)) { val handler = - context.actorOf(RecomputeContextHandler.props(timeout, runtime)) + context.actorOf( + RecomputeContextHandler.props(config, timeout, runtime) + ) val invalidatedExpressions = expressions.map(toRuntimeInvalidatedExpressions) handler.forward( @@ -173,12 +176,14 @@ final class ContextRegistry( sender() ! AccessDenied } - case AttachVisualisation(clientId, visualisationId, expressionId, config) => - if (store.hasContext(clientId, config.executionContextId)) { + case AttachVisualisation(clientId, visualisationId, expressionId, cfg) => + if (store.hasContext(clientId, cfg.executionContextId)) { val handler = - context.actorOf(AttachVisualisationHandler.props(timeout, runtime)) + context.actorOf( + AttachVisualisationHandler.props(config, timeout, runtime) + ) - val configuration = convertVisualisationConfig(config) + val configuration = convertVisualisationConfig(cfg) handler.forward( Api.AttachVisualisation(visualisationId, expressionId, configuration) @@ -195,7 +200,9 @@ final class ContextRegistry( ) => if (store.hasContext(clientId, contextId)) { val handler = - context.actorOf(DetachVisualisationHandler.props(timeout, runtime)) + context.actorOf( + DetachVisualisationHandler.props(config, timeout, runtime) + ) handler.forward( Api.DetachVisualisation(contextId, visualisationId, expressionId) @@ -204,12 +211,14 @@ final class ContextRegistry( sender() ! AccessDenied } - case ModifyVisualisation(clientId, visualisationId, config) => - if (store.hasContext(clientId, config.executionContextId)) { + case ModifyVisualisation(clientId, visualisationId, cfg) => + if (store.hasContext(clientId, cfg.executionContextId)) { val handler = - context.actorOf(ModifyVisualisationHandler.props(timeout, runtime)) + context.actorOf( + ModifyVisualisationHandler.props(config, timeout, runtime) + ) - val configuration = convertVisualisationConfig(config) + val configuration = convertVisualisationConfig(cfg) handler.forward( Api.ModifyVisualisation(visualisationId, configuration) diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala index 07c5f523ded7..69045e6dc3f2 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala @@ -389,8 +389,12 @@ object ContextRegistryProtocol { * a [[ModifyVisualisation]] cannot be evaluated. * * @param message the reason of the failure + * @param failure the detailed information about the failure */ - case class VisualisationExpressionFailed(message: String) extends Failure + case class VisualisationExpressionFailed( + message: String, + failure: Option[ExecutionFailure] + ) extends Failure /** Signals that an evaluation of a code responsible for generating * visualisation data failed. @@ -399,7 +403,7 @@ object ContextRegistryProtocol { * @param visualisationId a visualisation identifier * @param expressionId an identifier of a visualised expression * @param message the reason of the failure - * @param diagnostic detailed information about the error + * @param diagnostic the detailed information about the error */ case class VisualisationEvaluationFailed( contextId: UUID, diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ExecutionApi.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ExecutionApi.scala index eaaf78b322c6..5e50976e23e5 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ExecutionApi.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ExecutionApi.scala @@ -2,6 +2,8 @@ package org.enso.languageserver.runtime import java.util.UUID +import io.circe.Encoder +import io.circe.generic.auto._ import org.enso.jsonrpc.{Error, HasParams, HasResult, Method, Unused} import org.enso.languageserver.data.CapabilityRegistration import org.enso.languageserver.filemanager.Path @@ -156,10 +158,18 @@ object ExecutionApi { case object VisualisationNotFoundError extends Error(2006, s"Visualisation not found") - case class VisualisationExpressionError(msg: String) - extends Error( + case class VisualisationExpressionError( + msg: String, + executionFailure: Option[ContextRegistryProtocol.ExecutionFailure] + ) extends Error( 2007, s"Evaluation of the visualisation expression failed [$msg]" + ) { + + override def payload = + executionFailure.map( + Encoder[ContextRegistryProtocol.ExecutionFailure].apply(_) ) + } } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/RuntimeFailureMapper.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/RuntimeFailureMapper.scala index 2cf0a8125bcb..ffe0cc718107 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/RuntimeFailureMapper.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/RuntimeFailureMapper.scala @@ -1,13 +1,63 @@ package org.enso.languageserver.runtime +import org.enso.jsonrpc.Error +import org.enso.languageserver.data.Config import org.enso.languageserver.filemanager.FileSystemFailureMapper import org.enso.languageserver.protocol.json.ErrorApi._ import org.enso.languageserver.runtime.ExecutionApi._ import org.enso.polyglot.runtime.Runtime.Api -import org.enso.jsonrpc.Error +final class RuntimeFailureMapper(config: Config) { + + /** Maps runtime Api error into a registry error. + * + * @param error runtime Api error + * @return registry error + */ + def mapApiError(error: Api.Error): ContextRegistryProtocol.Failure = + error match { + case Api.ContextNotExistError(contextId) => + ContextRegistryProtocol.ContextNotFound(contextId) + case Api.EmptyStackError(contextId) => + ContextRegistryProtocol.EmptyStackError(contextId) + case Api.InvalidStackItemError(contextId) => + ContextRegistryProtocol.InvalidStackItemError(contextId) + case Api.ModuleNotFound(moduleName) => + ContextRegistryProtocol.ModuleNotFound(moduleName) + case Api.VisualisationExpressionFailed(message, result) => + ContextRegistryProtocol.VisualisationExpressionFailed( + message, + result.map(toProtocolError) + ) + case Api.VisualisationNotFound() => + ContextRegistryProtocol.VisualisationNotFound + } + + /** Convert the runtime failure message to the context registry protocol + * representation. + * + * @param error the error message + * @return the registry protocol representation fo the diagnostic message + */ + private def toProtocolError( + error: Api.ExecutionResult.Failure + ): ContextRegistryProtocol.ExecutionFailure = + ContextRegistryProtocol.ExecutionFailure( + error.message, + error.file.flatMap(config.findRelativePath) + ) + +} object RuntimeFailureMapper { + /** Create runtime failure mapper instance. + * + * @param config the language server config + * @return a new instance of [[RuntimeFailureMapper]] + */ + def apply(config: Config): RuntimeFailureMapper = + new RuntimeFailureMapper(config) + /** Maps registry error into JSON RPC error. * * @param error registry error @@ -29,29 +79,7 @@ object RuntimeFailureMapper { VisualisationNotFoundError case ContextRegistryProtocol.ModuleNotFound(name) => ModuleNotFoundError(name) - case ContextRegistryProtocol.VisualisationExpressionFailed(msg) => - VisualisationExpressionError(msg) + case ContextRegistryProtocol.VisualisationExpressionFailed(msg, result) => + VisualisationExpressionError(msg, result) } - - /** Maps runtime Api error into a registry error. - * - * @param error runtime Api error - * @return registry error - */ - def mapApiError(error: Api.Error): ContextRegistryProtocol.Failure = - error match { - case Api.ContextNotExistError(contextId) => - ContextRegistryProtocol.ContextNotFound(contextId) - case Api.EmptyStackError(contextId) => - ContextRegistryProtocol.EmptyStackError(contextId) - case Api.InvalidStackItemError(contextId) => - ContextRegistryProtocol.InvalidStackItemError(contextId) - case Api.ModuleNotFound(moduleName: String) => - ContextRegistryProtocol.ModuleNotFound(moduleName) - case Api.VisualisationExpressionFailed(message: String) => - ContextRegistryProtocol.VisualisationExpressionFailed(message) - case Api.VisualisationNotFound() => - ContextRegistryProtocol.VisualisationNotFound - } - } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/handler/AttachVisualisationHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/handler/AttachVisualisationHandler.scala index 7461f7ec8423..fcd6b4e1d013 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/handler/AttachVisualisationHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/handler/AttachVisualisationHandler.scala @@ -3,6 +3,7 @@ package org.enso.languageserver.runtime.handler import java.util.UUID import akka.actor.{Actor, ActorLogging, ActorRef, Cancellable, Props} +import org.enso.languageserver.data.Config import org.enso.languageserver.requesthandler.RequestTimeout import org.enso.languageserver.runtime.{ ContextRegistryProtocol, @@ -15,10 +16,12 @@ import scala.concurrent.duration.FiniteDuration /** A request handler for attach visualisation commands. * + * @param config the language server config * @param timeout request timeout * @param runtime reference to the runtime connector */ class AttachVisualisationHandler( + config: Config, timeout: FiniteDuration, runtime: ActorRef ) extends Actor @@ -50,7 +53,7 @@ class AttachVisualisationHandler( context.stop(self) case Api.Response(_, error: Api.Error) => - replyTo ! RuntimeFailureMapper.mapApiError(error) + replyTo ! RuntimeFailureMapper(config).mapApiError(error) cancellable.cancel() context.stop(self) } @@ -61,10 +64,11 @@ object AttachVisualisationHandler { /** Creates configuration object used to create a [[AttachVisualisationHandler]]. * + * @param config the language server config * @param timeout request timeout * @param runtime reference to the runtime connector */ - def props(timeout: FiniteDuration, runtime: ActorRef): Props = - Props(new AttachVisualisationHandler(timeout, runtime)) + def props(config: Config, timeout: FiniteDuration, runtime: ActorRef): Props = + Props(new AttachVisualisationHandler(config, timeout, runtime)) } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/handler/CreateContextHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/handler/CreateContextHandler.scala index 2879f45d2a2f..e13f52ffc376 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/handler/CreateContextHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/handler/CreateContextHandler.scala @@ -3,6 +3,7 @@ package org.enso.languageserver.runtime.handler import java.util.UUID import akka.actor.{Actor, ActorLogging, ActorRef, Cancellable, Props} +import org.enso.languageserver.data.Config import org.enso.languageserver.requesthandler.RequestTimeout import org.enso.languageserver.runtime.{ ContextRegistryProtocol, @@ -15,10 +16,12 @@ import scala.concurrent.duration.FiniteDuration /** A request handler for create context commands. * + * @param config the language server config * @param timeout request timeout * @param runtime reference to the runtime connector */ final class CreateContextHandler( + config: Config, timeout: FiniteDuration, runtime: ActorRef ) extends Actor @@ -50,7 +53,7 @@ final class CreateContextHandler( context.stop(self) case Api.Response(_, error: Api.Error) => - replyTo ! RuntimeFailureMapper.mapApiError(error) + replyTo ! RuntimeFailureMapper(config).mapApiError(error) cancellable.cancel() context.stop(self) } @@ -60,9 +63,10 @@ object CreateContextHandler { /** Creates configuration object used to create a [[CreateContextHandler]]. * + * @param config the language server config * @param timeout request timeout * @param runtime reference to the runtime connector */ - def props(timeout: FiniteDuration, runtime: ActorRef): Props = - Props(new CreateContextHandler(timeout, runtime)) + def props(config: Config, timeout: FiniteDuration, runtime: ActorRef): Props = + Props(new CreateContextHandler(config, timeout, runtime)) } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/handler/DestroyContextHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/handler/DestroyContextHandler.scala index cc0bc0e31689..998cc66672f4 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/handler/DestroyContextHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/handler/DestroyContextHandler.scala @@ -3,6 +3,7 @@ package org.enso.languageserver.runtime.handler import java.util.UUID import akka.actor.{Actor, ActorLogging, ActorRef, Cancellable, Props} +import org.enso.languageserver.data.Config import org.enso.languageserver.requesthandler.RequestTimeout import org.enso.languageserver.runtime.{ ContextRegistryProtocol, @@ -15,10 +16,12 @@ import scala.concurrent.duration.FiniteDuration /** A request handler for destroy context commands. * + * @param config the language server config * @param timeout request timeout * @param runtime reference to the runtime conector */ final class DestroyContextHandler( + config: Config, timeout: FiniteDuration, runtime: ActorRef ) extends Actor @@ -50,7 +53,7 @@ final class DestroyContextHandler( context.stop(self) case Api.Response(_, error: Api.Error) => - replyTo ! RuntimeFailureMapper.mapApiError(error) + replyTo ! RuntimeFailureMapper(config).mapApiError(error) cancellable.cancel() context.stop(self) } @@ -60,9 +63,10 @@ object DestroyContextHandler { /** Creates a configuration object used to create [[DestroyContextHandler]]. * + * @param config the language server config * @param timeout request timeout * @param runtime reference to the runtime conector */ - def props(timeout: FiniteDuration, runtime: ActorRef): Props = - Props(new DestroyContextHandler(timeout, runtime)) + def props(config: Config, timeout: FiniteDuration, runtime: ActorRef): Props = + Props(new DestroyContextHandler(config, timeout, runtime)) } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/handler/DetachVisualisationHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/handler/DetachVisualisationHandler.scala index 698ed845b632..2adf97504c4b 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/handler/DetachVisualisationHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/handler/DetachVisualisationHandler.scala @@ -3,6 +3,7 @@ package org.enso.languageserver.runtime.handler import java.util.UUID import akka.actor.{Actor, ActorLogging, ActorRef, Cancellable, Props} +import org.enso.languageserver.data.Config import org.enso.languageserver.requesthandler.RequestTimeout import org.enso.languageserver.runtime.{ ContextRegistryProtocol, @@ -15,10 +16,12 @@ import scala.concurrent.duration.FiniteDuration /** A request handler for detach visualisation commands. * + * @param config the language server config * @param timeout request timeout * @param runtime reference to the runtime connector */ class DetachVisualisationHandler( + config: Config, timeout: FiniteDuration, runtime: ActorRef ) extends Actor @@ -50,7 +53,7 @@ class DetachVisualisationHandler( context.stop(self) case Api.Response(_, error: Api.Error) => - replyTo ! RuntimeFailureMapper.mapApiError(error) + replyTo ! RuntimeFailureMapper(config).mapApiError(error) cancellable.cancel() context.stop(self) } @@ -61,10 +64,11 @@ object DetachVisualisationHandler { /** Creates configuration object used to create a [[DetachVisualisationHandler]]. * + * @param config the language server config * @param timeout request timeout * @param runtime reference to the runtime connector */ - def props(timeout: FiniteDuration, runtime: ActorRef): Props = - Props(new DetachVisualisationHandler(timeout, runtime)) + def props(config: Config, timeout: FiniteDuration, runtime: ActorRef): Props = + Props(new DetachVisualisationHandler(config, timeout, runtime)) } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/handler/ModifyVisualisationHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/handler/ModifyVisualisationHandler.scala index 53eef6345deb..5389e11b9934 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/handler/ModifyVisualisationHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/handler/ModifyVisualisationHandler.scala @@ -3,6 +3,7 @@ package org.enso.languageserver.runtime.handler import java.util.UUID import akka.actor.{Actor, ActorLogging, ActorRef, Cancellable, Props} +import org.enso.languageserver.data.Config import org.enso.languageserver.requesthandler.RequestTimeout import org.enso.languageserver.runtime.{ ContextRegistryProtocol, @@ -15,10 +16,12 @@ import scala.concurrent.duration.FiniteDuration /** A request handler for modify visualisation commands. * + * @param config the language server config * @param timeout request timeout * @param runtime reference to the runtime connector */ class ModifyVisualisationHandler( + config: Config, timeout: FiniteDuration, runtime: ActorRef ) extends Actor @@ -50,7 +53,7 @@ class ModifyVisualisationHandler( context.stop(self) case Api.Response(_, error: Api.Error) => - replyTo ! RuntimeFailureMapper.mapApiError(error) + replyTo ! RuntimeFailureMapper(config).mapApiError(error) cancellable.cancel() context.stop(self) } @@ -61,10 +64,11 @@ object ModifyVisualisationHandler { /** Creates configuration object used to create a [[ModifyVisualisationHandler]]. * + * @param config the language server config * @param timeout request timeout * @param runtime reference to the runtime connector */ - def props(timeout: FiniteDuration, runtime: ActorRef): Props = - Props(new ModifyVisualisationHandler(timeout, runtime)) + def props(config: Config, timeout: FiniteDuration, runtime: ActorRef): Props = + Props(new ModifyVisualisationHandler(config, timeout, runtime)) } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/handler/PopContextHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/handler/PopContextHandler.scala index aea715572038..ed77852fd30a 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/handler/PopContextHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/handler/PopContextHandler.scala @@ -3,6 +3,7 @@ package org.enso.languageserver.runtime.handler import java.util.UUID import akka.actor.{Actor, ActorLogging, ActorRef, Cancellable, Props} +import org.enso.languageserver.data.Config import org.enso.languageserver.requesthandler.RequestTimeout import org.enso.languageserver.runtime.{ ContextRegistryProtocol, @@ -15,10 +16,12 @@ import scala.concurrent.duration.FiniteDuration /** A request handler for push commands. * + * @param config the language server config * @param timeout request timeout * @param runtime reference to the runtime conector */ final class PopContextHandler( + config: Config, timeout: FiniteDuration, runtime: ActorRef ) extends Actor @@ -50,7 +53,7 @@ final class PopContextHandler( context.stop(self) case Api.Response(_, error: Api.Error) => - replyTo ! RuntimeFailureMapper.mapApiError(error) + replyTo ! RuntimeFailureMapper(config).mapApiError(error) cancellable.cancel() context.stop(self) } @@ -60,9 +63,10 @@ object PopContextHandler { /** Creates a configuration object used to create [[PopContextHandler]]. * + * @param config the language server config * @param timeout request timeout * @param runtime reference to the runtime conector */ - def props(timeout: FiniteDuration, runtime: ActorRef): Props = - Props(new PopContextHandler(timeout, runtime)) + def props(config: Config, timeout: FiniteDuration, runtime: ActorRef): Props = + Props(new PopContextHandler(config, timeout, runtime)) } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/handler/PushContextHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/handler/PushContextHandler.scala index 5f720cbdb058..6e16b0d50bdc 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/handler/PushContextHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/handler/PushContextHandler.scala @@ -3,6 +3,7 @@ package org.enso.languageserver.runtime.handler import java.util.UUID import akka.actor.{Actor, ActorLogging, ActorRef, Cancellable, Props} +import org.enso.languageserver.data.Config import org.enso.languageserver.requesthandler.RequestTimeout import org.enso.languageserver.runtime.{ ContextRegistryProtocol, @@ -15,10 +16,12 @@ import scala.concurrent.duration.FiniteDuration /** A request handler for push commands. * + * @param config the language server config * @param timeout request timeout * @param runtime reference to the runtime conector */ final class PushContextHandler( + config: Config, timeout: FiniteDuration, runtime: ActorRef ) extends Actor @@ -50,7 +53,7 @@ final class PushContextHandler( context.stop(self) case Api.Response(_, error: Api.Error) => - replyTo ! RuntimeFailureMapper.mapApiError(error) + replyTo ! RuntimeFailureMapper(config).mapApiError(error) cancellable.cancel() context.stop(self) } @@ -60,9 +63,10 @@ object PushContextHandler { /** Creates a configuration object used to create [[PushContextHandler]]. * + * @param config the language server config * @param timeout request timeout * @param runtime reference to the runtime conector */ - def props(timeout: FiniteDuration, runtime: ActorRef): Props = - Props(new PushContextHandler(timeout, runtime)) + def props(config: Config, timeout: FiniteDuration, runtime: ActorRef): Props = + Props(new PushContextHandler(config, timeout, runtime)) } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/handler/RecomputeContextHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/handler/RecomputeContextHandler.scala index fe0c39490b12..776579221876 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/handler/RecomputeContextHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/handler/RecomputeContextHandler.scala @@ -3,6 +3,7 @@ package org.enso.languageserver.runtime.handler import java.util.UUID import akka.actor.{Actor, ActorLogging, ActorRef, Cancellable, Props} +import org.enso.languageserver.data.Config import org.enso.languageserver.requesthandler.RequestTimeout import org.enso.languageserver.runtime.{ ContextRegistryProtocol, @@ -15,10 +16,12 @@ import scala.concurrent.duration.FiniteDuration /** A request handler for recompute commands. * + * @param config the language server config * @param timeout request timeout * @param runtime reference to the runtime connector */ final class RecomputeContextHandler( + config: Config, timeout: FiniteDuration, runtime: ActorRef ) extends Actor @@ -50,7 +53,7 @@ final class RecomputeContextHandler( context.stop(self) case Api.Response(_, error: Api.Error) => - replyTo ! RuntimeFailureMapper.mapApiError(error) + replyTo ! RuntimeFailureMapper(config).mapApiError(error) cancellable.cancel() context.stop(self) } @@ -60,9 +63,10 @@ object RecomputeContextHandler { /** Creates configuration object used to create a [[RecomputeContextHandler]]. * + * @param config the language server config * @param timeout request timeout * @param runtime reference to the runtime connector */ - def props(timeout: FiniteDuration, runtime: ActorRef): Props = - Props(new RecomputeContextHandler(timeout, runtime)) + def props(config: Config, timeout: FiniteDuration, runtime: ActorRef): Props = + Props(new RecomputeContextHandler(config, timeout, runtime)) } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/search/SuggestionsHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/search/SuggestionsHandler.scala index 5bdf794ccd8f..705bd0359f66 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/search/SuggestionsHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/search/SuggestionsHandler.scala @@ -281,7 +281,7 @@ final class SuggestionsHandler( .getOrElse(SearchProtocol.SuggestionNotFoundError) val handler = context.system - .actorOf(ImportModuleHandler.props(timeout, runtimeConnector)) + .actorOf(ImportModuleHandler.props(config, timeout, runtimeConnector)) action.pipeTo(handler)(sender()) case FileDeletedEvent(path) => @@ -326,7 +326,9 @@ final class SuggestionsHandler( } yield SearchProtocol.InvalidateModulesIndex val handler = context.system - .actorOf(InvalidateModulesIndexHandler.props(timeout, runtimeConnector)) + .actorOf( + InvalidateModulesIndexHandler.props(config, timeout, runtimeConnector) + ) action.pipeTo(handler)(sender()) case ProjectNameChangedEvent(oldName, newName) => diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/search/handler/ImportModuleHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/search/handler/ImportModuleHandler.scala index 2b999ffbc7b1..11783211d368 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/search/handler/ImportModuleHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/search/handler/ImportModuleHandler.scala @@ -3,6 +3,7 @@ package org.enso.languageserver.search.handler import java.util.UUID import akka.actor.{Actor, ActorLogging, ActorRef, Cancellable, Props} +import org.enso.languageserver.data.Config import org.enso.languageserver.requesthandler.RequestTimeout import org.enso.languageserver.runtime.RuntimeFailureMapper import org.enso.languageserver.search.SearchProtocol @@ -13,10 +14,12 @@ import scala.concurrent.duration.FiniteDuration /** A request handler for import module command. * + * @param config the language server config * @param timeout request timeout * @param runtime reference to the runtime connector */ final class ImportModuleHandler( + config: Config, timeout: FiniteDuration, runtime: ActorRef ) extends Actor @@ -60,7 +63,7 @@ final class ImportModuleHandler( context.stop(self) case Api.Response(_, error: Api.Error) => - replyTo ! RuntimeFailureMapper.mapApiError(error) + replyTo ! RuntimeFailureMapper(config).mapApiError(error) cancellable.cancel() context.stop(self) } @@ -78,9 +81,10 @@ object ImportModuleHandler { /** Creates a configuration object used to create [[ImportModuleHandler]]. * + * @param config the language server config * @param timeout request timeout * @param runtime reference to the runtime conector */ - def props(timeout: FiniteDuration, runtime: ActorRef): Props = - Props(new ImportModuleHandler(timeout, runtime)) + def props(config: Config, timeout: FiniteDuration, runtime: ActorRef): Props = + Props(new ImportModuleHandler(config, timeout, runtime)) } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/search/handler/InvalidateModulesIndexHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/search/handler/InvalidateModulesIndexHandler.scala index cf73d4dcf8df..848a3f7f9a35 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/search/handler/InvalidateModulesIndexHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/search/handler/InvalidateModulesIndexHandler.scala @@ -3,6 +3,7 @@ package org.enso.languageserver.search.handler import java.util.UUID import akka.actor.{Actor, ActorLogging, ActorRef, Cancellable, Props} +import org.enso.languageserver.data.Config import org.enso.languageserver.requesthandler.RequestTimeout import org.enso.languageserver.runtime.RuntimeFailureMapper import org.enso.languageserver.search.SearchProtocol @@ -13,10 +14,12 @@ import scala.concurrent.duration.FiniteDuration /** A request handler for invalidate modules index command. * + * @param config the language server config * @param timeout request timeout * @param runtime reference to the runtime connector */ final class InvalidateModulesIndexHandler( + config: Config, timeout: FiniteDuration, runtime: ActorRef ) extends Actor @@ -52,7 +55,7 @@ final class InvalidateModulesIndexHandler( context.stop(self) case Api.Response(_, error: Api.Error) => - replyTo ! RuntimeFailureMapper.mapApiError(error) + replyTo ! RuntimeFailureMapper(config).mapApiError(error) cancellable.cancel() context.stop(self) } @@ -62,9 +65,10 @@ object InvalidateModulesIndexHandler { /** Creates a configuration object used to create [[InvalidateModulesIndexHandler]]. * + * @param config the language server config * @param timeout request timeout * @param runtime reference to the runtime conector */ - def props(timeout: FiniteDuration, runtime: ActorRef): Props = - Props(new InvalidateModulesIndexHandler(timeout, runtime)) + def props(config: Config, timeout: FiniteDuration, runtime: ActorRef): Props = + Props(new InvalidateModulesIndexHandler(config, timeout, runtime)) } diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/ContextRegistryTest.scala b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/ContextRegistryTest.scala index 82334e6aaa0a..754d48382628 100644 --- a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/ContextRegistryTest.scala +++ b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/ContextRegistryTest.scala @@ -610,7 +610,7 @@ class ContextRegistryTest extends BaseServerTest { } runtimeConnectorProbe.lastSender ! Api.Response( requestId2, - Api.VisualisationExpressionFailed(expressionFailureMessage) + Api.VisualisationExpressionFailed(expressionFailureMessage, None) ) client.expectJson( json.executionContextVisualisationExpressionFailed( diff --git a/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala b/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala index e30b55b1f51e..09bedefc4b4f 100644 --- a/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala +++ b/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala @@ -891,8 +891,12 @@ object Runtime { * a [[ModifyVisualisation]] cannot be evaluated. * * @param message the reason of the failure + * @param failure the detailed information about the failure */ - case class VisualisationExpressionFailed(message: String) extends Error + case class VisualisationExpressionFailed( + message: String, + failure: Option[ExecutionResult.Failure] + ) extends Error /** Signals that an evaluation of a code responsible for generating * visualisation data failed. @@ -901,13 +905,14 @@ object Runtime { * @param visualisationId the visualisation identifier * @param expressionId the identifier of a visualised expression * @param message the reason of the failure + * @param diagnostic the detailed information about the failure */ case class VisualisationEvaluationFailed( contextId: ContextId, visualisationId: VisualisationId, expressionId: ExpressionId, message: String, - diagnostic: Option[ExecutionResult] + diagnostic: Option[ExecutionResult.Diagnostic] ) extends ApiNotification /** Signals that visualisation cannot be found. diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/DetachVisualisationJob.scala b/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/DetachVisualisationJob.scala index 646d2e116d30..378537093baf 100644 --- a/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/DetachVisualisationJob.scala +++ b/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/DetachVisualisationJob.scala @@ -23,8 +23,7 @@ class DetachVisualisationJob( expressionId: ExpressionId, contextId: ContextId, response: ApiResponse -) extends Job[Unit](List(contextId), true, false) - with ProgramExecutionSupport { +) extends Job[Unit](List(contextId), true, false) { /** @inheritdoc */ override def run(implicit ctx: RuntimeContext): Unit = { diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ExecuteJob.scala b/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ExecuteJob.scala index 1ec437e511d0..d743e66cadd9 100644 --- a/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ExecuteJob.scala +++ b/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ExecuteJob.scala @@ -25,8 +25,7 @@ class ExecuteJob( // TODO[MK]: make this interruptible when https://github.com/oracle/graal/issues/3273 // is resolved mayInterruptIfRunning = false - ) - with ProgramExecutionSupport { + ) { def this(exe: Executable) = this( @@ -43,7 +42,7 @@ class ExecuteJob( ctx.executionService.getContext.getThreadManager.enter() try { val outcome = - runProgram( + ProgramExecutionSupport.runProgram( contextId, stack, updatedVisualisations, diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala b/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala index 77448110a4d1..1b4962bc04a4 100644 --- a/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala +++ b/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala @@ -16,10 +16,6 @@ import org.enso.interpreter.instrument.execution.{ LocationResolver, RuntimeContext } -import org.enso.interpreter.instrument.job.ProgramExecutionSupport.{ - ExecutionFrame, - LocalCallFrame -} import org.enso.interpreter.instrument.profiling.ExecutionTime import org.enso.interpreter.instrument.{ InstrumentFrame, @@ -47,7 +43,7 @@ import scala.jdk.OptionConverters._ /** Provides support for executing Enso code. Adds convenient methods to * run Enso programs in a Truffle context. */ -trait ProgramExecutionSupport { +object ProgramExecutionSupport { /** Runs an Enso program. * @@ -272,48 +268,49 @@ trait ProgramExecutionSupport { * @param ctx the runtime context * @return the API message describing the error */ - private def getExecutionOutcome( + def getExecutionOutcome( t: Throwable - )(implicit ctx: RuntimeContext): Option[Api.ExecutionResult] = { - t match { - case ex: AbstractTruffleException - if Option(ctx.executionService.getLanguage(ex)) - .forall(_ == LanguageInfo.ID) => - val section = Option(ctx.executionService.getSourceLocation(ex)) - Some( - Api.ExecutionResult.Diagnostic.error( - ex.getMessage, - section.flatMap(sec => findFileByModuleName(sec.getSource.getName)), - section.map(LocationResolver.sectionToRange), - section - .flatMap(LocationResolver.getExpressionId(_)) - .map(_.externalId), - ErrorResolver.getStackTrace(ex) - ) - ) + )(implicit ctx: RuntimeContext): Option[Api.ExecutionResult] = + getDiagnosticOutcome.orElse(getFailureOutcome).lift(t) - case ex: ConstructorNotFoundException => - Some( - Api.ExecutionResult.Failure( - ex.getMessage, - findFileByModuleName(ex.getModule) - ) - ) + /** Extract diagnostic information from the provided exception. */ + def getDiagnosticOutcome(implicit + ctx: RuntimeContext + ): PartialFunction[Throwable, Api.ExecutionResult.Diagnostic] = { + case ex: AbstractTruffleException + if Option(ctx.executionService.getLanguage(ex)) + .forall(_ == LanguageInfo.ID) => + val section = Option(ctx.executionService.getSourceLocation(ex)) + val source = section.flatMap(sec => Option(sec.getSource)) + Api.ExecutionResult.Diagnostic.error( + ex.getMessage, + source.flatMap(src => findFileByModuleName(src.getName)), + section.map(LocationResolver.sectionToRange), + section + .flatMap(LocationResolver.getExpressionId(_)) + .map(_.externalId), + ErrorResolver.getStackTrace(ex) + ) + } - case ex: MethodNotFoundException => - Some( - Api.ExecutionResult.Failure( - ex.getMessage, - findFileByModuleName(ex.getModule) - ) - ) + /** Extract information about the failure from the provided exception. */ + def getFailureOutcome(implicit + ctx: RuntimeContext + ): PartialFunction[Throwable, Api.ExecutionResult.Failure] = { + case ex: ConstructorNotFoundException => + Api.ExecutionResult.Failure( + ex.getMessage, + findFileByModuleName(ex.getModule) + ) - case ex: ServiceException => - Some(Api.ExecutionResult.Failure(ex.getMessage, None)) + case ex: MethodNotFoundException => + Api.ExecutionResult.Failure( + ex.getMessage, + findFileByModuleName(ex.getModule) + ) - case _ => - None - } + case ex: ServiceException => + Api.ExecutionResult.Failure(ex.getMessage, None) } private def sendErrorUpdate(contextId: ContextId, error: Exception)(implicit @@ -323,7 +320,13 @@ trait ProgramExecutionSupport { Api.Response( Api.ExecutionUpdate( contextId, - Seq(Api.ExecutionResult.Diagnostic.error(error.getMessage)) + Seq( + getDiagnosticOutcome.applyOrElse( + error, + (ex: Exception) => + Api.ExecutionResult.Diagnostic.error(ex.getMessage) + ) + ) ) ) ) @@ -377,6 +380,13 @@ trait ProgramExecutionSupport { } } + /** Find visualisations for the provided expression value, compute and send + * the updates. + * + * @param contextId the identifier of an execution context + * @param value the computed value + * @param ctx the runtime context + */ private def fireVisualisationUpdates( contextId: ContextId, value: ExpressionValue @@ -436,7 +446,6 @@ trait ProgramExecutionSupport { ) ) } - errorMsgOrVisualisationData match { case Left(error) => ctx.endpoint.sendToClient( @@ -446,7 +455,7 @@ trait ProgramExecutionSupport { visualisation.id, expressionId, error.getMessage, - getExecutionOutcome(error) + getDiagnosticOutcome.lift(error) ) ) ) @@ -471,6 +480,11 @@ trait ProgramExecutionSupport { } } + /** Extract method pointer information from the expression value. + * + * @param value the expression value. + * @return the method pointer info + */ private def toMethodPointer( value: ExpressionValue ): Option[Api.MethodPointer] = @@ -498,10 +512,6 @@ trait ProgramExecutionSupport { path <- Option(module.getPath) } yield new File(path) -} - -object ProgramExecutionSupport { - /** An execution frame. * * @param item the executionitem diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/UpsertVisualisationJob.scala b/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/UpsertVisualisationJob.scala index 11adfdc153c9..ebcdfb3823f4 100644 --- a/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/UpsertVisualisationJob.scala +++ b/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/UpsertVisualisationJob.scala @@ -15,8 +15,6 @@ import org.enso.polyglot.runtime.Runtime.Api.{ } import org.enso.polyglot.runtime.Runtime.{Api, ApiResponse} -import scala.util.control.NonFatal - /** A job that upserts a visualisation. * * @param requestId maybe a request id @@ -35,8 +33,7 @@ class UpsertVisualisationJob( List(config.executionContextId), true, false - ) - with ProgramExecutionSupport { + ) { /** @inheritdoc */ override def run(implicit ctx: RuntimeContext): Option[Executable] = { @@ -51,8 +48,8 @@ class UpsertVisualisationJob( replyWithModuleNotFoundError() None - case Left(EvaluationFailed(msg)) => - replyWithExpressionFailedError(msg) + case Left(EvaluationFailed(message, result)) => + replyWithExpressionFailedError(message, result) None case Right(callable) => @@ -63,7 +60,7 @@ class UpsertVisualisationJob( .flatMap(frame => Option(frame.cache.get(expressionId))) cachedValue match { case Some(value) => - emitVisualisationUpdate( + ProgramExecutionSupport.emitVisualisationUpdate( config.executionContextId, visualisation, expressionId, @@ -104,12 +101,13 @@ class UpsertVisualisationJob( } private def replyWithExpressionFailedError( - msg: String + message: String, + executionResult: Option[Api.ExecutionResult.Failure] )(implicit ctx: RuntimeContext): Unit = { ctx.endpoint.sendToClient( Api.Response( requestId, - Api.VisualisationExpressionFailed(msg) + Api.VisualisationExpressionFailed(message, executionResult) ) ) } @@ -136,11 +134,16 @@ class UpsertVisualisationJob( else Left(ModuleNotFound) notFoundOrModule.flatMap { module => - try { - ctx.executionService.evaluateExpression(module, expression).asRight - } catch { - case NonFatal(th) => EvaluationFailed(th.getMessage).asLeft - } + Either + .catchNonFatal { + ctx.executionService.evaluateExpression(module, expression) + } + .leftMap { error => + EvaluationFailed( + error.getMessage, + ProgramExecutionSupport.getFailureOutcome.lift(error) + ) + } } } @@ -159,8 +162,12 @@ object UpsertVisualisationJob { /** Signals that an evaluation of an expression failed. * - * @param msg the textual reason of a failure + * @param message the textual reason of a failure + * @param failure the error description */ - case class EvaluationFailed(msg: String) extends EvalFailure + case class EvaluationFailed( + message: String, + failure: Option[Api.ExecutionResult.Failure] + ) extends EvalFailure } From 0c54722e52b1f300e3c22e8adc2a29d7a760b30c Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Thu, 15 Apr 2021 13:33:17 +0300 Subject: [PATCH 06/13] refactor: runtime failure mapper --- .../runtime/ContextEventsListener.scala | 74 ++---------------- .../runtime/ContextRegistryProtocol.scala | 4 +- .../languageserver/runtime/ExecutionApi.scala | 6 +- .../runtime/RuntimeFailureMapper.scala | 54 ++++++++++++- .../org/enso/polyglot/runtime/Runtime.scala | 2 +- .../job/ProgramExecutionSupport.scala | 2 +- .../job/UpsertVisualisationJob.scala | 6 +- .../test/instrument/RuntimeServerTest.scala | 76 ++++++++++++++++++- 8 files changed, 142 insertions(+), 82 deletions(-) diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextEventsListener.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextEventsListener.scala index 0b9893ad300f..f83d0c0d7279 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextEventsListener.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextEventsListener.scala @@ -4,10 +4,6 @@ import akka.actor.{Actor, ActorLogging, ActorRef, Props} import akka.pattern.pipe import org.enso.languageserver.data.Config import org.enso.languageserver.runtime.ContextRegistryProtocol.{ - ExecutionDiagnostic, - ExecutionDiagnosticKind, - ExecutionFailure, - ExecutionStackTraceElement, VisualisationContext, VisualisationUpdate } @@ -51,6 +47,8 @@ final class ContextEventsListener( import ContextEventsListener.RunExpressionUpdates import context.dispatcher + private val failureMapper = RuntimeFailureMapper(config) + override def preStart(): Unit = { if (updatesSendRate.length > 0) { context.system.scheduler.scheduleWithFixedDelay( @@ -86,7 +84,7 @@ final class ContextEventsListener( val payload = ContextRegistryProtocol.ExecutionFailedNotification( contextId, - toProtocolError(error) + failureMapper.toProtocolFailure(error) ) sessionRouter ! DeliverToJsonController(rpcSession.clientId, payload) @@ -94,7 +92,7 @@ final class ContextEventsListener( val payload = ContextRegistryProtocol.ExecutionDiagnosticNotification( contextId, - diagnostics.map(toProtocolDiagnostic) + diagnostics.map(failureMapper.toProtocolDiagnostic) ) sessionRouter ! DeliverToJsonController(rpcSession.clientId, payload) @@ -111,7 +109,7 @@ final class ContextEventsListener( visualisationId, expressionId, message, - diagnostic.map(toProtocolDiagnostic) + diagnostic.map(failureMapper.toProtocolDiagnostic) ) sessionRouter ! DeliverToJsonController(rpcSession.clientId, payload) @@ -210,68 +208,6 @@ final class ContextEventsListener( case Api.ProfilingInfo.ExecutionTime(t) => ProfilingInfo.ExecutionTime(t) } - - /** Convert the runtime failure message to the context registry protocol - * representation. - * - * @param error the error message - * @return the registry protocol representation fo the diagnostic message - */ - private def toProtocolError( - error: Api.ExecutionResult.Failure - ): ExecutionFailure = - ExecutionFailure( - error.message, - error.file.flatMap(config.findRelativePath) - ) - - /** Convert the runtime diagnostic message to the context registry protocol - * representation. - * - * @param diagnostic the diagnostic message - * @return the registry protocol representation of the diagnostic message - */ - private def toProtocolDiagnostic( - diagnostic: Api.ExecutionResult.Diagnostic - ): ExecutionDiagnostic = - ExecutionDiagnostic( - toDiagnosticType(diagnostic.kind), - diagnostic.message, - diagnostic.file.flatMap(config.findRelativePath), - diagnostic.location, - diagnostic.expressionId, - diagnostic.stack.map(toStackTraceElement) - ) - - /** Convert the runtime diagnostic type to the context registry protocol - * representation. - * - * @param kind the diagnostic type - * @return the registry protocol representation of the diagnostic type - */ - private def toDiagnosticType( - kind: Api.DiagnosticType - ): ExecutionDiagnosticKind = - kind match { - case Api.DiagnosticType.Error() => ExecutionDiagnosticKind.Error - case Api.DiagnosticType.Warning() => ExecutionDiagnosticKind.Warning - } - - /** Convert the runtime stack trace element to the context registry protocol - * representation. - * - * @param element the runtime stack trace element - * @return the registry protocol representation of the stack trace element - */ - private def toStackTraceElement( - element: Api.StackTraceElement - ): ExecutionStackTraceElement = - ExecutionStackTraceElement( - element.functionName, - element.file.flatMap(config.findRelativePath), - element.location, - element.expressionId - ) } object ContextEventsListener { diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala index 69045e6dc3f2..889e7818514e 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala @@ -389,11 +389,11 @@ object ContextRegistryProtocol { * a [[ModifyVisualisation]] cannot be evaluated. * * @param message the reason of the failure - * @param failure the detailed information about the failure + * @param diagnostic the detailed information about the failure */ case class VisualisationExpressionFailed( message: String, - failure: Option[ExecutionFailure] + diagnostic: Option[ExecutionDiagnostic] ) extends Failure /** Signals that an evaluation of a code responsible for generating diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ExecutionApi.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ExecutionApi.scala index 5e50976e23e5..3e4c7bc8c248 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ExecutionApi.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ExecutionApi.scala @@ -160,15 +160,15 @@ object ExecutionApi { case class VisualisationExpressionError( msg: String, - executionFailure: Option[ContextRegistryProtocol.ExecutionFailure] + diagnostic: Option[ContextRegistryProtocol.ExecutionDiagnostic] ) extends Error( 2007, s"Evaluation of the visualisation expression failed [$msg]" ) { override def payload = - executionFailure.map( - Encoder[ContextRegistryProtocol.ExecutionFailure].apply(_) + diagnostic.map( + Encoder[ContextRegistryProtocol.ExecutionDiagnostic].apply(_) ) } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/RuntimeFailureMapper.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/RuntimeFailureMapper.scala index ffe0cc718107..5d88a8586478 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/RuntimeFailureMapper.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/RuntimeFailureMapper.scala @@ -27,7 +27,7 @@ final class RuntimeFailureMapper(config: Config) { case Api.VisualisationExpressionFailed(message, result) => ContextRegistryProtocol.VisualisationExpressionFailed( message, - result.map(toProtocolError) + result.map(toProtocolDiagnostic) ) case Api.VisualisationNotFound() => ContextRegistryProtocol.VisualisationNotFound @@ -39,7 +39,7 @@ final class RuntimeFailureMapper(config: Config) { * @param error the error message * @return the registry protocol representation fo the diagnostic message */ - private def toProtocolError( + def toProtocolFailure( error: Api.ExecutionResult.Failure ): ContextRegistryProtocol.ExecutionFailure = ContextRegistryProtocol.ExecutionFailure( @@ -47,6 +47,56 @@ final class RuntimeFailureMapper(config: Config) { error.file.flatMap(config.findRelativePath) ) + /** Convert the runtime diagnostic message to the context registry protocol + * representation. + * + * @param diagnostic the diagnostic message + * @return the registry protocol representation of the diagnostic message + */ + def toProtocolDiagnostic( + diagnostic: Api.ExecutionResult.Diagnostic + ): ContextRegistryProtocol.ExecutionDiagnostic = + ContextRegistryProtocol.ExecutionDiagnostic( + toDiagnosticType(diagnostic.kind), + diagnostic.message, + diagnostic.file.flatMap(config.findRelativePath), + diagnostic.location, + diagnostic.expressionId, + diagnostic.stack.map(toStackTraceElement) + ) + + /** Convert the runtime diagnostic type to the context registry protocol + * representation. + * + * @param kind the diagnostic type + * @return the registry protocol representation of the diagnostic type + */ + private def toDiagnosticType( + kind: Api.DiagnosticType + ): ContextRegistryProtocol.ExecutionDiagnosticKind = + kind match { + case Api.DiagnosticType.Error() => + ContextRegistryProtocol.ExecutionDiagnosticKind.Error + case Api.DiagnosticType.Warning() => + ContextRegistryProtocol.ExecutionDiagnosticKind.Warning + } + + /** Convert the runtime stack trace element to the context registry protocol + * representation. + * + * @param element the runtime stack trace element + * @return the registry protocol representation of the stack trace element + */ + private def toStackTraceElement( + element: Api.StackTraceElement + ): ContextRegistryProtocol.ExecutionStackTraceElement = + ContextRegistryProtocol.ExecutionStackTraceElement( + element.functionName, + element.file.flatMap(config.findRelativePath), + element.location, + element.expressionId + ) + } object RuntimeFailureMapper { diff --git a/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala b/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala index 09bedefc4b4f..b9a8720c5a21 100644 --- a/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala +++ b/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala @@ -895,7 +895,7 @@ object Runtime { */ case class VisualisationExpressionFailed( message: String, - failure: Option[ExecutionResult.Failure] + failure: Option[ExecutionResult.Diagnostic] ) extends Error /** Signals that an evaluation of a code responsible for generating diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala b/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala index 1b4962bc04a4..3edc0f579707 100644 --- a/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala +++ b/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala @@ -258,7 +258,7 @@ object ProgramExecutionSupport { ) executionUpdate.getOrElse( Api.ExecutionResult - .Failure(s"Error in function $itemName.", None) + .Failure(s"Error in function $itemName. ${error.getMessage}", None) ) } diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/UpsertVisualisationJob.scala b/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/UpsertVisualisationJob.scala index ebcdfb3823f4..b838a4d63f23 100644 --- a/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/UpsertVisualisationJob.scala +++ b/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/UpsertVisualisationJob.scala @@ -102,7 +102,7 @@ class UpsertVisualisationJob( private def replyWithExpressionFailedError( message: String, - executionResult: Option[Api.ExecutionResult.Failure] + executionResult: Option[Api.ExecutionResult.Diagnostic] )(implicit ctx: RuntimeContext): Unit = { ctx.endpoint.sendToClient( Api.Response( @@ -141,7 +141,7 @@ class UpsertVisualisationJob( .leftMap { error => EvaluationFailed( error.getMessage, - ProgramExecutionSupport.getFailureOutcome.lift(error) + ProgramExecutionSupport.getDiagnosticOutcome.lift(error) ) } } @@ -167,7 +167,7 @@ object UpsertVisualisationJob { */ case class EvaluationFailed( message: String, - failure: Option[Api.ExecutionResult.Failure] + failure: Option[Api.ExecutionResult.Diagnostic] ) extends EvalFailure } diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala b/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala index 66ce4c651524..c77a5bbdf713 100644 --- a/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala +++ b/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala @@ -109,7 +109,7 @@ class RuntimeServerTest } def receive: Option[Api.Response] = { - Option(messageQueue.poll(10, TimeUnit.SECONDS)) + Option(messageQueue.poll(12, TimeUnit.SECONDS)) } def receive(n: Int): List[Api.Response] = { @@ -3821,6 +3821,80 @@ class RuntimeServerTest ) } + it should "return VisualisationExpressionFailed error when attaching visualisation" in { + val idMain = context.Main.metadata.addItem(78, 1) + val contents = context.Main.code + val mainFile = context.writeMain(context.Main.code) + val moduleName = "Test.Main" + + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + val visualisationId = UUID.randomUUID() + + // create context + context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) + context.receive shouldEqual Some( + Api.Response(requestId, Api.CreateContextResponse(contextId)) + ) + + // Open the new file + context.send( + Api.Request(Api.OpenFileNotification(mainFile, contents, true)) + ) + context.receiveNone shouldEqual None + + // push main + val item1 = Api.StackItem.ExplicitCall( + Api.MethodPointer(moduleName, "Test.Main", "main"), + None, + Vector() + ) + context.send( + Api.Request(requestId, Api.PushContextRequest(contextId, item1)) + ) + context.receive(6) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + context.Main.Update.mainX(contextId), + context.Main.Update.mainY(contextId), + context.Main.Update.mainZ(contextId), + TestMessages.update(contextId, idMain, Constants.INTEGER), + context.executionComplete(contextId) + ) + + // attach visualisation + context.send( + Api.Request( + requestId, + Api.AttachVisualisation( + visualisationId, + idMain, + Api.VisualisationConfiguration( + contextId, + "Test.Main", + "here.does_not_exist" + ) + ) + ) + ) + context.receive(1) should contain theSameElementsAs Seq( + Api.Response( + requestId, + Api.VisualisationExpressionFailed( + "Method `does_not_exist` of Main could not be found.", + Some( + Api.ExecutionResult.Diagnostic.error( + message = "Method `does_not_exist` of Main could not be found.", + stack = Vector( + Api.StackTraceElement("", None, None, None), + Api.StackTraceElement("Debug.eval", None, None, None) + ) + ) + ) + ) + ) + ) + } + it should "return visualisation evaluation errors with diagnostic info" in { val idMain = context.Main.metadata.addItem(78, 1) val contents = context.Main.code From 988dfd42494aeab4699aa8a39b8af669b7ade67d Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Thu, 15 Apr 2021 16:26:43 +0300 Subject: [PATCH 07/13] fix: error payload serialization --- .../protocol-language-server.md | 23 ++- .../languageserver/runtime/ExecutionApi.scala | 4 +- .../json/VisualisationOperationsTest.scala | 184 ++++++++++++++---- .../scala/org/enso/jsonrpc/JsonProtocol.scala | 2 +- .../org/enso/jsonrpc/MessageHandler.scala | 6 +- .../scala/org/enso/jsonrpc/Protocol.scala | 2 +- 6 files changed, 180 insertions(+), 41 deletions(-) diff --git a/docs/language-server/protocol-language-server.md b/docs/language-server/protocol-language-server.md index f4b6a5a04d8b..67d3827145f4 100644 --- a/docs/language-server/protocol-language-server.md +++ b/docs/language-server/protocol-language-server.md @@ -3502,6 +3502,9 @@ null; The language server component also has its own set of errors. This section is not a complete specification and will be updated as new errors are added. +Besides the required `code` and `message` fields, the errors may have a `data` +field which can store additional error-specific payload. + ### `AccessDeniedError` It signals that a user doesn't have access to a resource. @@ -3649,12 +3652,30 @@ It signals that the visualisation cannot be found. ### `VisualisationExpressionError` It signals that the expression specified in the `VisualisationConfiguration` -cannot be evaluated. +cannot be evaluated. The error contains an optional `data` field of type +[`Diagnostic`](#diagnostic) providing error details. ```typescript "error" : { "code" : 2007, "message" : "Evaluation of the visualisation expression failed [i is not defined]" + "data" : { + "kind" : "Error", + "message" : "i is not defined", + "path" : null, + "location" : { + "start" : { + "line" : 0, + "character" : 8 + }, + "end" : { + "line" : 0, + "character" : 9 + } + }, + "expressionId" : "aa1f75c4-8c4d-493d-a6a7-72123a52f084", + "stack" : [] + } } ``` diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ExecutionApi.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ExecutionApi.scala index 3e4c7bc8c248..a76c4e3e521b 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ExecutionApi.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ExecutionApi.scala @@ -2,7 +2,7 @@ package org.enso.languageserver.runtime import java.util.UUID -import io.circe.Encoder +import io.circe.{Encoder, Json} import io.circe.generic.auto._ import org.enso.jsonrpc.{Error, HasParams, HasResult, Method, Unused} import org.enso.languageserver.data.CapabilityRegistration @@ -166,7 +166,7 @@ object ExecutionApi { s"Evaluation of the visualisation expression failed [$msg]" ) { - override def payload = + override def payload: Option[Json] = diagnostic.map( Encoder[ContextRegistryProtocol.ExecutionDiagnostic].apply(_) ) diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/VisualisationOperationsTest.scala b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/VisualisationOperationsTest.scala index 1ff571595ffb..4caeba1fbb38 100644 --- a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/VisualisationOperationsTest.scala +++ b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/VisualisationOperationsTest.scala @@ -4,8 +4,7 @@ import java.util.UUID import io.circe.literal._ import org.enso.languageserver.runtime.VisualisationConfiguration import org.enso.polyglot.runtime.Runtime.Api - -import scala.annotation.nowarn +import org.enso.text.editing.model class VisualisationOperationsTest extends BaseServerTest { @@ -19,21 +18,14 @@ class VisualisationOperationsTest extends BaseServerTest { val contextId = createExecutionContext(client) val visualisationConfig = VisualisationConfiguration(contextId, "Foo.Bar.baz", "a=x+y") - client.send(json""" - { "jsonrpc": "2.0", - "method": "executionContext/attachVisualisation", - "id": 1, - "params": { - "visualisationId": $visualisationId, - "expressionId": $expressionId, - "visualisationConfig": { - "executionContextId": $contextId, - "visualisationModule": ${visualisationConfig.visualisationModule}, - "expression": ${visualisationConfig.expression} - } - } - } - """) + client.send( + ExecutionContextJsonMessages.executionContextAttachVisualisationRequest( + 1, + visualisationId, + expressionId, + visualisationConfig + ) + ) val requestId = runtimeConnectorProbe.receiveN(1).head match { @@ -68,21 +60,14 @@ class VisualisationOperationsTest extends BaseServerTest { val client = getInitialisedWsClient() val visualisationConfig = VisualisationConfiguration(contextId, "Foo.Bar.baz", "a=x+y") - client.send(json""" - { "jsonrpc": "2.0", - "method": "executionContext/attachVisualisation", - "id": 1, - "params": { - "visualisationId": $visualisationId, - "expressionId": $expressionId, - "visualisationConfig": { - "executionContextId": $contextId, - "visualisationModule": ${visualisationConfig.visualisationModule}, - "expression": ${visualisationConfig.expression} - } - } - } - """) + client.send( + ExecutionContextJsonMessages.executionContextAttachVisualisationRequest( + 1, + visualisationId, + expressionId, + visualisationConfig + ) + ) client.expectJson(json""" { "jsonrpc": "2.0", "id" : 1, @@ -94,6 +79,138 @@ class VisualisationOperationsTest extends BaseServerTest { """) } + "reply with ModuleNotFound error when attaching a visualisation" in { + val visualisationId = UUID.randomUUID() + val expressionId = UUID.randomUUID() + + val client = getInitialisedWsClient() + val contextId = createExecutionContext(client) + val visualisationConfig = + VisualisationConfiguration(contextId, "Foo.Bar", "_") + client.send( + ExecutionContextJsonMessages.executionContextAttachVisualisationRequest( + 1, + visualisationId, + expressionId, + visualisationConfig + ) + ) + + val requestId = + runtimeConnectorProbe.receiveN(1).head match { + case Api.Request( + requestId, + Api.AttachVisualisation( + `visualisationId`, + `expressionId`, + config + ) + ) => + config.expression shouldBe visualisationConfig.expression + config.visualisationModule shouldBe visualisationConfig.visualisationModule + config.executionContextId shouldBe visualisationConfig.executionContextId + requestId + + case msg => + fail(s"Unexpected message: $msg") + } + + runtimeConnectorProbe.lastSender ! Api.Response( + requestId, + Api.ModuleNotFound(visualisationConfig.visualisationModule) + ) + client.expectJson( + ExecutionContextJsonMessages.executionContextModuleNotFound( + 1, + visualisationConfig.visualisationModule + ) + ) + } + + "reply with VisualisationExpressionFailed error when attaching a visualisation" in { + val visualisationId = UUID.randomUUID() + val expressionId = UUID.randomUUID() + + val client = getInitialisedWsClient() + val contextId = createExecutionContext(client) + val visualisationConfig = + VisualisationConfiguration(contextId, "Foo.Bar", "_") + val expressionFailureMessage = "Method `to_json` could not be found." + client.send( + ExecutionContextJsonMessages.executionContextAttachVisualisationRequest( + 1, + visualisationId, + expressionId, + visualisationConfig + ) + ) + + val requestId = + runtimeConnectorProbe.receiveN(1).head match { + case Api.Request( + requestId, + Api.AttachVisualisation( + `visualisationId`, + `expressionId`, + config + ) + ) => + config.expression shouldBe visualisationConfig.expression + config.visualisationModule shouldBe visualisationConfig.visualisationModule + config.executionContextId shouldBe visualisationConfig.executionContextId + requestId + + case msg => + fail(s"Unexpected message: $msg") + } + + runtimeConnectorProbe.lastSender ! Api.Response( + requestId, + Api.VisualisationExpressionFailed( + expressionFailureMessage, + Some( + Api.ExecutionResult.Diagnostic.error( + expressionFailureMessage, + location = Some( + model.Range(model.Position(0, 0), model.Position(0, 15)) + ), + expressionId = Some(expressionId) + ) + ) + ) + ) + val errorMessage = + s"Evaluation of the visualisation expression failed [$expressionFailureMessage]" + client.expectJson( + json""" + { "jsonrpc" : "2.0", + "id" : 1, + "error" : { + "code" : 2007, + "message" : $errorMessage, + "data": { + "kind" : "Error", + "message" : $expressionFailureMessage, + "path" : null, + "location" : { + "start" : { + "line" : 0, + "character" : 0 + }, + "end" : { + "line" : 0, + "character" : 15 + } + }, + "expressionId" : $expressionId, + "stack" : [] + } + } + } + """ + ) + } + } "executionContext/detachVisualisation" must { @@ -142,9 +259,6 @@ class VisualisationOperationsTest extends BaseServerTest { val expressionId = UUID.randomUUID() val contextId = UUID.randomUUID() val client = getInitialisedWsClient() - @nowarn("cat=unused-locals") - val visualisationConfig = - VisualisationConfiguration(contextId, "Foo.Bar.baz", "a=x+y") client.send(json""" { "jsonrpc": "2.0", "method": "executionContext/detachVisualisation", diff --git a/lib/scala/json-rpc-server/src/main/scala/org/enso/jsonrpc/JsonProtocol.scala b/lib/scala/json-rpc-server/src/main/scala/org/enso/jsonrpc/JsonProtocol.scala index ac3c09308e70..d0a7d0e4d61b 100644 --- a/lib/scala/json-rpc-server/src/main/scala/org/enso/jsonrpc/JsonProtocol.scala +++ b/lib/scala/json-rpc-server/src/main/scala/org/enso/jsonrpc/JsonProtocol.scala @@ -31,7 +31,7 @@ object JsonProtocol { case class ResponseError(id: Option[Id], error: ErrorData) extends JsonMessage /** The error response details. */ - case class ErrorData(code: Int, message: String, data: Option[Json] = None) + case class ErrorData(code: Int, message: String, data: Option[Json]) object Constants { val jsonrpc: String = "jsonrpc" diff --git a/lib/scala/json-rpc-server/src/main/scala/org/enso/jsonrpc/MessageHandler.scala b/lib/scala/json-rpc-server/src/main/scala/org/enso/jsonrpc/MessageHandler.scala index c08eb8986f3c..701b0ab13e2a 100644 --- a/lib/scala/json-rpc-server/src/main/scala/org/enso/jsonrpc/MessageHandler.scala +++ b/lib/scala/json-rpc-server/src/main/scala/org/enso/jsonrpc/MessageHandler.scala @@ -63,7 +63,11 @@ class MessageHandler(val protocol: Protocol, val controller: ActorRef) webConnection: ActorRef ): Unit = { val bareError = - JsonProtocol.ErrorData(response.error.code, response.error.message) + JsonProtocol.ErrorData( + response.error.code, + response.error.message, + response.error.payload + ) val bareResponse = JsonProtocol.ResponseError(response.id, bareError) webConnection ! MessageHandler.WebMessage(JsonProtocol.encode(bareResponse)) } diff --git a/lib/scala/json-rpc-server/src/main/scala/org/enso/jsonrpc/Protocol.scala b/lib/scala/json-rpc-server/src/main/scala/org/enso/jsonrpc/Protocol.scala index 39332b26262f..9126137dd8c3 100644 --- a/lib/scala/json-rpc-server/src/main/scala/org/enso/jsonrpc/Protocol.scala +++ b/lib/scala/json-rpc-server/src/main/scala/org/enso/jsonrpc/Protocol.scala @@ -113,7 +113,7 @@ object Errors { case class UnknownError( override val code: Int, override val message: String, - override val payload: Option[Json] = None + override val payload: Option[Json] ) extends Error(code, message) } From e70ea6f21dfa68c23ea8c776bda14fe43c1c50e3 Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Thu, 15 Apr 2021 16:31:06 +0300 Subject: [PATCH 08/13] misc: cleanup --- .../enso/interpreter/test/instrument/RuntimeServerTest.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala b/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala index c77a5bbdf713..eb0ec352a28d 100644 --- a/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala +++ b/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala @@ -109,7 +109,7 @@ class RuntimeServerTest } def receive: Option[Api.Response] = { - Option(messageQueue.poll(12, TimeUnit.SECONDS)) + Option(messageQueue.poll(10, TimeUnit.SECONDS)) } def receive(n: Int): List[Api.Response] = { From 84762415775fc9424b039ed7840d28666f5983ba Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Thu, 15 Apr 2021 17:26:38 +0300 Subject: [PATCH 09/13] test: fix project-manager tests --- .../protocol/ProjectCreateDefaultToLatestSpec.scala | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/scala/project-manager/src/test/scala/org/enso/projectmanager/protocol/ProjectCreateDefaultToLatestSpec.scala b/lib/scala/project-manager/src/test/scala/org/enso/projectmanager/protocol/ProjectCreateDefaultToLatestSpec.scala index c84781ec5db6..7d7bc368d497 100644 --- a/lib/scala/project-manager/src/test/scala/org/enso/projectmanager/protocol/ProjectCreateDefaultToLatestSpec.scala +++ b/lib/scala/project-manager/src/test/scala/org/enso/projectmanager/protocol/ProjectCreateDefaultToLatestSpec.scala @@ -29,7 +29,13 @@ class ProjectCreateDefaultToLatestSpec extends BaseServerSpec { { "jsonrpc":"2.0", "id":1, - "error": { "code": 4022, "message": $message } + "error": { + "code": 4022, + "message": $message, + "data" : { + "minimumRequiredVersion" : "999.0.0" + } + } } """) } From 013572979f6c6d3a1e925c42dc0beb2f98130522 Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Thu, 15 Apr 2021 22:56:49 +0300 Subject: [PATCH 10/13] test: fix windows newline --- .../enso/interpreter/test/instrument/RuntimeServerTest.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala b/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala index eb0ec352a28d..461c84b918d5 100644 --- a/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala +++ b/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala @@ -3992,7 +3992,7 @@ class RuntimeServerTest |encode x = x.visualise_me | |inc_and_encode x = here.encode x+1 - |""".stripMargin + |""".stripMargin.linesIterator.mkString("\n") val visualisationFile = context.writeInSrcDir("Visualisation", visualisationCode) From 573f0ac2a8af71eb7ac7a4b3a56819ec5b2fe9b8 Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Wed, 21 Apr 2021 12:33:16 +0300 Subject: [PATCH 11/13] fix: null error message --- .../interpreter/instrument/job/ProgramExecutionSupport.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala b/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala index 3edc0f579707..541ab84e031d 100644 --- a/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala +++ b/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala @@ -448,13 +448,15 @@ object ProgramExecutionSupport { } errorMsgOrVisualisationData match { case Left(error) => + val message = + Option(error.getMessage).getOrElse(error.getClass.getSimpleName) ctx.endpoint.sendToClient( Api.Response( Api.VisualisationEvaluationFailed( contextId, visualisation.id, expressionId, - error.getMessage, + message, getDiagnosticOutcome.lift(error) ) ) From cc4098f7435b48bf71017bc92c914bfb01aabf31 Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Wed, 21 Apr 2021 12:37:37 +0300 Subject: [PATCH 12/13] misc: getLanguage comment --- .../interpreter/instrument/job/ProgramExecutionSupport.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala b/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala index 541ab84e031d..158489e9c97b 100644 --- a/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala +++ b/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala @@ -278,6 +278,8 @@ object ProgramExecutionSupport { ctx: RuntimeContext ): PartialFunction[Throwable, Api.ExecutionResult.Diagnostic] = { case ex: AbstractTruffleException + // The empty language is allowed because `getLanguage` return null when + // the error originates in builtin node. if Option(ctx.executionService.getLanguage(ex)) .forall(_ == LanguageInfo.ID) => val section = Option(ctx.executionService.getSourceLocation(ex)) From ca2f2426a3132a48cd16d6127249c77bfa534cf7 Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Wed, 21 Apr 2021 13:42:09 +0300 Subject: [PATCH 13/13] Update engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Radosław Waśko --- .../interpreter/instrument/job/ProgramExecutionSupport.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala b/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala index 158489e9c97b..047e5452a548 100644 --- a/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala +++ b/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala @@ -278,7 +278,7 @@ object ProgramExecutionSupport { ctx: RuntimeContext ): PartialFunction[Throwable, Api.ExecutionResult.Diagnostic] = { case ex: AbstractTruffleException - // The empty language is allowed because `getLanguage` return null when + // The empty language is allowed because `getLanguage` returns null when // the error originates in builtin node. if Option(ctx.executionService.getLanguage(ex)) .forall(_ == LanguageInfo.ID) =>