Skip to content

Commit

Permalink
Attach visualizations to sub-expressions (#4048)
Browse files Browse the repository at this point in the history
Add ability to attach visualizations to sub-expressions.
  • Loading branch information
4e6 authored Jan 16, 2023
1 parent 619974d commit 0a6e623
Show file tree
Hide file tree
Showing 10 changed files with 326 additions and 27 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,7 @@
place][4022]
- [Introducing Meta.atom_with_hole][4023]
- [Report failures in name resolution in type signatures][4030]
- [Attach visualizations to sub-expressions][4048]

[3227]: https://github.com/enso-org/enso/pull/3227
[3248]: https://github.com/enso-org/enso/pull/3248
Expand Down Expand Up @@ -594,6 +595,7 @@
[4022]: https://github.com/enso-org/enso/pull/4022
[4023]: https://github.com/enso-org/enso/pull/4023
[4030]: https://github.com/enso-org/enso/pull/4030
[4048]: https://github.com/enso-org/enso/pull/4048

# Enso 2.0.0-alpha.18 (2021-10-12)

Expand Down
17 changes: 17 additions & 0 deletions docs/language-server/protocol-language-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ transport formats, please look [here](./protocol-architecture).
- [`EmptyStackError`](#emptystackerror)
- [`InvalidStackItemError`](#invalidstackitemerror)
- [`ModuleNotFoundError`](#modulenotfounderror)
- [`ModuleNotFoundForExpressionError`](#modulenotfoundforexpressionerror)
- [`VisualisationNotFoundError`](#visualisationnotfounderror)
- [`VisualisationExpressionError`](#visualisationexpressionerror)
- [`FileNotOpenedError`](#filenotopenederror)
Expand Down Expand Up @@ -3991,6 +3992,8 @@ null;
by provided id.
- [`ModuleNotFoundError`](#modulenotfounderror) to signal that the module with
the visualisation cannot be found.
- [`ModuleNotFoundForExpressionError`](#modulenotfoundforexpressionerror) to
signal that the module containing the provided expression cannot be found.
- [`VisualisationExpressionError`](#visualisationexpressionerror) to signal that
the expression specified in the `VisualisationConfiguration` cannot be
evaluated.
Expand Down Expand Up @@ -4029,6 +4032,8 @@ null;
by provided id.
- [`ModuleNotFoundError`](#modulenotfounderror) to signal that the module with
the visualisation cannot be found.
- [`ModuleNotFoundForExpressionError`](#modulenotfoundforexpressionerror) to
signal that the module containing the provided expression cannot be found.
- [`VisualisationExpressionError`](#visualisationexpressionerror) to signal that
the expression specified in the `VisualisationConfiguration` cannot be
evaluated.
Expand Down Expand Up @@ -5500,6 +5505,18 @@ cannot be evaluated. The error contains an optional `data` field of type
}
```

### `ModuleNotFoundForExpressionError`

It signals that the module containing the provided expression id cannot be
found.

```typescript
"error" : {
"code" : 2008,
"message" : "Module not found for [aa1f75c4-8c4d-493d-a6a7-72123a52f084]"
}
```

### `FileNotOpenedError`

Signals that a file wasn't opened.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,13 @@ object ContextRegistryProtocol {
*/
case class ModuleNotFound(moduleName: String) extends Failure

/** Signals that the module containing the provided expression id cannot be
* found.
*
* @param expressionId the expression id
*/
case class ModuleNotFoundForExpression(expressionId: UUID) extends Failure

/** Signals that visualisation cannot be found.
*/
case object VisualisationNotFound extends Failure
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,4 +224,7 @@ object ExecutionApi {
)
}

case class ModuleNotFoundForExpressionError(expressionId: ExpressionId)
extends Error(2008, s"Module not found for [$expressionId]")

}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ final class RuntimeFailureMapper(contentRootManager: ContentRootManager) {
ContextRegistryProtocol.InvalidStackItemError(contextId)
case Api.ModuleNotFound(moduleName) =>
ContextRegistryProtocol.ModuleNotFound(moduleName)
case Api.ModuleNotFoundForExpression(expressionId) =>
ContextRegistryProtocol.ModuleNotFoundForExpression(expressionId)
case Api.VisualisationExpressionFailed(message, result) =>
for (diagnostic <- result.map(toProtocolDiagnostic).sequence)
yield ContextRegistryProtocol.VisualisationExpressionFailed(
Expand Down Expand Up @@ -164,6 +166,8 @@ object RuntimeFailureMapper {
VisualisationNotFoundError
case ContextRegistryProtocol.ModuleNotFound(name) =>
ModuleNotFoundError(name)
case ContextRegistryProtocol.ModuleNotFoundForExpression(expressionId) =>
ModuleNotFoundForExpressionError(expressionId)
case ContextRegistryProtocol.VisualisationExpressionFailed(msg, result) =>
VisualisationExpressionError(msg, result)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,10 @@ object Runtime {
value = classOf[Api.ModuleNotFound],
name = "moduleNotFound"
),
new JsonSubTypes.Type(
value = classOf[Api.ModuleNotFoundForExpression],
name = "moduleNotFoundForExpression"
),
new JsonSubTypes.Type(
value = classOf[Api.ExecutionUpdate],
name = "executionUpdate"
Expand Down Expand Up @@ -1218,6 +1222,13 @@ object Runtime {
*/
final case class ModuleNotFound(moduleName: String) extends Error

/** Signals that a module cannot be found for the provided expression id.
*
* @param expressionId the expression id
*/
final case class ModuleNotFoundForExpression(expressionId: ExpressionId)
extends Error

/** Signals that execution of a context completed.
*
* @param contextId the context's id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,11 @@ class RuntimeVisualizationsTest

val metadata = new Metadata

val idMainX = metadata.addItem(63, 1)
val idMainY = metadata.addItem(73, 7)
val idMainZ = metadata.addItem(89, 5)
val idFooY = metadata.addItem(133, 8)
val idFooZ = metadata.addItem(150, 5)
val idMainX = metadata.addItem(63, 1, "aa")
val idMainY = metadata.addItem(73, 7, "ab")
val idMainZ = metadata.addItem(89, 5, "ac")
val idFooY = metadata.addItem(133, 8, "ad")
val idFooZ = metadata.addItem(150, 5, "ae")

def code =
metadata.appendToCode(
Expand Down Expand Up @@ -1153,15 +1153,11 @@ class RuntimeVisualizationsTest
)
)
)
context.receiveN(3) should contain theSameElementsAs Seq(
Api.Response(requestId, Api.VisualisationAttached()),
context.receiveN(1) should contain theSameElementsAs Seq(
Api.Response(
Api.ExecutionFailed(
contextId,
Api.ExecutionResult.Failure("Execution stack is empty.", None)
)
),
context.executionComplete(contextId)
requestId,
Api.ModuleNotFoundForExpression(context.Main.idMainX)
)
)

// push main
Expand All @@ -1173,17 +1169,40 @@ class RuntimeVisualizationsTest
context.send(
Api.Request(requestId, Api.PushContextRequest(contextId, item1))
)
val pushResponses = context.receiveNIgnorePendingExpressionUpdates(6)
pushResponses should contain allOf (
context.receiveNIgnorePendingExpressionUpdates(
5
) 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),
context.executionComplete(contextId)
)

// attach visualisation
context.send(
Api.Request(
requestId,
Api.AttachVisualisation(
visualisationId,
context.Main.idMainX,
Api.VisualisationConfiguration(
contextId,
Api.VisualisationExpression.Text(
"Enso_Test.Test.Visualisation",
"x -> encode x"
)
)
)
)
)
val attachVisualisationResponses = context.receiveN(2)
attachVisualisationResponses should contain(
Api.Response(requestId, Api.VisualisationAttached())
)
val expectedExpressionId = context.Main.idMainX
val Some(data) =
pushResponses.collectFirst {
attachVisualisationResponses.collectFirst {
case Api.Response(
None,
Api.VisualisationUpdate(
Expand Down Expand Up @@ -2985,4 +3004,139 @@ class RuntimeVisualizationsTest
}
new String(data, StandardCharsets.UTF_8) shouldEqual "(Mk_Newtype 42)"
}

it should "emit visualisation update for the target of a method call" in {
val contextId = UUID.randomUUID()
val requestId = UUID.randomUUID()
val visualisationId = UUID.randomUUID()
val moduleName = "Enso_Test.Test.Main"
val metadata = new Metadata

val idX = metadata.addItem(65, 1, "aa")
val idY = metadata.addItem(65, 7, "ab")

val code =
"""type T
| C
|
| inc self x = x + 1
|
|main =
| x = T.C
| y = x.inc 7
| y
|""".stripMargin.linesIterator.mkString("\n")
val contents = metadata.appendToCode(code)
val mainFile = context.writeMain(contents)

// 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))
)
context.receiveNone shouldEqual None

// push main
val item1 = Api.StackItem.ExplicitCall(
Api.MethodPointer(moduleName, "Enso_Test.Test.Main", "main"),
None,
Vector()
)
context.send(
Api.Request(requestId, Api.PushContextRequest(contextId, item1))
)
context.receiveNIgnorePendingExpressionUpdates(
4
) should contain theSameElementsAs Seq(
Api.Response(requestId, Api.PushContextResponse(contextId)),
TestMessages.update(contextId, idX, s"$moduleName.T"),
TestMessages.update(
contextId,
idY,
ConstantsGen.INTEGER,
Api.MethodPointer(moduleName, s"$moduleName.T", "inc")
),
context.executionComplete(contextId)
)

// attach visualisation
context.send(
Api.Request(
requestId,
Api.AttachVisualisation(
visualisationId,
idX,
Api.VisualisationConfiguration(
contextId,
Api.VisualisationExpression.Text(
moduleName,
"x -> x.to_text"
)
)
)
)
)
val attachVisualisationResponses =
context.receiveNIgnoreExpressionUpdates(3)
attachVisualisationResponses should contain allOf (
Api.Response(requestId, Api.VisualisationAttached()),
context.executionComplete(contextId)
)
val Some(data) = attachVisualisationResponses.collectFirst {
case Api.Response(
None,
Api.VisualisationUpdate(
Api.VisualisationContext(
`visualisationId`,
`contextId`,
`idX`
),
data
)
) =>
data
}
new String(data, StandardCharsets.UTF_8) shouldEqual "C"

// Modify the file
context.send(
Api.Request(
Api.EditFileNotification(
mainFile,
Seq(
TextEdit(
model.Range(model.Position(7, 8), model.Position(7, 9)),
"x"
)
),
execute = true
)
)
)

val editFileResponse = context.receiveNIgnoreExpressionUpdates(2)
editFileResponse should contain(
context.executionComplete(contextId)
)
val Some(data1) = editFileResponse.collectFirst {
case Api.Response(
None,
Api.VisualisationUpdate(
Api.VisualisationContext(
`visualisationId`,
`contextId`,
`idX`
),
data
)
) =>
data
}
new String(data1, StandardCharsets.UTF_8) shouldEqual "C"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,11 @@ public Optional<Module> findModuleByExpressionId(UUID expressionId) {
return getTopScope().getModules().stream()
.filter(
module ->
module.getIr().preorder().exists(ir -> ir.getExternalId().contains(expressionId)))
module.getIr() != null
&& module
.getIr()
.preorder()
.exists(ir -> ir.getExternalId().contains(expressionId)))
.findFirst();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -823,7 +823,7 @@ case object DataflowAnalysis extends IRPass {
* @return the set of all associations with `key`, if key exists
*/
def get(key: DependencyInfo.Type): Option[Set[DependencyInfo.Type]] = {
val visited = mutable.Set[DependencyInfo.Type]()
val visited = mutable.LinkedHashSet[DependencyInfo.Type]()

def go(key: DependencyInfo.Type): Set[DependencyInfo.Type] = {
if (!visited.contains(key)) {
Expand Down
Loading

0 comments on commit 0a6e623

Please sign in to comment.