mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 18:34:03 +03:00
Truffle Integration for the Binary Protocol (#711)
This commit is contained in:
parent
7c7bd3e6ae
commit
29190f8339
@ -2552,7 +2552,16 @@ null
|
||||
```
|
||||
|
||||
##### Errors
|
||||
TBC
|
||||
- [`AccessDeniedError`](#accessdeniederror) when the user does not hold the
|
||||
`executionContext/canModify` capability for this context.
|
||||
- [`ContextNotFoundError`](#contextnotfounderror) when context can not be found
|
||||
by provided id.
|
||||
- [`ModuleNotFoundError`](#modulenotfounderror) to signal that the module with
|
||||
the visualisation cannot be found.
|
||||
- [`VisualisationExpressionError`](#visualisationexpressionerror) to signal that
|
||||
the expression specified in the `VisualisationConfiguration` cannot be
|
||||
evaluated.
|
||||
|
||||
|
||||
#### `executionContext/detachVisualisation`
|
||||
This message allows a client to detach a visualisation from the executing code.
|
||||
@ -2568,6 +2577,7 @@ This message allows a client to detach a visualisation from the executing code.
|
||||
interface DetachVisualisationRequest {
|
||||
executionContextId: UUID;
|
||||
visualisationId: UUID;
|
||||
expressionId: UUID;
|
||||
}
|
||||
```
|
||||
|
||||
@ -2578,7 +2588,12 @@ null
|
||||
```
|
||||
|
||||
##### Errors
|
||||
TBC
|
||||
- [`AccessDeniedError`](#accessdeniederror) when the user does not hold the
|
||||
`executionContext/canModify` capability for this context.
|
||||
- [`ContextNotFoundError`](#contextnotfounderror) when context can not be found
|
||||
by provided id.
|
||||
- [`VisualisationNotFoundError`](#visualisationnotfounderror) when a
|
||||
visualisation can not be found.
|
||||
|
||||
#### `executionContext/modifyVisualisation`
|
||||
This message allows a client to modify the configuration for an existing
|
||||
@ -2605,7 +2620,17 @@ null
|
||||
```
|
||||
|
||||
##### Errors
|
||||
TBC
|
||||
- [`AccessDeniedError`](#accessdeniederror) when the user does not hold the
|
||||
`executionContext/canModify` capability for this context.
|
||||
- [`ContextNotFoundError`](#contextnotfounderror) when context can not be found
|
||||
by provided id.
|
||||
- [`ModuleNotFoundError`](#modulenotfounderror) to signal that the module with
|
||||
the visualisation cannot be found.
|
||||
- [`VisualisationExpressionError`](#visualisationexpressionerror) to signal that
|
||||
the expression specified in the `VisualisationConfiguration` cannot be
|
||||
evaluated.
|
||||
- [`VisualisationNotFoundError`](#visualisationnotfounderror) when a
|
||||
visualisation can not be found.
|
||||
|
||||
#### `executionContext/visualisationUpdate`
|
||||
This message is responsible for providing a visualisation data update to the
|
||||
@ -2771,6 +2796,48 @@ It signals that stack is invalid in this context.
|
||||
}
|
||||
```
|
||||
|
||||
##### `ModuleNotFoundError`
|
||||
It signals that the given module cannot be found.
|
||||
|
||||
```typescript
|
||||
"error" : {
|
||||
"code" : 2005,
|
||||
"message" : "Module not found [Foo.Bar.Baz]"
|
||||
}
|
||||
```
|
||||
|
||||
##### `VisualisationNotFoundError`
|
||||
It signals that the visualisation cannot be found.
|
||||
|
||||
```typescript
|
||||
"error" : {
|
||||
"code" : 2006,
|
||||
"message" : "Visualisation not found"
|
||||
}
|
||||
```
|
||||
|
||||
##### `VisualisationExpressionError`
|
||||
It signals that the expression specified in the `VisualisationConfiguration`
|
||||
cannot be evaluated.
|
||||
|
||||
```typescript
|
||||
"error" : {
|
||||
"code" : 2007,
|
||||
"message" : "Evaluation of the visualisation expression failed [i is not defined]"
|
||||
}
|
||||
```
|
||||
|
||||
##### `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.
|
||||
|
||||
|
@ -28,7 +28,7 @@ import org.enso.languageserver.protocol.data.factory.{
|
||||
}
|
||||
import org.enso.languageserver.protocol.data.session.SessionInit
|
||||
import org.enso.languageserver.protocol.data.util.EnsoUUID
|
||||
import org.enso.languageserver.runtime.VisualisationProtocol.VisualisationUpdate
|
||||
import org.enso.languageserver.runtime.ContextRegistryProtocol.VisualisationUpdate
|
||||
import org.enso.languageserver.session.DataSession
|
||||
import org.enso.languageserver.util.UnhandledLogging
|
||||
import org.enso.languageserver.util.binary.DecodingFailure
|
||||
|
@ -2,7 +2,7 @@ package org.enso.languageserver.protocol.data.factory
|
||||
|
||||
import com.google.flatbuffers.FlatBufferBuilder
|
||||
import org.enso.languageserver.protocol.data.executioncontext
|
||||
import org.enso.languageserver.runtime.VisualisationProtocol.{
|
||||
import org.enso.languageserver.runtime.ContextRegistryProtocol.{
|
||||
VisualisationContext,
|
||||
VisualisationUpdate
|
||||
}
|
||||
|
@ -8,8 +8,7 @@ import org.enso.languageserver.requesthandler.RequestTimeout
|
||||
import org.enso.languageserver.runtime.VisualisationApi.AttachVisualisation
|
||||
import org.enso.languageserver.runtime.{
|
||||
ContextRegistryProtocol,
|
||||
RuntimeFailureMapper,
|
||||
VisualisationProtocol
|
||||
RuntimeFailureMapper
|
||||
}
|
||||
import org.enso.languageserver.util.UnhandledLogging
|
||||
|
||||
@ -36,7 +35,7 @@ class AttachVisualisationHandler(
|
||||
|
||||
private def requestStage: Receive = {
|
||||
case Request(AttachVisualisation, id, params: AttachVisualisation.Params) =>
|
||||
contextRegistry ! VisualisationProtocol.AttachVisualisation(
|
||||
contextRegistry ! ContextRegistryProtocol.AttachVisualisation(
|
||||
clientId,
|
||||
params.visualisationId,
|
||||
params.expressionId,
|
||||
@ -57,7 +56,7 @@ class AttachVisualisationHandler(
|
||||
replyTo ! ResponseError(Some(id), ServiceError)
|
||||
context.stop(self)
|
||||
|
||||
case VisualisationProtocol.VisualisationAttached =>
|
||||
case ContextRegistryProtocol.VisualisationAttached =>
|
||||
replyTo ! ResponseResult(AttachVisualisation, id, Unused)
|
||||
cancellable.cancel()
|
||||
context.stop(self)
|
||||
|
@ -8,8 +8,7 @@ import org.enso.languageserver.requesthandler.RequestTimeout
|
||||
import org.enso.languageserver.runtime.VisualisationApi.DetachVisualisation
|
||||
import org.enso.languageserver.runtime.{
|
||||
ContextRegistryProtocol,
|
||||
RuntimeFailureMapper,
|
||||
VisualisationProtocol
|
||||
RuntimeFailureMapper
|
||||
}
|
||||
import org.enso.languageserver.util.UnhandledLogging
|
||||
|
||||
@ -36,7 +35,7 @@ class DetachVisualisationHandler(
|
||||
|
||||
private def requestStage: Receive = {
|
||||
case Request(DetachVisualisation, id, params: DetachVisualisation.Params) =>
|
||||
contextRegistry ! VisualisationProtocol.DetachVisualisation(
|
||||
contextRegistry ! ContextRegistryProtocol.DetachVisualisation(
|
||||
clientId,
|
||||
params.contextId,
|
||||
params.visualisationId,
|
||||
@ -57,7 +56,7 @@ class DetachVisualisationHandler(
|
||||
replyTo ! ResponseError(Some(id), ServiceError)
|
||||
context.stop(self)
|
||||
|
||||
case VisualisationProtocol.VisualisationDetached =>
|
||||
case ContextRegistryProtocol.VisualisationDetached =>
|
||||
replyTo ! ResponseResult(DetachVisualisation, id, Unused)
|
||||
cancellable.cancel()
|
||||
context.stop(self)
|
||||
|
@ -8,8 +8,7 @@ import org.enso.languageserver.requesthandler.RequestTimeout
|
||||
import org.enso.languageserver.runtime.VisualisationApi.ModifyVisualisation
|
||||
import org.enso.languageserver.runtime.{
|
||||
ContextRegistryProtocol,
|
||||
RuntimeFailureMapper,
|
||||
VisualisationProtocol
|
||||
RuntimeFailureMapper
|
||||
}
|
||||
import org.enso.languageserver.util.UnhandledLogging
|
||||
|
||||
@ -36,7 +35,7 @@ class ModifyVisualisationHandler(
|
||||
|
||||
private def requestStage: Receive = {
|
||||
case Request(ModifyVisualisation, id, params: ModifyVisualisation.Params) =>
|
||||
contextRegistry ! VisualisationProtocol.ModifyVisualisation(
|
||||
contextRegistry ! ContextRegistryProtocol.ModifyVisualisation(
|
||||
clientId,
|
||||
params.visualisationId,
|
||||
params.visualisationConfig
|
||||
@ -56,7 +55,7 @@ class ModifyVisualisationHandler(
|
||||
replyTo ! ResponseError(Some(id), ServiceError)
|
||||
context.stop(self)
|
||||
|
||||
case VisualisationProtocol.VisualisationModified =>
|
||||
case ContextRegistryProtocol.VisualisationModified =>
|
||||
replyTo ! ResponseResult(ModifyVisualisation, id, Unused)
|
||||
cancellable.cancel()
|
||||
context.stop(self)
|
||||
|
@ -2,11 +2,11 @@ package org.enso.languageserver.runtime
|
||||
|
||||
import akka.actor.{Actor, ActorLogging, ActorRef, Props}
|
||||
import org.enso.languageserver.data.Config
|
||||
import org.enso.languageserver.runtime.ExecutionApi.ContextId
|
||||
import org.enso.languageserver.runtime.VisualisationProtocol.{
|
||||
import org.enso.languageserver.runtime.ContextRegistryProtocol.{
|
||||
VisualisationContext,
|
||||
VisualisationUpdate
|
||||
}
|
||||
import org.enso.languageserver.runtime.ExecutionApi.ContextId
|
||||
import org.enso.languageserver.session.RpcSession
|
||||
import org.enso.languageserver.session.SessionRouter.DeliverToDataController
|
||||
import org.enso.languageserver.util.UnhandledLogging
|
||||
|
@ -6,11 +6,6 @@ import akka.actor.{Actor, ActorLogging, ActorRef, Props}
|
||||
import org.enso.languageserver.data.{ClientId, Config}
|
||||
import org.enso.languageserver.filemanager.FileSystemFailure
|
||||
import org.enso.languageserver.monitoring.MonitoringProtocol.{Ping, Pong}
|
||||
import org.enso.languageserver.runtime.VisualisationProtocol.{
|
||||
AttachVisualisation,
|
||||
DetachVisualisation,
|
||||
ModifyVisualisation
|
||||
}
|
||||
import org.enso.languageserver.runtime.handler._
|
||||
import org.enso.languageserver.util.UnhandledLogging
|
||||
import org.enso.polyglot.runtime.Runtime.Api
|
||||
|
@ -1,5 +1,8 @@
|
||||
package org.enso.languageserver.runtime
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
import org.enso.languageserver.data.ClientId
|
||||
import org.enso.languageserver.filemanager.FileSystemFailure
|
||||
import org.enso.languageserver.runtime.ExecutionApi.ContextId
|
||||
import org.enso.languageserver.session.RpcSession
|
||||
@ -139,4 +142,119 @@ object ContextRegistryProtocol {
|
||||
* @param contextId execution context identifier
|
||||
*/
|
||||
case class InvalidStackItemError(contextId: ContextId) extends Failure
|
||||
|
||||
/**
|
||||
* Requests the language server to attach a visualisation to the expression
|
||||
* specified by `expressionId`.
|
||||
*
|
||||
* @param clientId the requester id
|
||||
* @param visualisationId an identifier of a visualisation
|
||||
* @param expressionId an identifier of an expression which is visualised
|
||||
* @param visualisationConfig a configuration object for properties of the
|
||||
* visualisation
|
||||
*/
|
||||
case class AttachVisualisation(
|
||||
clientId: ClientId,
|
||||
visualisationId: UUID,
|
||||
expressionId: UUID,
|
||||
visualisationConfig: VisualisationConfiguration
|
||||
)
|
||||
|
||||
/**
|
||||
* Signals that attaching a visualisation has succeeded.
|
||||
*/
|
||||
case object VisualisationAttached
|
||||
|
||||
/**
|
||||
* Requests the language server to detach a visualisation from the expression
|
||||
* specified by `expressionId`.
|
||||
*
|
||||
* @param clientId the requester id
|
||||
* @param contextId an execution context identifier
|
||||
* @param visualisationId an identifier of a visualisation
|
||||
* @param expressionId an identifier of an expression which is visualised
|
||||
*/
|
||||
case class DetachVisualisation(
|
||||
clientId: ClientId,
|
||||
contextId: UUID,
|
||||
visualisationId: UUID,
|
||||
expressionId: UUID
|
||||
)
|
||||
|
||||
/**
|
||||
* Signals that detaching a visualisation has succeeded.
|
||||
*/
|
||||
case object VisualisationDetached
|
||||
|
||||
/**
|
||||
* Requests the language server to modify a visualisation.
|
||||
*
|
||||
* @param clientId the requester id
|
||||
* @param visualisationId an identifier of a visualisation
|
||||
* @param visualisationConfig a configuration object for properties of the
|
||||
* visualisation
|
||||
*/
|
||||
case class ModifyVisualisation(
|
||||
clientId: ClientId,
|
||||
visualisationId: UUID,
|
||||
visualisationConfig: VisualisationConfiguration
|
||||
)
|
||||
|
||||
/**
|
||||
* Signals that a visualisation modification has succeeded.
|
||||
*/
|
||||
case object VisualisationModified
|
||||
|
||||
/**
|
||||
* Represents a visualisation context.
|
||||
*
|
||||
* @param visualisationId a visualisation identifier
|
||||
* @param contextId a context identifier
|
||||
* @param expressionId an expression identifier
|
||||
*/
|
||||
case class VisualisationContext(
|
||||
visualisationId: UUID,
|
||||
contextId: UUID,
|
||||
expressionId: UUID
|
||||
)
|
||||
|
||||
/**
|
||||
* An event signaling a visualisation update.
|
||||
*
|
||||
* @param visualisationContext a visualisation context
|
||||
* @param data a visualisation data
|
||||
*/
|
||||
case class VisualisationUpdate(
|
||||
visualisationContext: VisualisationContext,
|
||||
data: Array[Byte]
|
||||
)
|
||||
|
||||
/**
|
||||
* Signals that a module cannot be found.
|
||||
*
|
||||
* @param moduleName the module name
|
||||
*/
|
||||
case class ModuleNotFound(moduleName: String) extends Failure
|
||||
|
||||
/**
|
||||
* Signals that visualisation cannot be found.
|
||||
*/
|
||||
case object VisualisationNotFound extends Failure
|
||||
|
||||
/**
|
||||
* Signals that an expression specified in a [[AttachVisualisation]] or
|
||||
* a [[ModifyVisualisation]] cannot be evaluated.
|
||||
*
|
||||
* @param message the reason of the failure
|
||||
*/
|
||||
case class VisualisationExpressionFailed(message: String) extends Failure
|
||||
|
||||
/**
|
||||
* Signals that an evaluation of a code responsible for generating
|
||||
* visualisation data failed.
|
||||
*
|
||||
* @param message the reason of the failure
|
||||
*/
|
||||
case class VisualisationEvaluationFailed(message: String) extends Failure
|
||||
|
||||
}
|
||||
|
@ -104,4 +104,18 @@ object ExecutionApi {
|
||||
|
||||
case object InvalidStackItemError extends Error(2004, "Invalid stack item")
|
||||
|
||||
case class ModuleNotFoundError(moduleName: String)
|
||||
extends Error(2005, s"Module not found [$moduleName]")
|
||||
|
||||
case object VisualisationNotFoundError
|
||||
extends Error(2006, s"Visualisation not found")
|
||||
|
||||
case class VisualisationExpressionError(msg: String)
|
||||
extends Error(
|
||||
2007,
|
||||
s"Evaluation of the visualisation expression failed [$msg]"
|
||||
)
|
||||
case class VisualisationEvaluationError(msg: String)
|
||||
extends Error(2008, s"Evaluation of the visualisation failed [$msg]")
|
||||
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
package org.enso.languageserver.runtime
|
||||
|
||||
import org.enso.jsonrpc._
|
||||
import org.enso.languageserver.filemanager.FileSystemFailureMapper
|
||||
import org.enso.languageserver.protocol.rpc.ErrorApi._
|
||||
import org.enso.languageserver.runtime.ExecutionApi._
|
||||
import org.enso.polyglot.runtime.Runtime.Api
|
||||
import org.enso.jsonrpc.Error
|
||||
|
||||
object RuntimeFailureMapper {
|
||||
|
||||
@ -26,6 +26,14 @@ object RuntimeFailureMapper {
|
||||
EmptyStackError
|
||||
case ContextRegistryProtocol.InvalidStackItemError(_) =>
|
||||
InvalidStackItemError
|
||||
case ContextRegistryProtocol.VisualisationNotFound =>
|
||||
VisualisationNotFoundError
|
||||
case ContextRegistryProtocol.ModuleNotFound(name) =>
|
||||
ModuleNotFoundError(name)
|
||||
case ContextRegistryProtocol.VisualisationExpressionFailed(msg) =>
|
||||
VisualisationExpressionError(msg)
|
||||
case ContextRegistryProtocol.VisualisationEvaluationFailed(msg) =>
|
||||
VisualisationEvaluationError(msg)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -42,6 +50,14 @@ object RuntimeFailureMapper {
|
||||
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.VisualisationEvaluationFailed(message: String) =>
|
||||
ContextRegistryProtocol.VisualisationEvaluationFailed(message)
|
||||
case Api.VisualisationNotFound() =>
|
||||
ContextRegistryProtocol.VisualisationNotFound
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,95 +0,0 @@
|
||||
package org.enso.languageserver.runtime
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
import org.enso.languageserver.data.ClientId
|
||||
|
||||
object VisualisationProtocol {
|
||||
|
||||
/**
|
||||
* Requests the language server to attach a visualisation to the expression
|
||||
* specified by `expressionId`.
|
||||
*
|
||||
* @param clientId the requester id
|
||||
* @param visualisationId an identifier of a visualisation
|
||||
* @param expressionId an identifier of an expression which is visualised
|
||||
* @param visualisationConfig a configuration object for properties of the
|
||||
* visualisation
|
||||
*/
|
||||
case class AttachVisualisation(
|
||||
clientId: ClientId,
|
||||
visualisationId: UUID,
|
||||
expressionId: UUID,
|
||||
visualisationConfig: VisualisationConfiguration
|
||||
)
|
||||
|
||||
/**
|
||||
* Signals that attaching a visualisation has succeeded.
|
||||
*/
|
||||
case object VisualisationAttached
|
||||
|
||||
/**
|
||||
* Requests the language server to detach a visualisation from the expression
|
||||
* specified by `expressionId`.
|
||||
*
|
||||
* @param clientId the requester id
|
||||
* @param contextId an execution context identifier
|
||||
* @param visualisationId an identifier of a visualisation
|
||||
* @param expressionId an identifier of an expression which is visualised
|
||||
*/
|
||||
case class DetachVisualisation(
|
||||
clientId: ClientId,
|
||||
contextId: UUID,
|
||||
visualisationId: UUID,
|
||||
expressionId: UUID
|
||||
)
|
||||
|
||||
/**
|
||||
* Signals that detaching a visualisation has succeeded.
|
||||
*/
|
||||
case object VisualisationDetached
|
||||
|
||||
/**
|
||||
* Requests the language server to modify a visualisation.
|
||||
*
|
||||
* @param clientId the requester id
|
||||
* @param visualisationId an identifier of a visualisation
|
||||
* @param visualisationConfig a configuration object for properties of the
|
||||
* visualisation
|
||||
*/
|
||||
case class ModifyVisualisation(
|
||||
clientId: ClientId,
|
||||
visualisationId: UUID,
|
||||
visualisationConfig: VisualisationConfiguration
|
||||
)
|
||||
|
||||
/**
|
||||
* Signals that a visualisation modification has succeeded.
|
||||
*/
|
||||
case object VisualisationModified
|
||||
|
||||
/**
|
||||
* Represents a visualisation context.
|
||||
*
|
||||
* @param visualisationId a visualisation identifier
|
||||
* @param contextId a context identifier
|
||||
* @param expressionId an expression identifier
|
||||
*/
|
||||
case class VisualisationContext(
|
||||
visualisationId: UUID,
|
||||
contextId: UUID,
|
||||
expressionId: UUID
|
||||
)
|
||||
|
||||
/**
|
||||
* An event signaling a visualisation update.
|
||||
*
|
||||
* @param visualisationContext a visualisation context
|
||||
* @param data a visualisation data
|
||||
*/
|
||||
case class VisualisationUpdate(
|
||||
visualisationContext: VisualisationContext,
|
||||
data: Array[Byte]
|
||||
)
|
||||
|
||||
}
|
@ -5,8 +5,8 @@ import java.util.UUID
|
||||
import akka.actor.{Actor, ActorLogging, ActorRef, Cancellable, Props}
|
||||
import org.enso.languageserver.requesthandler.RequestTimeout
|
||||
import org.enso.languageserver.runtime.{
|
||||
RuntimeFailureMapper,
|
||||
VisualisationProtocol
|
||||
ContextRegistryProtocol,
|
||||
RuntimeFailureMapper
|
||||
}
|
||||
import org.enso.languageserver.util.UnhandledLogging
|
||||
import org.enso.polyglot.runtime.Runtime.Api
|
||||
@ -47,7 +47,7 @@ class AttachVisualisationHandler(
|
||||
context.stop(self)
|
||||
|
||||
case Api.Response(_, Api.VisualisationAttached()) =>
|
||||
replyTo ! VisualisationProtocol.VisualisationAttached
|
||||
replyTo ! ContextRegistryProtocol.VisualisationAttached
|
||||
cancellable.cancel()
|
||||
context.stop(self)
|
||||
|
||||
|
@ -5,8 +5,8 @@ import java.util.UUID
|
||||
import akka.actor.{Actor, ActorLogging, ActorRef, Cancellable, Props}
|
||||
import org.enso.languageserver.requesthandler.RequestTimeout
|
||||
import org.enso.languageserver.runtime.{
|
||||
RuntimeFailureMapper,
|
||||
VisualisationProtocol
|
||||
ContextRegistryProtocol,
|
||||
RuntimeFailureMapper
|
||||
}
|
||||
import org.enso.languageserver.util.UnhandledLogging
|
||||
import org.enso.polyglot.runtime.Runtime.Api
|
||||
@ -47,7 +47,7 @@ class DetachVisualisationHandler(
|
||||
context.stop(self)
|
||||
|
||||
case Api.Response(_, Api.VisualisationDetached()) =>
|
||||
replyTo ! VisualisationProtocol.VisualisationDetached
|
||||
replyTo ! ContextRegistryProtocol.VisualisationDetached
|
||||
cancellable.cancel()
|
||||
context.stop(self)
|
||||
|
||||
|
@ -5,8 +5,8 @@ import java.util.UUID
|
||||
import akka.actor.{Actor, ActorLogging, ActorRef, Cancellable, Props}
|
||||
import org.enso.languageserver.requesthandler.RequestTimeout
|
||||
import org.enso.languageserver.runtime.{
|
||||
RuntimeFailureMapper,
|
||||
VisualisationProtocol
|
||||
ContextRegistryProtocol,
|
||||
RuntimeFailureMapper
|
||||
}
|
||||
import org.enso.languageserver.util.UnhandledLogging
|
||||
import org.enso.polyglot.runtime.Runtime.Api
|
||||
@ -47,7 +47,7 @@ class ModifyVisualisationHandler(
|
||||
context.stop(self)
|
||||
|
||||
case Api.Response(_, Api.VisualisationModified()) =>
|
||||
replyTo ! VisualisationProtocol.VisualisationModified
|
||||
replyTo ! ContextRegistryProtocol.VisualisationModified
|
||||
cancellable.cancel()
|
||||
context.stop(self)
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
include "util.fbs";
|
||||
include "session.fbs";
|
||||
include "execution-context.fbs";
|
||||
include "execution_context.fbs";
|
||||
|
||||
namespace org.enso.languageserver.protocol.data.envelope;
|
||||
|
||||
|
@ -10,7 +10,7 @@ import org.enso.languageserver.protocol.data.envelope.{
|
||||
OutboundPayload
|
||||
}
|
||||
import org.enso.languageserver.protocol.data.executioncontext
|
||||
import org.enso.languageserver.runtime.VisualisationProtocol.{
|
||||
import org.enso.languageserver.runtime.ContextRegistryProtocol.{
|
||||
VisualisationContext,
|
||||
VisualisationUpdate
|
||||
}
|
||||
|
@ -75,6 +75,10 @@ object Runtime {
|
||||
value = classOf[Api.CloseFileNotification],
|
||||
name = "closeFileNotification"
|
||||
),
|
||||
new JsonSubTypes.Type(
|
||||
value = classOf[Api.VisualisationUpdate],
|
||||
name = "visualisationUpdate"
|
||||
),
|
||||
new JsonSubTypes.Type(
|
||||
value = classOf[Api.AttachVisualisation],
|
||||
name = "attachVisualisation"
|
||||
@ -111,6 +115,22 @@ object Runtime {
|
||||
value = classOf[Api.EmptyStackError],
|
||||
name = "emptyStackError"
|
||||
),
|
||||
new JsonSubTypes.Type(
|
||||
value = classOf[Api.ModuleNotFound],
|
||||
name = "moduleNotFound"
|
||||
),
|
||||
new JsonSubTypes.Type(
|
||||
value = classOf[Api.VisualisationExpressionFailed],
|
||||
name = "visualisationExpressionFailed"
|
||||
),
|
||||
new JsonSubTypes.Type(
|
||||
value = classOf[Api.VisualisationEvaluationFailed],
|
||||
name = "visualisationEvaluationFailed"
|
||||
),
|
||||
new JsonSubTypes.Type(
|
||||
value = classOf[Api.VisualisationNotFound],
|
||||
name = "visualisationNotFound"
|
||||
),
|
||||
new JsonSubTypes.Type(
|
||||
value = classOf[Api.InvalidStackItemError],
|
||||
name = "invalidStackItemError"
|
||||
@ -428,6 +448,34 @@ object Runtime {
|
||||
*/
|
||||
case class ContextNotExistError(contextId: ContextId) extends Error
|
||||
|
||||
/**
|
||||
* Signals that a module cannot be found.
|
||||
*
|
||||
* @param moduleName the module name
|
||||
*/
|
||||
case class ModuleNotFound(moduleName: String) extends Error
|
||||
|
||||
/**
|
||||
* Signals that an expression specified in a [[AttachVisualisation]] or
|
||||
* a [[ModifyVisualisation]] cannot be evaluated.
|
||||
*
|
||||
* @param message the reason of the failure
|
||||
*/
|
||||
case class VisualisationExpressionFailed(message: String) extends Error
|
||||
|
||||
/**
|
||||
* Signals that an evaluation of a code responsible for generating
|
||||
* visualisation data failed.
|
||||
*
|
||||
* @param message the reason of the failure
|
||||
*/
|
||||
case class VisualisationEvaluationFailed(message: String) extends Error
|
||||
|
||||
/**
|
||||
* Signals that visualisation cannot be found.
|
||||
*/
|
||||
case class VisualisationNotFound() extends Error
|
||||
|
||||
/**
|
||||
* An error response signifying that stack is empty.
|
||||
*
|
||||
|
@ -174,6 +174,16 @@ public class Context {
|
||||
.flatMap(n -> getCompiler().topScope().getModule(n.toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a module with a given name.
|
||||
*
|
||||
* @param moduleName the qualified name of the module to lookup.
|
||||
* @return the relevant module, if exists.
|
||||
*/
|
||||
public Optional<Module> findModule(String moduleName) {
|
||||
return getCompiler().topScope().getModule(moduleName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new module corresponding to a given file.
|
||||
*
|
||||
|
@ -2,10 +2,7 @@ package org.enso.interpreter.service;
|
||||
|
||||
import com.oracle.truffle.api.instrumentation.EventBinding;
|
||||
import com.oracle.truffle.api.instrumentation.ExecutionEventListener;
|
||||
import com.oracle.truffle.api.interop.ArityException;
|
||||
import com.oracle.truffle.api.interop.InteropLibrary;
|
||||
import com.oracle.truffle.api.interop.UnsupportedMessageException;
|
||||
import com.oracle.truffle.api.interop.UnsupportedTypeException;
|
||||
import com.oracle.truffle.api.interop.*;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import org.enso.interpreter.instrument.Cache;
|
||||
import org.enso.interpreter.instrument.IdExecutionInstrument;
|
||||
@ -17,6 +14,8 @@ import org.enso.interpreter.runtime.callable.function.Function;
|
||||
import org.enso.interpreter.runtime.scope.ModuleScope;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.enso.polyglot.MethodNames;
|
||||
import org.enso.text.buffer.Rope;
|
||||
import org.enso.text.editing.JavaEditorAdapter;
|
||||
import org.enso.text.editing.model;
|
||||
@ -129,6 +128,36 @@ public class ExecutionService {
|
||||
execute(callMay.get(), cache, valueCallback, funCallCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates an expression in the scope of the provided module.
|
||||
*
|
||||
* @param module the module providing a scope for the expression
|
||||
* @param expression the expression to evluated
|
||||
* @return a result of evaluation
|
||||
*/
|
||||
public Object evaluateExpression(Module module, String expression)
|
||||
throws UnsupportedMessageException, ArityException,
|
||||
UnknownIdentifierException, UnsupportedTypeException {
|
||||
return interopLibrary.invokeMember(
|
||||
module,
|
||||
MethodNames.Module.EVAL_EXPRESSION,
|
||||
expression
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a function with the given argument.
|
||||
*
|
||||
* @param fn the function object
|
||||
* @param argument the argument applied to the function
|
||||
* @return the result of calling the function
|
||||
*/
|
||||
public Object callFunction(Object fn, Object argument)
|
||||
throws UnsupportedTypeException, ArityException,
|
||||
UnsupportedMessageException {
|
||||
return interopLibrary.execute(fn, argument);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a module at a given path to use a literal source.
|
||||
*
|
||||
@ -155,6 +184,16 @@ public class ExecutionService {
|
||||
module.ifPresent(Module::unsetLiteralSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a module by qualified name.
|
||||
*
|
||||
* @param moduleName the qualified name of the module
|
||||
* @return the relevant module, if exists
|
||||
*/
|
||||
public Optional<Module> findModule(String moduleName) {
|
||||
return context.findModule(moduleName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies modifications to literal module sources.
|
||||
*
|
||||
|
@ -1,79 +0,0 @@
|
||||
package org.enso.interpeter.instrument
|
||||
|
||||
import org.enso.polyglot.runtime.Runtime.Api.{ContextId, StackItem}
|
||||
|
||||
import scala.collection.mutable.Stack
|
||||
|
||||
/**
|
||||
* Storage for active execution contexts.
|
||||
*/
|
||||
class ExecutionContextManager {
|
||||
private var contexts: Map[ContextId, Stack[StackItem]] = Map()
|
||||
|
||||
/**
|
||||
* Creates a new context with a given id.
|
||||
*
|
||||
* @param id the context id.
|
||||
*/
|
||||
def create(id: ContextId): Unit =
|
||||
contexts += id -> Stack.empty
|
||||
|
||||
/**
|
||||
* Destroys a context with a given id.
|
||||
* @param id the context id.
|
||||
*/
|
||||
def destroy(id: ContextId): Unit =
|
||||
contexts -= id
|
||||
|
||||
/**
|
||||
* Gets a context with a given id.
|
||||
*
|
||||
* @param id the context id.
|
||||
* @return the context with the given id, if exists.
|
||||
*/
|
||||
def get(id: ContextId): Option[ContextId] =
|
||||
for {
|
||||
_ <- contexts.get(id)
|
||||
} yield id
|
||||
|
||||
/**
|
||||
* Gets a stack for a given context id.
|
||||
*
|
||||
* @param id the context id.
|
||||
* @return the stack.
|
||||
*/
|
||||
def getStack(id: ContextId): Stack[StackItem] =
|
||||
contexts.getOrElse(id, Stack())
|
||||
|
||||
/**
|
||||
* Gets all execution contexts.
|
||||
*
|
||||
* @return all currently available execution contexsts.
|
||||
*/
|
||||
def getAll: collection.MapView[ContextId, Stack[StackItem]] =
|
||||
contexts.view
|
||||
|
||||
/**
|
||||
* If the context exists, push the item on the stack.
|
||||
*
|
||||
* @param id the context id.
|
||||
* @param item stack item.
|
||||
* @return Unit representing success or None if the context does not exist.
|
||||
*/
|
||||
def push(id: ContextId, item: StackItem): Option[Unit] =
|
||||
for {
|
||||
stack <- contexts.get(id)
|
||||
} yield stack.push(item)
|
||||
|
||||
/**
|
||||
* If the context exists and stack not empty, pop the item from the stack.
|
||||
*
|
||||
* @param id the context id.
|
||||
* @return stack item or None if the stack is empty or not exists.
|
||||
*/
|
||||
def pop(id: ContextId): Option[StackItem] =
|
||||
for {
|
||||
stack <- contexts.get(id)
|
||||
if stack.nonEmpty
|
||||
} yield stack.pop()
|
||||
}
|
@ -0,0 +1,158 @@
|
||||
package org.enso.interpeter.instrument
|
||||
|
||||
import org.enso.polyglot.runtime.Runtime.Api.{
|
||||
ContextId,
|
||||
ExpressionId,
|
||||
StackItem,
|
||||
VisualisationId
|
||||
}
|
||||
|
||||
import scala.collection.mutable.Stack
|
||||
|
||||
/**
|
||||
* Storage for active execution contexts.
|
||||
*/
|
||||
class ExecutionContextManager {
|
||||
|
||||
private var contexts: Map[ContextId, ExecutionContextState] =
|
||||
Map().withDefaultValue(ExecutionContextState.empty)
|
||||
|
||||
/**
|
||||
* Creates a new context with a given id.
|
||||
*
|
||||
* @param id the context id.
|
||||
*/
|
||||
def create(id: ContextId): Unit =
|
||||
contexts += id -> ExecutionContextState.empty
|
||||
|
||||
/**
|
||||
* Destroys a context with a given id.
|
||||
* @param id the context id.
|
||||
*/
|
||||
def destroy(id: ContextId): Unit =
|
||||
contexts -= id
|
||||
|
||||
/**
|
||||
* Gets a context with a given id.
|
||||
*
|
||||
* @param id the context id.
|
||||
* @return the context with the given id, if exists.
|
||||
*/
|
||||
def get(id: ContextId): Option[ContextId] =
|
||||
for {
|
||||
_ <- contexts.get(id)
|
||||
} yield id
|
||||
|
||||
/**
|
||||
* Gets a stack for a given context id.
|
||||
*
|
||||
* @param id the context id.
|
||||
* @return the stack.
|
||||
*/
|
||||
def getStack(id: ContextId): Stack[StackItem] =
|
||||
contexts(id).stack
|
||||
|
||||
/**
|
||||
* Gets all execution contexts.
|
||||
*
|
||||
* @return all currently available execution contexsts.
|
||||
*/
|
||||
def getAll: collection.MapView[ContextId, Stack[StackItem]] =
|
||||
contexts.view.mapValues(_.stack)
|
||||
|
||||
/**
|
||||
* If the context exists, push the item on the stack.
|
||||
*
|
||||
* @param id the context id.
|
||||
* @param item stack item.
|
||||
* @return Unit representing success or None if the context does not exist.
|
||||
*/
|
||||
def push(id: ContextId, item: StackItem): Option[Unit] =
|
||||
for {
|
||||
state <- contexts.get(id)
|
||||
} yield state.stack.push(item)
|
||||
|
||||
/**
|
||||
* If the context exists and stack not empty, pop the item from the stack.
|
||||
*
|
||||
* @param id the context id.
|
||||
* @return stack item or None if the stack is empty or not exists.
|
||||
*/
|
||||
def pop(id: ContextId): Option[StackItem] =
|
||||
for {
|
||||
state <- contexts.get(id)
|
||||
if state.stack.nonEmpty
|
||||
} yield state.stack.pop()
|
||||
|
||||
/**
|
||||
* Tests if a context specified by its id is stored by the manager.
|
||||
*
|
||||
* @param contextId the identifier of the execution context
|
||||
* @return true if the context is stored or false otherwise
|
||||
*/
|
||||
def contains(contextId: ContextId): Boolean = contexts.contains(contextId)
|
||||
|
||||
/**
|
||||
* Upserts a visualisation for the specified context.
|
||||
*
|
||||
* @param contextId the identifier of the execution context
|
||||
* @param visualisation the visualisation to upsert
|
||||
*/
|
||||
def upsertVisualisation(
|
||||
contextId: ContextId,
|
||||
visualisation: Visualisation
|
||||
): Unit = {
|
||||
val state = contexts(contextId)
|
||||
state.visualisations.upsert(visualisation)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a visualisation with the provided id.
|
||||
*
|
||||
* @param contextId the identifier of the execution context
|
||||
* @param visualisationId the identifier of visualisation
|
||||
* @return an option with visualisation
|
||||
*/
|
||||
def getVisualisationById(
|
||||
contextId: ContextId,
|
||||
visualisationId: VisualisationId
|
||||
): Option[Visualisation] =
|
||||
for {
|
||||
state <- contexts.get(contextId)
|
||||
visualisation <- state.visualisations.getById(visualisationId)
|
||||
} yield visualisation
|
||||
|
||||
/**
|
||||
* Finds all visualisations attached to an expression.
|
||||
*
|
||||
* @param contextId the identifier of the execution context
|
||||
* @param expressionId the unique identifier of the expression
|
||||
* @return a list of matching visualisation
|
||||
*/
|
||||
def findVisualisationForExpression(
|
||||
contextId: ContextId,
|
||||
expressionId: ExpressionId
|
||||
): List[Visualisation] =
|
||||
for {
|
||||
state <- contexts.get(contextId).toList
|
||||
visualisation <- state.visualisations.find(expressionId)
|
||||
} yield visualisation
|
||||
|
||||
/**
|
||||
* Removes a visualisation from the holder.
|
||||
*
|
||||
* @param contextId the identifier of the execution context
|
||||
* @param visualisationId the visualisation identifier
|
||||
* @param expressionId the id of expression that the visualisation is
|
||||
* attached to
|
||||
*/
|
||||
def removeVisualisation(
|
||||
contextId: ContextId,
|
||||
expressionId: ExpressionId,
|
||||
visualisationId: VisualisationId
|
||||
): Unit = {
|
||||
val state = contexts(contextId)
|
||||
state.visualisations.remove(visualisationId, expressionId)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package org.enso.interpeter.instrument
|
||||
|
||||
import org.enso.polyglot.runtime.Runtime.Api.StackItem
|
||||
|
||||
import scala.collection.mutable.Stack
|
||||
|
||||
/**
|
||||
* Represents a state of an execution context.
|
||||
*
|
||||
* @param stack the current call stack for the execution context
|
||||
* @param visualisations the holder of all visualisations attached to the
|
||||
* execution context
|
||||
*/
|
||||
case class ExecutionContextState(
|
||||
stack: Stack[StackItem],
|
||||
visualisations: VisualisationHolder
|
||||
)
|
||||
|
||||
object ExecutionContextState {
|
||||
|
||||
/**
|
||||
* Returns empty state.
|
||||
*/
|
||||
def empty = ExecutionContextState(Stack.empty, VisualisationHolder.empty)
|
||||
|
||||
}
|
@ -5,8 +5,14 @@ import java.nio.ByteBuffer
|
||||
import java.util.UUID
|
||||
import java.util.function.Consumer
|
||||
|
||||
import cats.implicits._
|
||||
import com.oracle.truffle.api.TruffleContext
|
||||
import org.enso.interpreter.instrument.Cache
|
||||
import org.enso.interpeter.instrument.Handler.{
|
||||
EvalFailure,
|
||||
EvaluationFailed,
|
||||
ModuleNotFound
|
||||
}
|
||||
import org.enso.interpreter.instrument.IdExecutionInstrument.{
|
||||
ExpressionCall,
|
||||
ExpressionValue
|
||||
@ -14,11 +20,19 @@ import org.enso.interpreter.instrument.IdExecutionInstrument.{
|
||||
import org.enso.interpreter.node.callable.FunctionCallInstrumentationNode.FunctionCall
|
||||
import org.enso.interpreter.service.ExecutionService
|
||||
import org.enso.pkg.QualifiedName
|
||||
import org.enso.polyglot.runtime.Runtime.Api
|
||||
import org.enso.polyglot.runtime.Runtime.Api.{
|
||||
ContextId,
|
||||
ExpressionId,
|
||||
RequestId,
|
||||
VisualisationId
|
||||
}
|
||||
import org.enso.polyglot.runtime.Runtime.{Api, ApiResponse}
|
||||
import org.graalvm.polyglot.io.MessageEndpoint
|
||||
|
||||
import scala.jdk.javaapi.OptionConverters
|
||||
import scala.jdk.CollectionConverters._
|
||||
import scala.jdk.javaapi.OptionConverters
|
||||
import scala.util.control.NonFatal
|
||||
import cats.implicits._
|
||||
|
||||
/**
|
||||
* A message endpoint implementation used by the
|
||||
@ -95,9 +109,17 @@ final class Handler {
|
||||
case class CallData(callData: FunctionCall) extends ExecutionItem
|
||||
}
|
||||
|
||||
private def sendUpdate(
|
||||
private def onExpressionValueComputed(
|
||||
contextId: Api.ContextId,
|
||||
res: ExpressionValue
|
||||
value: ExpressionValue
|
||||
): Unit = {
|
||||
sendValueUpdate(contextId, value)
|
||||
fireVisualisationUpdates(contextId, value)
|
||||
}
|
||||
|
||||
private def sendValueUpdate(
|
||||
contextId: ContextId,
|
||||
value: ExpressionValue
|
||||
): Unit = {
|
||||
endpoint.sendToClient(
|
||||
Api.Response(
|
||||
@ -105,10 +127,10 @@ final class Handler {
|
||||
contextId,
|
||||
Vector(
|
||||
Api.ExpressionValueUpdate(
|
||||
res.getExpressionId,
|
||||
OptionConverters.toScala(res.getType),
|
||||
Some(res.getValue.toString),
|
||||
toMethodPointer(res)
|
||||
value.getExpressionId,
|
||||
OptionConverters.toScala(value.getType),
|
||||
Some(value.getValue.toString),
|
||||
toMethodPointer(value)
|
||||
)
|
||||
)
|
||||
)
|
||||
@ -116,6 +138,63 @@ final class Handler {
|
||||
)
|
||||
}
|
||||
|
||||
private def fireVisualisationUpdates(
|
||||
contextId: ContextId,
|
||||
value: ExpressionValue
|
||||
): Unit = {
|
||||
val visualisations =
|
||||
contextManager.findVisualisationForExpression(
|
||||
contextId,
|
||||
value.getExpressionId
|
||||
)
|
||||
visualisations foreach { visualisation =>
|
||||
emitVisualisationUpdate(contextId, value, visualisation)
|
||||
}
|
||||
}
|
||||
|
||||
private def emitVisualisationUpdate(
|
||||
contextId: ContextId,
|
||||
value: ExpressionValue,
|
||||
visualisation: Visualisation
|
||||
): Unit = {
|
||||
val errorMsgOrVisualisationData =
|
||||
Either
|
||||
.catchNonFatal {
|
||||
executionService.callFunction(
|
||||
visualisation.callback,
|
||||
value.getValue
|
||||
)
|
||||
}
|
||||
.leftMap(_.getMessage)
|
||||
.flatMap {
|
||||
case text: String => Right(text.getBytes("UTF-8"))
|
||||
case bytes: Array[Byte] => Right(bytes)
|
||||
case other =>
|
||||
Left(s"Cannot encode ${other.getClass} to byte array")
|
||||
}
|
||||
|
||||
errorMsgOrVisualisationData match {
|
||||
case Left(msg) =>
|
||||
endpoint.sendToClient(
|
||||
Api.Response(Api.VisualisationEvaluationFailed(msg))
|
||||
)
|
||||
|
||||
case Right(data) =>
|
||||
endpoint.sendToClient(
|
||||
Api.Response(
|
||||
Api.VisualisationUpdate(
|
||||
Api.VisualisationContext(
|
||||
visualisation.id,
|
||||
contextId,
|
||||
value.getExpressionId
|
||||
),
|
||||
data
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private def toMethodPointer(
|
||||
value: ExpressionValue
|
||||
): Option[Api.MethodPointer] =
|
||||
@ -199,7 +278,11 @@ final class Handler {
|
||||
}
|
||||
val (explicitCalls, localCalls) = unwind(stack, Nil, Nil)
|
||||
explicitCalls.headOption.foreach { item =>
|
||||
execute(toExecutionItem(item), localCalls, sendUpdate(contextId, _))
|
||||
execute(
|
||||
toExecutionItem(item),
|
||||
localCalls,
|
||||
onExpressionValueComputed(contextId, _)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -218,7 +301,7 @@ final class Handler {
|
||||
call.methodPointer.name
|
||||
)
|
||||
|
||||
private def withContext(action: => Unit): Unit = {
|
||||
private def withContext[A](action: => A): A = {
|
||||
val token = truffleContext.enter()
|
||||
try {
|
||||
action
|
||||
@ -322,7 +405,161 @@ final class Handler {
|
||||
executionService.modifyModuleSources(path, edits.asJava)
|
||||
withContext(executeAll())
|
||||
|
||||
case _ => throw new RuntimeException("Unhandled cases.")
|
||||
case Api.AttachVisualisation(visualisationId, expressionId, config) =>
|
||||
if (contextManager.contains(config.executionContextId)) {
|
||||
upsertVisualisation(
|
||||
requestId,
|
||||
visualisationId,
|
||||
expressionId,
|
||||
config,
|
||||
Api.VisualisationAttached()
|
||||
)
|
||||
} else {
|
||||
endpoint.sendToClient(
|
||||
Api.Response(
|
||||
requestId,
|
||||
Api.ContextNotExistError(config.executionContextId)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
case Api.DetachVisualisation(ctxId, visualisationId, exprId) =>
|
||||
if (contextManager.contains(ctxId)) {
|
||||
contextManager.removeVisualisation(ctxId, exprId, visualisationId)
|
||||
endpoint.sendToClient(
|
||||
Api.Response(
|
||||
requestId,
|
||||
Api.VisualisationDetached()
|
||||
)
|
||||
)
|
||||
} else {
|
||||
endpoint.sendToClient(
|
||||
Api.Response(
|
||||
requestId,
|
||||
Api.ContextNotExistError(ctxId)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
case Api.ModifyVisualisation(visualisationId, config) =>
|
||||
if (contextManager.contains(config.executionContextId)) {
|
||||
val maybeVisualisation = contextManager.getVisualisationById(
|
||||
config.executionContextId,
|
||||
visualisationId
|
||||
)
|
||||
maybeVisualisation match {
|
||||
case None =>
|
||||
endpoint.sendToClient(
|
||||
Api.Response(requestId, Api.VisualisationNotFound())
|
||||
)
|
||||
|
||||
case Some(visualisation) =>
|
||||
upsertVisualisation(
|
||||
requestId,
|
||||
visualisationId,
|
||||
visualisation.expressionId,
|
||||
config,
|
||||
Api.VisualisationModified()
|
||||
)
|
||||
}
|
||||
|
||||
} else {
|
||||
endpoint.sendToClient(
|
||||
Api.Response(
|
||||
requestId,
|
||||
Api.ContextNotExistError(config.executionContextId)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def upsertVisualisation(
|
||||
requestId: Option[RequestId],
|
||||
visualisationId: VisualisationId,
|
||||
expressionId: ExpressionId,
|
||||
config: Api.VisualisationConfiguration,
|
||||
replyWith: ApiResponse
|
||||
): Unit = {
|
||||
val maybeCallable =
|
||||
evaluateExpression(config.visualisationModule, config.expression)
|
||||
|
||||
maybeCallable match {
|
||||
case Left(ModuleNotFound) =>
|
||||
endpoint.sendToClient(
|
||||
Api.Response(
|
||||
requestId,
|
||||
Api.ModuleNotFound(config.visualisationModule)
|
||||
)
|
||||
)
|
||||
|
||||
case Left(EvaluationFailed(msg)) =>
|
||||
endpoint.sendToClient(
|
||||
Api.Response(
|
||||
requestId,
|
||||
Api.VisualisationExpressionFailed(msg)
|
||||
)
|
||||
)
|
||||
|
||||
case Right(callable) =>
|
||||
val visualisation = Visualisation(
|
||||
visualisationId,
|
||||
expressionId,
|
||||
callable
|
||||
)
|
||||
contextManager.upsertVisualisation(
|
||||
config.executionContextId,
|
||||
visualisation
|
||||
)
|
||||
endpoint.sendToClient(
|
||||
Api.Response(requestId, replyWith)
|
||||
)
|
||||
val stack = contextManager.getStack(config.executionContextId)
|
||||
withContext(execute(config.executionContextId, stack.toList))
|
||||
}
|
||||
}
|
||||
|
||||
private def evaluateExpression(
|
||||
moduleName: String,
|
||||
expression: String
|
||||
): Either[EvalFailure, AnyRef] = {
|
||||
val maybeModule = executionService.findModule(moduleName)
|
||||
|
||||
val notFoundOrModule =
|
||||
if (maybeModule.isPresent) Right(maybeModule.get())
|
||||
else Left(ModuleNotFound)
|
||||
|
||||
notFoundOrModule.flatMap { module =>
|
||||
try {
|
||||
withContext {
|
||||
executionService.evaluateExpression(module, expression).asRight
|
||||
}
|
||||
} catch {
|
||||
case NonFatal(th) => EvaluationFailed(th.getMessage).asLeft
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object Handler {
|
||||
|
||||
/**
|
||||
* Base trait for evaluation failures.
|
||||
*/
|
||||
sealed trait EvalFailure
|
||||
|
||||
/**
|
||||
* Signals that a module cannto be found.
|
||||
*/
|
||||
case object ModuleNotFound extends EvalFailure
|
||||
|
||||
/**
|
||||
* Signals that an evaluation of an expression failed.
|
||||
*
|
||||
* @param msg the textual reason of a failure
|
||||
*/
|
||||
case class EvaluationFailed(msg: String) extends EvalFailure
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,17 @@
|
||||
package org.enso.interpeter.instrument
|
||||
|
||||
import org.enso.polyglot.runtime.Runtime.Api.{ExpressionId, VisualisationId}
|
||||
|
||||
/**
|
||||
* An object containing visualisation data.
|
||||
*
|
||||
* @param id the unique identifier of visualisation
|
||||
* @param expressionId the identifier of expression that the visualisation is
|
||||
* attached to
|
||||
* @param callback the callable expression used to generate visualisation data
|
||||
*/
|
||||
case class Visualisation(
|
||||
id: VisualisationId,
|
||||
expressionId: ExpressionId,
|
||||
callback: AnyRef
|
||||
)
|
@ -0,0 +1,67 @@
|
||||
package org.enso.interpeter.instrument
|
||||
|
||||
import org.enso.polyglot.runtime.Runtime.Api.{ExpressionId, VisualisationId}
|
||||
|
||||
/**
|
||||
* A mutable holder of all visualisations attached to an execution context.
|
||||
*/
|
||||
class VisualisationHolder() {
|
||||
|
||||
private var visualisationMap: Map[ExpressionId, List[Visualisation]] =
|
||||
Map.empty.withDefaultValue(List.empty)
|
||||
|
||||
/**
|
||||
* Upserts a visualisation.
|
||||
*
|
||||
* @param visualisation the visualisation to upsert
|
||||
*/
|
||||
def upsert(visualisation: Visualisation): Unit = {
|
||||
val visualisations = visualisationMap(visualisation.expressionId)
|
||||
val removed = visualisations.filterNot(_.id == visualisation.id)
|
||||
visualisationMap += (visualisation.expressionId -> (visualisation :: removed))
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a visualisation from the holder.
|
||||
*
|
||||
* @param visualisationId the visualisation identifier
|
||||
* @param expressionId the id of expression that the visualisation is
|
||||
* attached to
|
||||
*/
|
||||
def remove(
|
||||
visualisationId: VisualisationId,
|
||||
expressionId: ExpressionId
|
||||
): Unit = {
|
||||
val visualisations = visualisationMap(expressionId)
|
||||
val removed = visualisations.filterNot(_.id == visualisationId)
|
||||
visualisationMap += (expressionId -> removed)
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all visualisations attached to an expression.
|
||||
*
|
||||
* @param expressionId the unique identifier of the expression
|
||||
* @return a list of matching visualisation
|
||||
*/
|
||||
def find(expressionId: ExpressionId): List[Visualisation] =
|
||||
visualisationMap(expressionId)
|
||||
|
||||
/**
|
||||
* Returns a visualisation with the provided id.
|
||||
*
|
||||
* @param visualisationId the identifier of visualisation
|
||||
* @return an option with visualisation
|
||||
*/
|
||||
def getById(visualisationId: VisualisationId): Option[Visualisation] =
|
||||
visualisationMap.values.flatten.find(_.id == visualisationId)
|
||||
|
||||
}
|
||||
|
||||
object VisualisationHolder {
|
||||
|
||||
/**
|
||||
* Returns an empty holder.
|
||||
*/
|
||||
def empty = new VisualisationHolder()
|
||||
|
||||
}
|
@ -11,6 +11,7 @@ import org.enso.interpreter.instrument.{
|
||||
}
|
||||
import org.enso.interpreter.test.Metadata
|
||||
import org.enso.pkg.Package
|
||||
import org.enso.polyglot.runtime.Runtime.Api.VisualisationUpdate
|
||||
import org.enso.polyglot.runtime.Runtime.{Api, ApiRequest}
|
||||
import org.enso.polyglot.{
|
||||
LanguageInfo,
|
||||
@ -77,7 +78,11 @@ class RuntimeServerTest
|
||||
def writeMain(contents: String): File =
|
||||
Files.write(pkg.mainFile.toPath, contents.getBytes).toFile
|
||||
|
||||
def writeFile(file: File, contents: String): File = {
|
||||
def writeFile(file: File, contents: String): File =
|
||||
Files.write(file.toPath, contents.getBytes).toFile
|
||||
|
||||
def writeInSrcDir(moduleName: String, contents: String): File = {
|
||||
val file = new File(pkg.sourceDir, s"$moduleName.enso")
|
||||
Files.write(file.toPath, contents.getBytes).toFile
|
||||
}
|
||||
|
||||
@ -89,6 +94,19 @@ class RuntimeServerTest
|
||||
msg
|
||||
}
|
||||
|
||||
def drain(): Unit = {
|
||||
messageQueue = List.empty
|
||||
}
|
||||
|
||||
def drainAndCollectFirstMatching[A](
|
||||
pf: PartialFunction[Api.Response, A]
|
||||
): A = {
|
||||
val maybeMessage = messageQueue.collectFirst(pf)
|
||||
maybeMessage.isDefined shouldBe true
|
||||
drain()
|
||||
maybeMessage.get
|
||||
}
|
||||
|
||||
def consumeOut: List[String] = {
|
||||
val result = out.toString
|
||||
out.reset()
|
||||
@ -199,6 +217,18 @@ class RuntimeServerTest
|
||||
}
|
||||
}
|
||||
|
||||
object Visualisation {
|
||||
|
||||
val code =
|
||||
"""
|
||||
|encode = x -> x.to_text
|
||||
|
|
||||
|incAndEncode = x -> here.encode x+1
|
||||
|
|
||||
|""".stripMargin
|
||||
|
||||
}
|
||||
|
||||
object Main2 {
|
||||
|
||||
val metadata = new Metadata
|
||||
@ -358,9 +388,6 @@ class RuntimeServerTest
|
||||
}
|
||||
|
||||
it should "support file modification operations" in {
|
||||
def send(msg: ApiRequest): Unit =
|
||||
context.send(Api.Request(UUID.randomUUID(), msg))
|
||||
|
||||
val fooFile = new File(context.pkg.sourceDir, "Foo.enso")
|
||||
val contextId = UUID.randomUUID()
|
||||
|
||||
@ -553,4 +580,275 @@ class RuntimeServerTest
|
||||
)
|
||||
context.consumeOut shouldEqual List()
|
||||
}
|
||||
|
||||
it should "emit visualisation update when expression is evaluated" in {
|
||||
val mainFile = context.writeMain(context.Main.code)
|
||||
val visualisationFile =
|
||||
context.writeInSrcDir("Visualisation", context.Visualisation.code)
|
||||
|
||||
send(
|
||||
Api.OpenFileNotification(
|
||||
visualisationFile,
|
||||
context.Visualisation.code
|
||||
)
|
||||
)
|
||||
|
||||
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))
|
||||
)
|
||||
|
||||
// push main
|
||||
val item1 = Api.StackItem.ExplicitCall(
|
||||
Api.MethodPointer(mainFile, "Main", "main"),
|
||||
None,
|
||||
Vector()
|
||||
)
|
||||
context.send(
|
||||
Api.Request(requestId, Api.PushContextRequest(contextId, item1))
|
||||
)
|
||||
|
||||
context.drain()
|
||||
|
||||
context.send(
|
||||
Api.Request(
|
||||
requestId,
|
||||
Api.AttachVisualisation(
|
||||
visualisationId,
|
||||
context.Main.idMainX,
|
||||
Api.VisualisationConfiguration(
|
||||
contextId,
|
||||
"Test.Visualisation",
|
||||
"x -> here.encode x"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
context.receive shouldBe Some(
|
||||
Api.Response(requestId, Api.VisualisationAttached())
|
||||
)
|
||||
val expectedExprId = context.Main.idMainX
|
||||
val data = context.drainAndCollectFirstMatching {
|
||||
case Api.Response(
|
||||
None,
|
||||
Api.VisualisationUpdate(
|
||||
Api.VisualisationContext(
|
||||
`visualisationId`,
|
||||
`contextId`,
|
||||
`expectedExprId`
|
||||
),
|
||||
data
|
||||
)
|
||||
) =>
|
||||
data
|
||||
}
|
||||
data.sameElements("6".getBytes) shouldBe true
|
||||
|
||||
// recompute
|
||||
context.send(
|
||||
Api.Request(requestId, Api.RecomputeContextRequest(contextId, None))
|
||||
)
|
||||
val data2 = context.drainAndCollectFirstMatching {
|
||||
case Api.Response(
|
||||
None,
|
||||
Api.VisualisationUpdate(
|
||||
Api.VisualisationContext(
|
||||
`visualisationId`,
|
||||
`contextId`,
|
||||
`expectedExprId`
|
||||
),
|
||||
data
|
||||
)
|
||||
) =>
|
||||
data
|
||||
}
|
||||
data2.sameElements("6".getBytes) shouldBe true
|
||||
|
||||
}
|
||||
|
||||
it should "be able to modify visualisations" in {
|
||||
val mainFile = context.writeMain(context.Main.code)
|
||||
val visualisationFile =
|
||||
context.writeInSrcDir("Visualisation", context.Visualisation.code)
|
||||
|
||||
send(
|
||||
Api.OpenFileNotification(
|
||||
visualisationFile,
|
||||
context.Visualisation.code
|
||||
)
|
||||
)
|
||||
|
||||
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))
|
||||
)
|
||||
|
||||
// push main
|
||||
val item1 = Api.StackItem.ExplicitCall(
|
||||
Api.MethodPointer(mainFile, "Main", "main"),
|
||||
None,
|
||||
Vector()
|
||||
)
|
||||
context.send(
|
||||
Api.Request(requestId, Api.PushContextRequest(contextId, item1))
|
||||
)
|
||||
|
||||
context.drain()
|
||||
|
||||
context.send(
|
||||
Api.Request(
|
||||
requestId,
|
||||
Api.AttachVisualisation(
|
||||
visualisationId,
|
||||
context.Main.idMainX,
|
||||
Api.VisualisationConfiguration(
|
||||
contextId,
|
||||
"Test.Visualisation",
|
||||
"x -> here.encode x"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
context.receive shouldBe Some(
|
||||
Api.Response(requestId, Api.VisualisationAttached())
|
||||
)
|
||||
val expectedExprId = context.Main.idMainX
|
||||
val data = context.drainAndCollectFirstMatching {
|
||||
case Api.Response(
|
||||
None,
|
||||
Api.VisualisationUpdate(
|
||||
Api.VisualisationContext(
|
||||
`visualisationId`,
|
||||
`contextId`,
|
||||
`expectedExprId`
|
||||
),
|
||||
data
|
||||
)
|
||||
) =>
|
||||
data
|
||||
}
|
||||
data.sameElements("6".getBytes) shouldBe true
|
||||
|
||||
context.send(
|
||||
Api.Request(
|
||||
requestId,
|
||||
Api.ModifyVisualisation(
|
||||
visualisationId,
|
||||
Api.VisualisationConfiguration(
|
||||
contextId,
|
||||
"Test.Visualisation",
|
||||
"x -> here.incAndEncode x"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
context.receive shouldBe Some(
|
||||
Api.Response(requestId, Api.VisualisationModified())
|
||||
)
|
||||
val dataAfterModification = context.drainAndCollectFirstMatching {
|
||||
case Api.Response(
|
||||
None,
|
||||
Api.VisualisationUpdate(
|
||||
Api.VisualisationContext(
|
||||
`visualisationId`,
|
||||
`contextId`,
|
||||
`expectedExprId`
|
||||
),
|
||||
data
|
||||
)
|
||||
) =>
|
||||
data
|
||||
}
|
||||
dataAfterModification.sameElements("7".getBytes) shouldBe true
|
||||
}
|
||||
|
||||
it should "not emit visualisation updates when visualisation is detached" in {
|
||||
val mainFile = context.writeMain(context.Main.code)
|
||||
val visualisationFile =
|
||||
context.writeInSrcDir("Visualisation", context.Visualisation.code)
|
||||
|
||||
send(
|
||||
Api.OpenFileNotification(
|
||||
visualisationFile,
|
||||
context.Visualisation.code
|
||||
)
|
||||
)
|
||||
|
||||
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))
|
||||
)
|
||||
|
||||
context.send(
|
||||
Api.Request(
|
||||
requestId,
|
||||
Api.AttachVisualisation(
|
||||
visualisationId,
|
||||
context.Main.idMainX,
|
||||
Api.VisualisationConfiguration(
|
||||
contextId,
|
||||
"Test.Visualisation",
|
||||
"x -> here.encode x"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
context.receive shouldBe Some(
|
||||
Api.Response(requestId, Api.VisualisationAttached())
|
||||
)
|
||||
// push main
|
||||
val item1 = Api.StackItem.ExplicitCall(
|
||||
Api.MethodPointer(mainFile, "Main", "main"),
|
||||
None,
|
||||
Vector()
|
||||
)
|
||||
context.send(
|
||||
Api.Request(requestId, Api.PushContextRequest(contextId, item1))
|
||||
)
|
||||
context.drain()
|
||||
|
||||
context.send(
|
||||
Api.Request(
|
||||
requestId,
|
||||
Api.DetachVisualisation(
|
||||
contextId,
|
||||
visualisationId,
|
||||
context.Main.idMainX
|
||||
)
|
||||
)
|
||||
)
|
||||
context.receive shouldBe Some(
|
||||
Api.Response(requestId, Api.VisualisationDetached())
|
||||
)
|
||||
// recompute
|
||||
context.send(
|
||||
Api.Request(requestId, Api.RecomputeContextRequest(contextId, None))
|
||||
)
|
||||
Set.fill(5)(context.receive) shouldEqual Set(
|
||||
Some(Api.Response(requestId, Api.RecomputeContextResponse(contextId))),
|
||||
Some(context.Main.Update.mainX(contextId)),
|
||||
Some(context.Main.Update.mainY(contextId)),
|
||||
Some(context.Main.Update.mainZ(contextId)),
|
||||
None
|
||||
)
|
||||
}
|
||||
|
||||
private def send(msg: ApiRequest): Unit =
|
||||
context.send(Api.Request(UUID.randomUUID(), msg))
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user