mirror of
https://github.com/enso-org/enso.git
synced 2024-11-22 11:52:59 +03:00
IDE uses new visualization API (#3661)
This commit is contained in:
parent
65140f48ca
commit
de0a231417
@ -58,8 +58,9 @@
|
||||
- [Fixed error handling during startup.][3648] This prevents entering IDE into a
|
||||
"zombie" state, where processes were started but not visible to user. They
|
||||
could cause issues with starting further IDE instances.
|
||||
- [New nodes are created in the project source when the searcher is opened
|
||||
and a new node is created.][3645]
|
||||
- [New nodes are created in the project source when the searcher is opened and a
|
||||
new node is created.][3645]
|
||||
- [IDE uses new visualization API.][3661]
|
||||
- [Visualization of long textual values improved][3665]
|
||||
|
||||
#### EnsoGL (rendering engine)
|
||||
@ -296,6 +297,7 @@
|
||||
[3644]: https://github.com/enso-org/enso/pull/3644
|
||||
[3645]: https://github.com/enso-org/enso/pull/3645
|
||||
[3648]: https://github.com/enso-org/enso/pull/3648
|
||||
[3661]: https://github.com/enso-org/enso/pull/3661
|
||||
[3665]: https://github.com/enso-org/enso/pull/3665
|
||||
[3634]: https://github.com/enso-org/enso/pull/3634
|
||||
[3669]: https://github.com/enso-org/enso/pull/3669
|
||||
|
@ -1,14 +1,14 @@
|
||||
use super::*;
|
||||
use std::future::Future;
|
||||
|
||||
use futures::task::LocalSpawnExt;
|
||||
use serde_json::json;
|
||||
use serde_json::Value;
|
||||
|
||||
use json_rpc::messages::Message;
|
||||
use json_rpc::messages::RequestMessage;
|
||||
use json_rpc::test_util::transport::mock::MockTransport;
|
||||
use serde_json::json;
|
||||
use serde_json::Value;
|
||||
use std::future::Future;
|
||||
|
||||
|
||||
use super::*;
|
||||
|
||||
// ===============
|
||||
// === Fixture ===
|
||||
@ -424,12 +424,18 @@ fn test_execution_context() {
|
||||
);
|
||||
let visualisation_id = uuid::Uuid::default();
|
||||
let expression_id = uuid::Uuid::default();
|
||||
let expression = "1 + 1".to_string();
|
||||
let visualisation_module = "[Foo.Bar.Baz]".to_string();
|
||||
let visualization_function = "foo";
|
||||
let visualization_module = "[Foo.Bar.Baz]";
|
||||
let expression = MethodPointer {
|
||||
module: visualization_module.to_string(),
|
||||
defined_on_type: visualization_module.to_string(),
|
||||
name: visualization_function.to_string(),
|
||||
};
|
||||
let positional_arguments_expressions = vec![1, 2, 3].iter().map(|x| x.to_string()).collect();
|
||||
let visualisation_config = VisualisationConfiguration {
|
||||
execution_context_id: context_id,
|
||||
expression,
|
||||
visualisation_module,
|
||||
positional_arguments_expressions,
|
||||
};
|
||||
test_request(
|
||||
|client| {
|
||||
@ -441,8 +447,12 @@ fn test_execution_context() {
|
||||
"expressionId" : "00000000-0000-0000-0000-000000000000",
|
||||
"visualisationConfig" : {
|
||||
"executionContextId" : "00000000-0000-0000-0000-000000000000",
|
||||
"visualisationModule" : "[Foo.Bar.Baz]",
|
||||
"expression" : "1 + 1"
|
||||
"expression" : {
|
||||
"module" : "[Foo.Bar.Baz]",
|
||||
"definedOnType" : "[Foo.Bar.Baz]",
|
||||
"name" : "foo"
|
||||
},
|
||||
"positionalArgumentsExpressions" : ["1", "2", "3"]
|
||||
}
|
||||
}),
|
||||
unit_json.clone(),
|
||||
@ -459,12 +469,18 @@ fn test_execution_context() {
|
||||
unit_json.clone(),
|
||||
(),
|
||||
);
|
||||
let expression = "1 + 1".to_string();
|
||||
let visualisation_module = "[Foo.Bar.Baz]".to_string();
|
||||
let visualization_function = "foo";
|
||||
let visualization_module = "[Foo.Bar.Baz]";
|
||||
let expression = MethodPointer {
|
||||
module: visualization_module.to_string(),
|
||||
defined_on_type: visualization_module.to_string(),
|
||||
name: visualization_function.to_string(),
|
||||
};
|
||||
let positional_arguments_expressions = vec!["foo"].iter().map(|x| x.to_string()).collect();
|
||||
let visualisation_config = VisualisationConfiguration {
|
||||
execution_context_id: context_id,
|
||||
expression,
|
||||
visualisation_module,
|
||||
positional_arguments_expressions,
|
||||
};
|
||||
test_request(
|
||||
|client| client.modify_visualisation(&visualisation_id, &visualisation_config),
|
||||
@ -473,8 +489,12 @@ fn test_execution_context() {
|
||||
"visualisationId" : "00000000-0000-0000-0000-000000000000",
|
||||
"visualisationConfig" : {
|
||||
"executionContextId" : "00000000-0000-0000-0000-000000000000",
|
||||
"visualisationModule" : "[Foo.Bar.Baz]",
|
||||
"expression" : "1 + 1"
|
||||
"expression" : {
|
||||
"module" : "[Foo.Bar.Baz]",
|
||||
"definedOnType" : "[Foo.Bar.Baz]",
|
||||
"name" : "foo"
|
||||
},
|
||||
"positionalArgumentsExpressions" : ["foo"]
|
||||
}
|
||||
}),
|
||||
unit_json.clone(),
|
||||
|
@ -649,12 +649,12 @@ pub type ExpressionId = Uuid;
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[allow(missing_docs)]
|
||||
pub struct VisualisationConfiguration {
|
||||
#[allow(missing_docs)]
|
||||
/// An execution context of the visualization.
|
||||
pub execution_context_id: ContextId,
|
||||
/// A qualified name of the module containing the expression which creates visualisation.
|
||||
pub visualisation_module: String,
|
||||
/// An enso lambda that will transform the data into expected format, i.e. `a -> a.json`.
|
||||
pub expression: String,
|
||||
/// An enso function that will transform the data into expected format.
|
||||
pub expression: MethodPointer,
|
||||
/// A list of arguments to pass to the visualization expression.
|
||||
pub positional_arguments_expressions: Vec<String>,
|
||||
}
|
||||
|
||||
/// Used to enter deeper in the execution context stack. In general, all consequent stack items
|
||||
|
@ -11,22 +11,22 @@ tags: [product]
|
||||
|
||||
Visualizations have two main purposes:
|
||||
|
||||
- **Display results of nodes**
|
||||
Each node can be assigned with one or more visualization. After a node
|
||||
computes its new value, the visualization shows it in an understandable way to
|
||||
the user. Please note that a single node can be assigned with multiple
|
||||
visualizations at the same time. For example, a node might want to display a
|
||||
map of locations, and their list at the same time next to each other.
|
||||
- **Display results of nodes** Each node can be assigned with one or more
|
||||
visualization. After a node computes its new value, the visualization shows it
|
||||
in an understandable way to the user. Please note that a single node can be
|
||||
assigned with multiple visualizations at the same time. For example, a node
|
||||
might want to display a map of locations, and their list at the same time next
|
||||
to each other.
|
||||
|
||||
- **Provide interactive way to generate new data**
|
||||
In a widget mode (described in detail later), visualizations provide users
|
||||
with an interactive GUI to define data. For example, a map visualization can
|
||||
both display locations, as well as allowing the user to pick locations by
|
||||
clicking with a mouse. Similarly, the histogram can both display a list of
|
||||
numbers, and can be manually draw with the mouse producing such a list.
|
||||
Several numbers can be visualized as a table of sliders, which can also be
|
||||
used to interactively generate a table of numbers. Image visualizations can
|
||||
behave like an image editor, etc.
|
||||
- **Provide interactive way to generate new data** In a widget mode (described
|
||||
in detail later), visualizations provide users with an interactive GUI to
|
||||
define data. For example, a map visualization can both display locations, as
|
||||
well as allowing the user to pick locations by clicking with a mouse.
|
||||
Similarly, the histogram can both display a list of numbers, and can be
|
||||
manually draw with the mouse producing such a list. Several numbers can be
|
||||
visualized as a table of sliders, which can also be used to interactively
|
||||
generate a table of numbers. Image visualizations can behave like an image
|
||||
editor, etc.
|
||||
|
||||
## Visualization Display Forms
|
||||
|
||||
@ -37,34 +37,33 @@ Visualizations can be displayed in the following ways:
|
||||
you move the node, the visualization moves as well. This mode can be toggled
|
||||
by tapping the spacebar.
|
||||
|
||||
- **Fullscreen**
|
||||
Visualization attached to node can grow (animate) to ocupy full IDE visual
|
||||
space. This mode can be triggered on the recently selected node (in case many
|
||||
nodes are selected, the last selected node will be used) by either pressing
|
||||
keeping the spacebar pressed for longer than approx 0.5s, or by tapping it
|
||||
twice. In the former case, the visualization shrinks to each original form
|
||||
whenever we release space, in the later, whenever we press space again.
|
||||
- **Fullscreen** Visualization attached to node can grow (animate) to occupy
|
||||
full IDE visual space. This mode can be triggered on the recently selected
|
||||
node (in case many nodes are selected, the last selected node will be used) by
|
||||
either pressing keeping the spacebar pressed for longer than approx 0.5s, or
|
||||
by tapping it twice. In the former case, the visualization shrinks to each
|
||||
original form whenever we release space, in the later, whenever we press space
|
||||
again.
|
||||
|
||||
- **Detached**
|
||||
Visualizations attached to nodes can be detached, scaled, and placed freely
|
||||
across the visual canvas (we might introduce a special place where you can put
|
||||
such visualizations). This is useful when defining dashboards or reports. We
|
||||
also plan to provide a notebook-like experience where you can write text mixed
|
||||
with visualizations (including widgets for an interactive experience).
|
||||
- **Detached** Visualizations attached to nodes can be detached, scaled, and
|
||||
placed freely across the visual canvas (we might introduce a special place
|
||||
where you can put such visualizations). This is useful when defining
|
||||
dashboards or reports. We also plan to provide a notebook-like experience
|
||||
where you can write text mixed with visualizations (including widgets for an
|
||||
interactive experience).
|
||||
|
||||
- **Widgets**
|
||||
In this mode visualizations behave like nodes but do not display expressions.
|
||||
They have one input and one output port. If the input port is connected, the
|
||||
visualization displays its value and passes its to the output port. In case it
|
||||
is not connected, the visualization becomes an interactive widget allowing the
|
||||
user to specify data. For example, a map visualization will allow the user to
|
||||
manually pick locations. After each change, the new locations will be sent to
|
||||
the output port. Under the hood, widgets are represented as nodes and their
|
||||
code lines are assigned with a dedicated "visualization" metadata.
|
||||
Visualizations generate expressions always in the form of `name = data`, where
|
||||
data is a hardcoded data produced from the visualization. For example, when
|
||||
user clicks the map to define locations, the data could be a string literal
|
||||
containing locations encoded in JSON.
|
||||
- **Widgets** In this mode visualizations behave like nodes but do not display
|
||||
expressions. They have one input and one output port. If the input port is
|
||||
connected, the visualization displays its value and passes its to the output
|
||||
port. In case it is not connected, the visualization becomes an interactive
|
||||
widget allowing the user to specify data. For example, a map visualization
|
||||
will allow the user to manually pick locations. After each change, the new
|
||||
locations will be sent to the output port. Under the hood, widgets are
|
||||
represented as nodes and their code lines are assigned with a dedicated
|
||||
"visualization" metadata. Visualizations generate expressions always in the
|
||||
form of `name = data`, where data is a hardcoded data produced from the
|
||||
visualization. For example, when user clicks the map to define locations, the
|
||||
data could be a string literal containing locations encoded in JSON.
|
||||
|
||||
### Choosing a Visualization Type.
|
||||
|
||||
@ -201,24 +200,12 @@ In particular:
|
||||
as visualizations. The superclass defines a default constructor and a set of
|
||||
utilities:
|
||||
|
||||
- #### Method `setPreprocessorCode(code)`
|
||||
Set an Enso code which will be evaluated on the server-side before sending
|
||||
data to visualization. If not called, a default unspecified code is used
|
||||
that will provide some JSON representation of the value. See
|
||||
[Lazy visualizations](#lazy-visualizations) section for details.
|
||||
- #### Method `setPreprocessorModule(module)`
|
||||
Define in which module's context the preprocessor code should be evaluated.
|
||||
If not called, the `Main` module of the project that defines visualization
|
||||
will be used. See [Lazy visualizations](#lazy-visualizations) section for
|
||||
details.
|
||||
- #### Method `setPreprocessor(code,mode)`
|
||||
Set both code and its module context at once. If both need to be updated,
|
||||
using this method can save an update processing and needless evaluation.
|
||||
Note that using both `setPreprocessorCode` and `setPreprocessorModule` from
|
||||
the visualization's custom constructor will not cause any unnecessary
|
||||
updates, as the preprocessor is applied only after visualization is fully
|
||||
constructed. See [Lazy visualizations](#lazy-visualizations) section for
|
||||
details.
|
||||
- #### Method `setPreprocessor(module,method,...arguments)`
|
||||
Set an Enso method which will be evaluated on the server-side before sending
|
||||
data to visualization. Note that `arguments` is a vararg. If not called, a
|
||||
default unspecified method is used that will provide some JSON
|
||||
representation of the value. See [Lazy visualizations](#lazy-visualizations)
|
||||
section for details.
|
||||
- #### Field `dom`
|
||||
It is initialized in the constructor to the DOM symbol used to host the
|
||||
visualization content. Users are free to modify the DOM element, including
|
||||
@ -297,9 +284,8 @@ visualization you may use the `setPreprocessor` method). This code defines an
|
||||
Enso function, which will be run by the compiler on data the visualization is
|
||||
attached to. Only the results of this code will be sent to the GUI. In the case
|
||||
of the JSON input format, the result of the call should be a valid JSON string.
|
||||
The code will be evaluated in the context of the `Main` module in the project
|
||||
where visualization is defined - you may use any symbol defined or imported in
|
||||
that module.
|
||||
The code will be evaluated in the context of the module where the preprocessor
|
||||
method is defined - you may use any symbol defined or imported in that module.
|
||||
|
||||
For example, imagine you want to display a heatmap of 10 million points on a
|
||||
map, and these points change rapidly. Sending such an amount of information via
|
||||
|
@ -9,6 +9,7 @@ use crate::prelude::*;
|
||||
use crate::model::execution_context::ComponentGroup;
|
||||
use crate::model::execution_context::ComputedValueInfoRegistry;
|
||||
use crate::model::execution_context::LocalCall;
|
||||
use crate::model::execution_context::QualifiedMethodPointer;
|
||||
use crate::model::execution_context::Visualization;
|
||||
use crate::model::execution_context::VisualizationId;
|
||||
use crate::model::execution_context::VisualizationUpdateData;
|
||||
@ -142,10 +143,10 @@ impl Handle {
|
||||
pub fn modify_visualization(
|
||||
&self,
|
||||
id: VisualizationId,
|
||||
expression: Option<String>,
|
||||
module: Option<model::module::QualifiedName>,
|
||||
method_pointer: Option<QualifiedMethodPointer>,
|
||||
arguments: Option<Vec<String>>,
|
||||
) -> BoxFuture<FallibleResult> {
|
||||
self.execution_ctx.modify_visualization(id, expression, module)
|
||||
self.execution_ctx.modify_visualization(id, method_pointer, arguments)
|
||||
}
|
||||
|
||||
/// See [`model::ExecutionContext::detach_visualization`].
|
||||
@ -165,18 +166,6 @@ impl Handle {
|
||||
self.execution_ctx.computed_value_info_registry()
|
||||
}
|
||||
|
||||
/// Modify preprocessor code in visualization. See also
|
||||
/// [`model::ExecutionContext::modify_visualization`].
|
||||
#[profile(Detail)]
|
||||
pub async fn set_visualization_preprocessor(
|
||||
&self,
|
||||
id: VisualizationId,
|
||||
code: String,
|
||||
module: model::module::QualifiedName,
|
||||
) -> FallibleResult {
|
||||
self.execution_ctx.modify_visualization(id, Some(code), Some(module)).await
|
||||
}
|
||||
|
||||
/// See [`model::ExecutionContext::component_groups`].
|
||||
pub fn component_groups(&self) -> Rc<Vec<ComponentGroup>> {
|
||||
self.execution_ctx.component_groups()
|
||||
|
@ -1,12 +1,16 @@
|
||||
//! This module consists of all structures describing Execution Context.
|
||||
|
||||
use crate::prelude::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::model::module::QualifiedName as ModuleQualifiedName;
|
||||
use crate::model::suggestion_database::entry as suggestion;
|
||||
use crate::notification::Publisher;
|
||||
use flo_stream::Subscriber;
|
||||
use mockall::automock;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use uuid::Uuid;
|
||||
|
||||
use double_representation::identifier::Identifier;
|
||||
use double_representation::project;
|
||||
use double_representation::tp;
|
||||
use engine_protocol::language_server;
|
||||
use engine_protocol::language_server::ExpressionUpdate;
|
||||
use engine_protocol::language_server::ExpressionUpdatePayload;
|
||||
@ -14,13 +18,11 @@ use engine_protocol::language_server::MethodPointer;
|
||||
use engine_protocol::language_server::SuggestionId;
|
||||
use engine_protocol::language_server::VisualisationConfiguration;
|
||||
use ensogl::data::color;
|
||||
use flo_stream::Subscriber;
|
||||
use mockall::automock;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::model::module;
|
||||
use crate::model::suggestion_database::entry as suggestion;
|
||||
use crate::notification::Publisher;
|
||||
use crate::prelude::*;
|
||||
|
||||
// ==============
|
||||
// === Export ===
|
||||
@ -216,6 +218,82 @@ pub struct LocalCall {
|
||||
|
||||
|
||||
|
||||
// ==============================
|
||||
// === QualifiedMethodPointer ===
|
||||
// ==============================
|
||||
|
||||
/// A method pointer containing the qualified module and type names.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct QualifiedMethodPointer {
|
||||
/// A module name containing the method.
|
||||
pub module: module::QualifiedName,
|
||||
/// A type on which the method is defined.
|
||||
pub defined_on_type: tp::QualifiedName,
|
||||
/// A method name.
|
||||
pub name: Identifier,
|
||||
}
|
||||
|
||||
impl QualifiedMethodPointer {
|
||||
/// Create a method pointer representing a module method.
|
||||
pub fn module_method(
|
||||
module: module::QualifiedName,
|
||||
name: Identifier,
|
||||
) -> QualifiedMethodPointer {
|
||||
QualifiedMethodPointer { module: module.clone(), defined_on_type: module.into(), name }
|
||||
}
|
||||
/// Tries to create a new method pointer from string components.
|
||||
pub fn from_qualified_text(
|
||||
module: &str,
|
||||
defined_on_type: &str,
|
||||
name: &str,
|
||||
) -> FallibleResult<QualifiedMethodPointer> {
|
||||
let resolved_module = module.try_into()?;
|
||||
let resolved_type = defined_on_type.try_into()?;
|
||||
let name_identifier = Identifier::from_text(name)?;
|
||||
Ok(QualifiedMethodPointer {
|
||||
module: resolved_module,
|
||||
defined_on_type: resolved_type,
|
||||
name: name_identifier,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<MethodPointer> for QualifiedMethodPointer {
|
||||
type Error = failure::Error;
|
||||
|
||||
fn try_from(method_pointer: MethodPointer) -> Result<Self, Self::Error> {
|
||||
Self::try_from(&method_pointer)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&MethodPointer> for QualifiedMethodPointer {
|
||||
type Error = failure::Error;
|
||||
|
||||
fn try_from(method_pointer: &MethodPointer) -> Result<Self, Self::Error> {
|
||||
let module = method_pointer.module.as_str().try_into()?;
|
||||
let defined_on_type = method_pointer.defined_on_type.as_str().try_into()?;
|
||||
let name = Identifier::from_text(method_pointer.name.clone())?;
|
||||
Ok(QualifiedMethodPointer { module, defined_on_type, name })
|
||||
}
|
||||
}
|
||||
|
||||
impl From<QualifiedMethodPointer> for MethodPointer {
|
||||
fn from(qualified_method_pointer: QualifiedMethodPointer) -> Self {
|
||||
Self::from(&qualified_method_pointer)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&QualifiedMethodPointer> for MethodPointer {
|
||||
fn from(qualified_method_pointer: &QualifiedMethodPointer) -> Self {
|
||||
let module = qualified_method_pointer.module.clone().into();
|
||||
let defined_on_type = qualified_method_pointer.defined_on_type.clone().into();
|
||||
let name = qualified_method_pointer.name.name().to_owned();
|
||||
MethodPointer { module, defined_on_type, name }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =====================
|
||||
// === Visualization ===
|
||||
// =====================
|
||||
@ -227,13 +305,13 @@ pub type VisualizationId = Uuid;
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Visualization {
|
||||
/// Unique identifier of this visualization.
|
||||
pub id: VisualizationId,
|
||||
pub id: VisualizationId,
|
||||
/// Expression that is to be visualized.
|
||||
pub expression_id: ExpressionId,
|
||||
/// An enso lambda that will transform the data into expected format, e.g. `a -> a.json`.
|
||||
pub preprocessor_code: String,
|
||||
/// Visualization module -- the module in which context the preprocessor code is evaluated.
|
||||
pub context_module: ModuleQualifiedName,
|
||||
pub expression_id: ExpressionId,
|
||||
/// A pointer to the enso method that will transform the data into expected format.
|
||||
pub method_pointer: QualifiedMethodPointer,
|
||||
/// Enso expressions for positional arguments
|
||||
pub arguments: Vec<String>,
|
||||
}
|
||||
|
||||
impl Visualization {
|
||||
@ -241,18 +319,22 @@ impl Visualization {
|
||||
/// identifier.
|
||||
pub fn new(
|
||||
expression_id: ExpressionId,
|
||||
preprocessor_code: String,
|
||||
context_module: ModuleQualifiedName,
|
||||
method_pointer: QualifiedMethodPointer,
|
||||
arguments: Vec<String>,
|
||||
) -> Visualization {
|
||||
let id = VisualizationId::new_v4();
|
||||
Visualization { id, expression_id, preprocessor_code, context_module }
|
||||
Visualization { id, expression_id, method_pointer, arguments }
|
||||
}
|
||||
|
||||
/// Creates a `VisualisationConfiguration` that is used in communication with language server.
|
||||
pub fn config(&self, execution_context_id: Uuid) -> VisualisationConfiguration {
|
||||
let expression = self.preprocessor_code.clone();
|
||||
let visualisation_module = self.context_module.to_string();
|
||||
VisualisationConfiguration { execution_context_id, visualisation_module, expression }
|
||||
let expression = self.method_pointer.clone().into();
|
||||
let positional_arguments_expressions = self.arguments.clone();
|
||||
VisualisationConfiguration {
|
||||
execution_context_id,
|
||||
expression,
|
||||
positional_arguments_expressions,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -391,8 +473,8 @@ pub trait API: Debug {
|
||||
fn modify_visualization<'a>(
|
||||
&'a self,
|
||||
id: VisualizationId,
|
||||
expression: Option<String>,
|
||||
module: Option<ModuleQualifiedName>,
|
||||
method_pointer: Option<QualifiedMethodPointer>,
|
||||
arguments: Option<Vec<String>>,
|
||||
) -> BoxFuture<'a, FallibleResult>;
|
||||
|
||||
/// Dispatches the visualization update data (typically received from as LS binary notification)
|
||||
@ -442,14 +524,14 @@ pub type Synchronized = synchronized::ExecutionContext;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::executor::test_utils::TestWithLocalPoolExecutor;
|
||||
|
||||
use engine_protocol::language_server::types::test::value_update_with_dataflow_error;
|
||||
use engine_protocol::language_server::types::test::value_update_with_dataflow_panic;
|
||||
use engine_protocol::language_server::types::test::value_update_with_type;
|
||||
|
||||
use crate::executor::test_utils::TestWithLocalPoolExecutor;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn getting_future_type_from_registry() {
|
||||
let mut fixture = TestWithLocalPoolExecutor::set_up();
|
||||
|
@ -6,10 +6,10 @@ use crate::model::execution_context::AttachedVisualization;
|
||||
use crate::model::execution_context::ComponentGroup;
|
||||
use crate::model::execution_context::ComputedValueInfoRegistry;
|
||||
use crate::model::execution_context::LocalCall;
|
||||
use crate::model::execution_context::QualifiedMethodPointer;
|
||||
use crate::model::execution_context::Visualization;
|
||||
use crate::model::execution_context::VisualizationId;
|
||||
use crate::model::execution_context::VisualizationUpdateData;
|
||||
use crate::model::module;
|
||||
|
||||
use engine_protocol::language_server::MethodPointer;
|
||||
use engine_protocol::language_server::VisualisationConfiguration;
|
||||
@ -134,17 +134,17 @@ impl ExecutionContext {
|
||||
pub fn modify_visualization(
|
||||
&self,
|
||||
id: VisualizationId,
|
||||
expression: Option<String>,
|
||||
module: Option<module::QualifiedName>,
|
||||
method_pointer: Option<QualifiedMethodPointer>,
|
||||
arguments: Option<Vec<String>>,
|
||||
) -> FallibleResult {
|
||||
let err = || InvalidVisualizationId(id);
|
||||
let mut visualizations = self.visualizations.borrow_mut();
|
||||
let visualization = &mut visualizations.get_mut(&id).ok_or_else(err)?.visualization;
|
||||
if let Some(expression) = expression {
|
||||
visualization.preprocessor_code = expression;
|
||||
if let Some(method_pointer) = method_pointer {
|
||||
visualization.method_pointer = method_pointer;
|
||||
}
|
||||
if let Some(module) = module {
|
||||
visualization.context_module = module;
|
||||
if let Some(arguments) = arguments {
|
||||
visualization.arguments = arguments;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -227,10 +227,11 @@ impl model::execution_context::API for ExecutionContext {
|
||||
fn modify_visualization(
|
||||
&self,
|
||||
id: VisualizationId,
|
||||
expression: Option<String>,
|
||||
module: Option<module::QualifiedName>,
|
||||
method_pointer: Option<QualifiedMethodPointer>,
|
||||
arguments: Option<Vec<String>>,
|
||||
) -> BoxFuture<FallibleResult> {
|
||||
futures::future::ready(self.modify_visualization(id, expression, module)).boxed_local()
|
||||
futures::future::ready(self.modify_visualization(id, method_pointer, arguments))
|
||||
.boxed_local()
|
||||
}
|
||||
|
||||
fn dispatch_visualization_update(
|
||||
|
@ -5,10 +5,10 @@ use crate::prelude::*;
|
||||
use crate::model::execution_context::ComponentGroup;
|
||||
use crate::model::execution_context::ComputedValueInfoRegistry;
|
||||
use crate::model::execution_context::LocalCall;
|
||||
use crate::model::execution_context::QualifiedMethodPointer;
|
||||
use crate::model::execution_context::Visualization;
|
||||
use crate::model::execution_context::VisualizationId;
|
||||
use crate::model::execution_context::VisualizationUpdateData;
|
||||
use crate::model::module;
|
||||
|
||||
use engine_protocol::language_server;
|
||||
|
||||
@ -269,10 +269,10 @@ impl model::execution_context::API for ExecutionContext {
|
||||
fn modify_visualization(
|
||||
&self,
|
||||
id: VisualizationId,
|
||||
expression: Option<String>,
|
||||
module: Option<module::QualifiedName>,
|
||||
method_pointer: Option<QualifiedMethodPointer>,
|
||||
arguments: Option<Vec<String>>,
|
||||
) -> BoxFuture<FallibleResult> {
|
||||
let result = self.model.modify_visualization(id, expression, module);
|
||||
let result = self.model.modify_visualization(id, method_pointer, arguments);
|
||||
let new_config = self.model.visualization_config(id, self.id);
|
||||
async move {
|
||||
result?;
|
||||
@ -315,6 +315,7 @@ impl Drop for ExecutionContext {
|
||||
#[cfg(test)]
|
||||
pub mod test {
|
||||
use super::*;
|
||||
use double_representation::identifier::Identifier;
|
||||
|
||||
use crate::executor::test_utils::TestWithLocalPoolExecutor;
|
||||
use crate::model::execution_context::plain::test::MockData;
|
||||
@ -449,11 +450,16 @@ pub mod test {
|
||||
|
||||
#[test]
|
||||
fn attaching_visualizations_and_notifying() {
|
||||
let method_pointer = QualifiedMethodPointer::module_method(
|
||||
MockData::new().module_qualified_name(),
|
||||
Identifier::from_text("foo").unwrap(),
|
||||
);
|
||||
let arguments = vec![];
|
||||
let vis = Visualization {
|
||||
id: model::execution_context::VisualizationId::new_v4(),
|
||||
expression_id: model::execution_context::ExpressionId::new_v4(),
|
||||
preprocessor_code: "".to_string(),
|
||||
context_module: MockData::new().module_qualified_name(),
|
||||
id: model::execution_context::VisualizationId::new_v4(),
|
||||
expression_id: model::execution_context::ExpressionId::new_v4(),
|
||||
method_pointer,
|
||||
arguments,
|
||||
};
|
||||
let Fixture { mut test, context, .. } = Fixture::new_customized(|ls, data| {
|
||||
let exe_id = data.context_id;
|
||||
@ -493,11 +499,16 @@ pub mod test {
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn detaching_all_visualizations() {
|
||||
let method_pointer = QualifiedMethodPointer::module_method(
|
||||
MockData::new().module_qualified_name(),
|
||||
Identifier::from_text("foo").unwrap(),
|
||||
);
|
||||
let arguments = vec!["foo".to_owned()];
|
||||
let vis = Visualization {
|
||||
id: model::execution_context::VisualizationId::new_v4(),
|
||||
expression_id: model::execution_context::ExpressionId::new_v4(),
|
||||
preprocessor_code: "".to_string(),
|
||||
context_module: MockData::new().module_qualified_name(),
|
||||
id: model::execution_context::VisualizationId::new_v4(),
|
||||
expression_id: model::execution_context::ExpressionId::new_v4(),
|
||||
method_pointer,
|
||||
arguments,
|
||||
};
|
||||
let vis2 = Visualization { id: VisualizationId::new_v4(), ..vis.clone() };
|
||||
|
||||
@ -525,15 +536,22 @@ pub mod test {
|
||||
|
||||
#[test]
|
||||
fn modifying_visualizations() {
|
||||
let method_pointer = QualifiedMethodPointer::module_method(
|
||||
MockData::new().module_qualified_name(),
|
||||
Identifier::from_text("foo").unwrap(),
|
||||
);
|
||||
let arguments = vec!["bar".to_owned()];
|
||||
let vis = Visualization {
|
||||
id: model::execution_context::VisualizationId::new_v4(),
|
||||
expression_id: model::execution_context::ExpressionId::new_v4(),
|
||||
preprocessor_code: "x -> x.to_json.to_string".to_string(),
|
||||
context_module: MockData::new().module_qualified_name(),
|
||||
id: model::execution_context::VisualizationId::new_v4(),
|
||||
expression_id: model::execution_context::ExpressionId::new_v4(),
|
||||
method_pointer,
|
||||
arguments: arguments.clone(),
|
||||
};
|
||||
let vis_id = vis.id;
|
||||
let new_expression = "x -> x";
|
||||
let new_module = "Test.Test_Module";
|
||||
let new_expression = QualifiedMethodPointer::module_method(
|
||||
MockData::new().module_qualified_name(),
|
||||
Identifier::from_text("quux").unwrap(),
|
||||
);
|
||||
let Fixture { mut test, context, .. } = Fixture::new_customized(|ls, data| {
|
||||
let exe_id = data.context_id;
|
||||
let ast_id = vis.expression_id;
|
||||
@ -541,8 +559,8 @@ pub mod test {
|
||||
|
||||
let expected_config = language_server::types::VisualisationConfiguration {
|
||||
execution_context_id: data.context_id,
|
||||
visualisation_module: new_module.to_owned(),
|
||||
expression: new_expression.to_owned(),
|
||||
expression: new_expression.clone().into(),
|
||||
positional_arguments_expressions: arguments.clone(),
|
||||
};
|
||||
|
||||
expect_call!(ls.attach_visualisation(vis_id,ast_id,config) => Ok(()));
|
||||
@ -551,9 +569,8 @@ pub mod test {
|
||||
|
||||
test.run_task(async move {
|
||||
context.attach_visualization(vis.clone()).await.unwrap();
|
||||
let expression = Some(new_expression.to_owned());
|
||||
let module = Some(QualifiedName::from_text(new_module).unwrap());
|
||||
context.modify_visualization(vis_id, expression, module).await.unwrap();
|
||||
let method_pointer = Some(new_expression);
|
||||
context.modify_visualization(vis_id, method_pointer, Some(arguments)).await.unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -181,10 +181,8 @@ impl Visualization {
|
||||
let network = frp::Network::new("presenter::graph::Visualization");
|
||||
|
||||
let controller = project.visualization().clone_ref();
|
||||
let (manager, notifications) =
|
||||
Manager::new(&logger, graph.clone_ref(), project.clone_ref());
|
||||
let (error_manager, error_notifications) =
|
||||
Manager::new(&logger, graph.clone_ref(), project);
|
||||
let (manager, notifications) = Manager::new(&logger, graph.clone_ref());
|
||||
let (error_manager, error_notifications) = Manager::new(&logger, graph.clone_ref());
|
||||
let model = Rc::new(Model {
|
||||
logger,
|
||||
controller,
|
||||
|
@ -4,6 +4,7 @@ use crate::prelude::*;
|
||||
|
||||
use crate::controller::ExecutedGraph;
|
||||
use crate::executor::global::spawn;
|
||||
use crate::model::execution_context::QualifiedMethodPointer;
|
||||
use crate::model::execution_context::Visualization;
|
||||
use crate::model::execution_context::VisualizationId;
|
||||
use crate::model::execution_context::VisualizationUpdateData;
|
||||
@ -11,31 +12,11 @@ use crate::sync::Synchronized;
|
||||
|
||||
use futures::channel::mpsc::UnboundedReceiver;
|
||||
use futures::future::ready;
|
||||
use ide_view::graph_editor::component::visualization;
|
||||
use ide_view::graph_editor::component::visualization::instance::ContextModule;
|
||||
use ide_view::graph_editor::component::visualization::Metadata;
|
||||
use ide_view::graph_editor::SharedHashMap;
|
||||
|
||||
|
||||
|
||||
// ================================
|
||||
// === Resolving Context Module ===
|
||||
// ================================
|
||||
|
||||
/// Resolve the context module to a fully qualified name.
|
||||
pub fn resolve_context_module(
|
||||
context_module: &ContextModule,
|
||||
main_module_name: impl FnOnce() -> model::module::QualifiedName,
|
||||
) -> FallibleResult<model::module::QualifiedName> {
|
||||
use visualization::instance::ContextModule::*;
|
||||
match context_module {
|
||||
ProjectMain => Ok(main_module_name()),
|
||||
Specific(module_name) => model::module::QualifiedName::from_text(module_name),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==============
|
||||
// === Errors ===
|
||||
// ==============
|
||||
@ -94,6 +75,7 @@ pub enum Notification {
|
||||
|
||||
/// Describes the state of the visualization on the Language Server.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum Status {
|
||||
/// Not attached and no ongoing background work.
|
||||
NotAttached,
|
||||
@ -225,7 +207,6 @@ pub struct Manager {
|
||||
logger: Logger,
|
||||
visualizations: SharedHashMap<ast::Id, Description>,
|
||||
executed_graph: ExecutedGraph,
|
||||
project: model::Project,
|
||||
notification_sender: futures::channel::mpsc::UnboundedSender<Notification>,
|
||||
}
|
||||
|
||||
@ -237,17 +218,10 @@ impl Manager {
|
||||
pub fn new(
|
||||
logger: impl AnyLogger,
|
||||
executed_graph: ExecutedGraph,
|
||||
project: model::Project,
|
||||
) -> (Rc<Self>, UnboundedReceiver<Notification>) {
|
||||
let logger = logger.sub("visualization::Manager");
|
||||
let (notification_sender, notification_receiver) = futures::channel::mpsc::unbounded();
|
||||
let ret = Self {
|
||||
logger,
|
||||
visualizations: default(),
|
||||
executed_graph,
|
||||
project,
|
||||
notification_sender,
|
||||
};
|
||||
let ret = Self { logger, visualizations: default(), executed_graph, notification_sender };
|
||||
(Rc::new(ret), notification_receiver)
|
||||
}
|
||||
|
||||
@ -352,21 +326,20 @@ impl Manager {
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_context_module(
|
||||
&self,
|
||||
context_module: &ContextModule,
|
||||
) -> FallibleResult<model::module::QualifiedName> {
|
||||
resolve_context_module(context_module, || self.project.main_module())
|
||||
}
|
||||
|
||||
fn prepare_visualization(&self, desired: Desired) -> FallibleResult<Visualization> {
|
||||
let context_module = desired.metadata.preprocessor.module;
|
||||
let resolved_module = self.resolve_context_module(&context_module)?;
|
||||
let preprocessor_module = desired.metadata.preprocessor.module;
|
||||
let preprocessor_method = desired.metadata.preprocessor.method;
|
||||
let method_pointer = QualifiedMethodPointer::from_qualified_text(
|
||||
&preprocessor_module,
|
||||
&preprocessor_module,
|
||||
&preprocessor_method,
|
||||
)?;
|
||||
let arguments = desired.metadata.preprocessor.arguments.deref().iter().map_into().collect();
|
||||
Ok(Visualization {
|
||||
id: desired.visualization_id,
|
||||
expression_id: desired.expression_id,
|
||||
preprocessor_code: desired.metadata.preprocessor.code.to_string(),
|
||||
context_module: resolved_module,
|
||||
id: desired.visualization_id,
|
||||
expression_id: desired.expression_id,
|
||||
method_pointer,
|
||||
arguments,
|
||||
})
|
||||
}
|
||||
|
||||
@ -502,10 +475,10 @@ impl Manager {
|
||||
Status::BeingModified { from: so_far.clone(), to: new_visualization.clone() };
|
||||
self.update_status(target, status);
|
||||
let id = so_far.id;
|
||||
let expression = new_visualization.preprocessor_code.clone();
|
||||
let module = new_visualization.context_module.clone();
|
||||
let method_pointer = new_visualization.method_pointer.clone();
|
||||
let arguments = new_visualization.arguments.clone();
|
||||
let modifying_result =
|
||||
self.executed_graph.modify_visualization(id, Some(expression), Some(module));
|
||||
self.executed_graph.modify_visualization(id, Some(method_pointer), Some(arguments));
|
||||
match modifying_result.await {
|
||||
Ok(_) => {
|
||||
let status = Status::Attached(new_visualization);
|
||||
@ -532,8 +505,10 @@ impl Manager {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::model::module;
|
||||
|
||||
use double_representation::identifier::Identifier;
|
||||
use futures::future::ready;
|
||||
use ide_view::graph_editor::component::visualization::instance::ContextModule;
|
||||
use ide_view::graph_editor::component::visualization::instance::PreprocessorConfiguration;
|
||||
use std::assert_matches::assert_matches;
|
||||
use wasm_bindgen_test::wasm_bindgen_test;
|
||||
@ -553,12 +528,17 @@ mod tests {
|
||||
Self { inner, node_id }
|
||||
}
|
||||
|
||||
fn vis_metadata(&self, code: impl Into<String>) -> Metadata {
|
||||
fn vis_metadata(
|
||||
&self,
|
||||
method: impl Into<String>,
|
||||
arguments: Vec<impl Into<String>>,
|
||||
) -> Metadata {
|
||||
Metadata {
|
||||
preprocessor: PreprocessorConfiguration {
|
||||
module: ContextModule::Specific(self.inner.module_name().to_string().into()),
|
||||
code: code.into().into(),
|
||||
},
|
||||
preprocessor: PreprocessorConfiguration::new(
|
||||
self.inner.module_name().to_string(),
|
||||
method.into(),
|
||||
arguments.into_iter().map_into().collect(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -567,11 +547,7 @@ mod tests {
|
||||
enum ExecutionContextRequest {
|
||||
Attach(Visualization),
|
||||
Detach(VisualizationId),
|
||||
Modify {
|
||||
id: VisualizationId,
|
||||
expression: Option<String>,
|
||||
module: Option<model::module::QualifiedName>,
|
||||
},
|
||||
Modify { id: VisualizationId, method_pointer: Option<QualifiedMethodPointer> },
|
||||
}
|
||||
|
||||
#[derive(Shrinkwrap)]
|
||||
@ -590,11 +566,18 @@ mod tests {
|
||||
|
||||
impl VisOperationsTester {
|
||||
fn new(inner: Fixture) -> Self {
|
||||
let qualified_module = inner.project.qualified_module_name(inner.module.path());
|
||||
let method_pointer = QualifiedMethodPointer {
|
||||
module: qualified_module.clone(),
|
||||
defined_on_type: qualified_module.into(),
|
||||
name: Identifier::from_text("faux").unwrap(),
|
||||
};
|
||||
let arguments = vec!["foo".to_owned()];
|
||||
let faux_vis = Visualization {
|
||||
id: default(),
|
||||
expression_id: default(),
|
||||
context_module: inner.project.qualified_module_name(inner.module.path()),
|
||||
preprocessor_code: "faux value".into(),
|
||||
id: default(),
|
||||
expression_id: default(),
|
||||
method_pointer,
|
||||
arguments,
|
||||
};
|
||||
let is_ready = Synchronized::new(false);
|
||||
let mut execution_context = model::execution_context::MockAPI::new();
|
||||
@ -619,8 +602,8 @@ mod tests {
|
||||
|
||||
let sender = request_sender;
|
||||
execution_context.expect_modify_visualization().returning_st(
|
||||
move |id, expression, module| {
|
||||
let request = ExecutionContextRequest::Modify { id, expression, module };
|
||||
move |id, method_pointer, _| {
|
||||
let request = ExecutionContextRequest::Modify { id, method_pointer };
|
||||
sender.unbounded_send(request).unwrap();
|
||||
ready(Ok(())).boxed_local()
|
||||
},
|
||||
@ -633,20 +616,16 @@ mod tests {
|
||||
execution_context,
|
||||
);
|
||||
let logger: Logger = inner.logger.sub("manager");
|
||||
let (manager, notifier) =
|
||||
Manager::new(logger, executed_graph.clone_ref(), inner.project.clone_ref());
|
||||
let (manager, notifier) = Manager::new(logger, executed_graph.clone_ref());
|
||||
Self { inner, is_ready, manager, notifier, requests }
|
||||
}
|
||||
}
|
||||
|
||||
fn matching_metadata(
|
||||
manager: &Manager,
|
||||
visualization: &Visualization,
|
||||
metadata: &Metadata,
|
||||
) -> bool {
|
||||
let PreprocessorConfiguration { module, code } = &metadata.preprocessor;
|
||||
visualization.preprocessor_code == code.to_string()
|
||||
&& visualization.context_module == manager.resolve_context_module(module).unwrap()
|
||||
fn matching_metadata(visualization: &Visualization, metadata: &Metadata) -> bool {
|
||||
let PreprocessorConfiguration { module, method, .. } = &metadata.preprocessor;
|
||||
let qualified_module: module::QualifiedName = module.deref().try_into().unwrap();
|
||||
visualization.method_pointer.module == qualified_module
|
||||
&& visualization.method_pointer.name.name() == method.deref()
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
@ -654,8 +633,8 @@ mod tests {
|
||||
let fixture = Fixture::new();
|
||||
let node_id = fixture.node_id;
|
||||
let fixture = VisOperationsTester::new(fixture);
|
||||
let desired_vis_1 = fixture.vis_metadata("expr1");
|
||||
let desired_vis_2 = fixture.vis_metadata("expr2");
|
||||
let desired_vis_1 = fixture.vis_metadata("expr1", vec!["one"]);
|
||||
let desired_vis_2 = fixture.vis_metadata("expr2", vec!["two"]);
|
||||
let VisOperationsTester { mut requests, manager, mut inner, is_ready, .. } = fixture;
|
||||
|
||||
// No requests are sent before execution context is ready.
|
||||
@ -686,15 +665,15 @@ mod tests {
|
||||
manager.request_visualization(node_id, desired_vis_1.clone());
|
||||
manager.request_visualization(node_id, desired_vis_1.clone());
|
||||
inner.run_until_stalled();
|
||||
if let ExecutionContextRequest::Modify { id, expression, module } = requests.expect_one() {
|
||||
assert!(expression.contains(&desired_vis_1.preprocessor.code.to_string()));
|
||||
if let ExecutionContextRequest::Modify { id, method_pointer } = requests.expect_one() {
|
||||
let desired_method_pointer = QualifiedMethodPointer::from_qualified_text(
|
||||
&desired_vis_1.preprocessor.module,
|
||||
&desired_vis_1.preprocessor.module,
|
||||
&desired_vis_1.preprocessor.method,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(method_pointer.contains(&desired_method_pointer));
|
||||
assert_eq!(id, attached_id);
|
||||
let get_main_module = || inner.inner.project.main_module();
|
||||
let expected_module =
|
||||
resolve_context_module(&desired_vis_1.preprocessor.module, get_main_module)
|
||||
.unwrap();
|
||||
assert_eq!(module, Some(expected_module));
|
||||
// assert!(module.is_none());
|
||||
}
|
||||
|
||||
// If visualization changes ID, then we need to use detach-attach API.
|
||||
@ -716,6 +695,6 @@ mod tests {
|
||||
other => panic!("Expected a detach request, got: {:?}", other),
|
||||
}
|
||||
assert_matches!(requests.expect_next(), ExecutionContextRequest::Attach(vis)
|
||||
if matching_metadata(&manager,&vis,&desired_vis_3.metadata));
|
||||
if matching_metadata(&vis,&desired_vis_3.metadata));
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,8 @@ use engine_protocol::language_server::*;
|
||||
use engine_protocol::types::*;
|
||||
use enso_gui::prelude::*;
|
||||
|
||||
use double_representation::identifier::Identifier;
|
||||
use enso_gui::model::execution_context::QualifiedMethodPointer;
|
||||
use enso_gui::model::execution_context::Visualization;
|
||||
use enso_gui::model::module;
|
||||
use enso_gui::transport::web::WebSocket;
|
||||
@ -27,7 +29,6 @@ use wasm_bindgen_test::wasm_bindgen_test;
|
||||
use wasm_bindgen_test::wasm_bindgen_test_configure;
|
||||
|
||||
|
||||
|
||||
/// The endpoint at which the Language Server should be accepting WS connections.
|
||||
const SERVER_ENDPOINT: &str = "ws://localhost:30616";
|
||||
|
||||
@ -125,18 +126,36 @@ async fn ls_text_protocol_test() {
|
||||
let visualisation_id = uuid::Uuid::new_v4();
|
||||
let expression_id = uuid::Uuid::parse_str("c553533e-a2b9-4305-9f12-b8fe7781f933");
|
||||
let expression_id = expression_id.expect("Couldn't parse expression id.");
|
||||
let expression = "x -> here.encode x".to_string();
|
||||
let visualisation_module = "Test.Visualisation".to_string();
|
||||
let visualisation_config =
|
||||
VisualisationConfiguration { execution_context_id, expression, visualisation_module };
|
||||
let visualization_function = "foo".to_string();
|
||||
let visualization_module = "Test.Visualisation";
|
||||
let expression = MethodPointer {
|
||||
module: visualization_module.to_string(),
|
||||
defined_on_type: visualization_module.to_string(),
|
||||
name: visualization_function,
|
||||
};
|
||||
let positional_arguments_expressions = vec!["1".to_owned()];
|
||||
let visualisation_config = VisualisationConfiguration {
|
||||
execution_context_id,
|
||||
expression,
|
||||
positional_arguments_expressions,
|
||||
};
|
||||
let response =
|
||||
client.attach_visualisation(&visualisation_id, &expression_id, &visualisation_config);
|
||||
response.await.expect("Couldn't attach visualisation.");
|
||||
|
||||
let expression = "x -> here.incAndEncode".to_string();
|
||||
let visualisation_module = "Test.Visualisation".to_string();
|
||||
let visualisation_config =
|
||||
VisualisationConfiguration { execution_context_id, expression, visualisation_module };
|
||||
let visualization_function = "bar".to_string();
|
||||
let visualization_module = "Test.Visualisation";
|
||||
let expression = MethodPointer {
|
||||
module: visualization_module.to_string(),
|
||||
defined_on_type: visualization_module.to_string(),
|
||||
name: visualization_function,
|
||||
};
|
||||
let positional_arguments_expressions = vec!["1".to_owned(), "2".to_owned()];
|
||||
let visualisation_config = VisualisationConfiguration {
|
||||
execution_context_id,
|
||||
expression,
|
||||
positional_arguments_expressions,
|
||||
};
|
||||
let response = client.modify_visualisation(&visualisation_id, &visualisation_config).await;
|
||||
response.expect("Couldn't modify visualisation.");
|
||||
|
||||
@ -331,8 +350,6 @@ async fn binary_visualization_updates_test_hlp() {
|
||||
let project = ide.current_project().expect("IDE is configured without an open project.");
|
||||
info!(logger, "Got project: {project:?}");
|
||||
|
||||
let expression = "x -> x.json_serialize".to_owned();
|
||||
|
||||
use controller::project::MAIN_DEFINITION_NAME;
|
||||
use ensogl::system::web::sleep;
|
||||
|
||||
@ -354,7 +371,11 @@ async fn binary_visualization_updates_test_hlp() {
|
||||
info!(logger, "The code is: {module.ast().repr():?}");
|
||||
info!(logger, "Main node: {the_node:?} with {the_node.expression().repr()}");
|
||||
|
||||
let visualization = Visualization::new(the_node.id(), expression, module_qualified_name);
|
||||
let method_pointer = QualifiedMethodPointer::module_method(
|
||||
module_qualified_name,
|
||||
Identifier::from_text("quux").unwrap(),
|
||||
);
|
||||
let visualization = Visualization::new(the_node.id(), method_pointer, vec![]);
|
||||
let stream = graph_executed.attach_visualization(visualization.clone()).await.unwrap();
|
||||
info!(logger, "Attached the visualization {visualization.id}");
|
||||
let mut stream = stream.boxed_local();
|
||||
|
@ -55,7 +55,7 @@ fn constructor_graph() -> visualization::java_script::Definition {
|
||||
console.log("pressed",e);
|
||||
})
|
||||
}
|
||||
this.setPreprocessor(`x ->\n IO.println "Preprocessor set after receiving ${data}`)
|
||||
this.setPreprocessor('Standard.Visualization.Preprocessor', 'default_preprocessor');
|
||||
|
||||
let first = data.shift();
|
||||
if (first) {
|
||||
|
@ -104,7 +104,7 @@ class GeoMapVisualization extends Visualization {
|
||||
this.initMapElement()
|
||||
this.initStyle()
|
||||
this.dataPoints = []
|
||||
this.setPreprocessor('process_to_json_text', 'Standard.Visualization.Geo_Map')
|
||||
this.setPreprocessor('Standard.Visualization.Geo_Map', 'process_to_json_text')
|
||||
}
|
||||
|
||||
initMapElement() {
|
||||
|
@ -12,8 +12,7 @@ class Heatmap extends Visualization {
|
||||
|
||||
constructor(data) {
|
||||
super(data)
|
||||
this.setPreprocessorModule('Standard.Visualization.Table.Visualization')
|
||||
this.setPreprocessorCode(`x -> prepare_visualization x 1000`)
|
||||
this.setPreprocessor('Standard.Visualization.Table.Visualization', 'prepare_visualization')
|
||||
}
|
||||
|
||||
onDataReceived(data) {
|
||||
|
@ -6,7 +6,6 @@ loadStyle('https://fontlibrary.org/face/dejavu-sans-mono')
|
||||
let shortcuts = {
|
||||
zoomIn: e => (e.ctrlKey || e.metaKey) && e.key === 'z',
|
||||
showAll: e => (e.ctrlKey || e.metaKey) && e.key === 'a',
|
||||
debugPreprocessor: e => (e.ctrlKey || e.metaKey) && e.key === 'd',
|
||||
}
|
||||
|
||||
const LABEL_STYLE = 'font-family: DejaVuSansMonoBook; font-size: 10px;'
|
||||
@ -44,7 +43,7 @@ class Histogram extends Visualization {
|
||||
|
||||
constructor(data) {
|
||||
super(data)
|
||||
this.setPreprocessor('process_to_json_text', 'Standard.Visualization.Histogram')
|
||||
this.setPreprocessor('Standard.Visualization.Histogram', 'process_to_json_text')
|
||||
this._dataBins = []
|
||||
}
|
||||
|
||||
@ -59,7 +58,6 @@ class Histogram extends Visualization {
|
||||
this.initCanvas()
|
||||
this.initLabels()
|
||||
this.initHistogram()
|
||||
this.initDebugShortcut()
|
||||
}
|
||||
|
||||
this.updateLabels()
|
||||
@ -252,15 +250,6 @@ class Histogram extends Visualization {
|
||||
this.initBrushing(selectedZoomBtn, zoom)
|
||||
}
|
||||
|
||||
initDebugShortcut() {
|
||||
document.addEventListener('keydown', e => {
|
||||
if (shortcuts.debugPreprocessor(e)) {
|
||||
this.setPreprocessor('x -> "[1,2,3,4]"')
|
||||
e.preventDefault()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise panning and zooming functionality on the visualization.
|
||||
*/
|
||||
|
@ -62,11 +62,14 @@ class ScatterPlot extends Visualization {
|
||||
}
|
||||
|
||||
updatePreprocessor() {
|
||||
let fn = 'x -> process_to_json_text x limit=' + this.limit
|
||||
let args = []
|
||||
if (this.bounds) {
|
||||
fn += ' bounds=[' + this.bounds.join(',') + ']'
|
||||
args.push('[' + this.bounds.join(',') + ']')
|
||||
} else {
|
||||
args.push('Nothing')
|
||||
}
|
||||
this.setPreprocessor(fn, 'Standard.Visualization.Scatter_Plot')
|
||||
args.push(this.limit.toString())
|
||||
this.setPreprocessor('Standard.Visualization.Scatter_Plot', 'process_to_json_text', ...args)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -87,8 +87,7 @@ class SqlVisualization extends Visualization {
|
||||
|
||||
constructor(api) {
|
||||
super(api)
|
||||
this.setPreprocessorModule('Standard.Visualization.Sql.Visualization')
|
||||
this.setPreprocessorCode(`x -> prepare_visualization x`)
|
||||
this.setPreprocessor('Standard.Visualization.Sql.Visualization', 'prepare_visualization')
|
||||
}
|
||||
|
||||
onDataReceived(data) {
|
||||
|
@ -16,8 +16,7 @@ class TableVisualization extends Visualization {
|
||||
|
||||
constructor(data) {
|
||||
super(data)
|
||||
this.setPreprocessorModule('Standard.Visualization.Table.Visualization')
|
||||
this.setPreprocessorCode(`x -> prepare_visualization x 1000`)
|
||||
this.setPreprocessor('Standard.Visualization.Table.Visualization', 'prepare_visualization')
|
||||
}
|
||||
|
||||
onDataReceived(data) {
|
||||
|
@ -31,20 +31,27 @@ pub use crate::component::node::error::Kind;
|
||||
// =================
|
||||
|
||||
const PADDING_TEXT: f32 = 10.0;
|
||||
/// The Error Visualization preprocessor. See also _Lazy Visualization_ section
|
||||
/// [here](http://dev.enso.org/docs/ide/product/visualizations.html).
|
||||
// NOTE: contents of this const need to be kept in sync with Scala test in
|
||||
// RuntimeVisualisationsTest.scala, used to verify the snippet's correctness
|
||||
pub const PREPROCESSOR_CODE: &str = include_str!("inc/error_preprocessor.enso");
|
||||
|
||||
/// The context module for the `PREPROCESSOR_CODE`. See there.
|
||||
/// The module containing the `PREPROCESSOR_FUNCTION`. See there.
|
||||
// NOTE: contents of this const need to be kept in sync with Scala test in
|
||||
// RuntimeVisualisationsTest.scala, used to verify the snippet's correctness
|
||||
pub const PREPROCESSOR_MODULE: &str = "Standard.Base.Main";
|
||||
const PREPROCESSOR_MODULE: &str = "Standard.Visualization.Preprocessor";
|
||||
|
||||
/// The method name of the error preprocessor.
|
||||
// NOTE: contents of this const need to be kept in sync with Scala test in
|
||||
// RuntimeVisualisationsTest.scala, used to verify the snippet's correctness
|
||||
const PREPROCESSOR_METHOD: &str = "error_preprocessor";
|
||||
|
||||
/// The list of arguments passed to the error preprocessor.
|
||||
const PREPROCESSOR_ARGUMENTS: Vec<String> = vec![];
|
||||
|
||||
/// Get preprocessor configuration for error visualization.
|
||||
pub fn preprocessor() -> instance::PreprocessorConfiguration {
|
||||
instance::PreprocessorConfiguration::new(PREPROCESSOR_CODE, PREPROCESSOR_MODULE)
|
||||
instance::PreprocessorConfiguration::new(
|
||||
PREPROCESSOR_MODULE,
|
||||
PREPROCESSOR_METHOD,
|
||||
PREPROCESSOR_ARGUMENTS,
|
||||
)
|
||||
}
|
||||
|
||||
/// Get metadata description for error visualization.
|
||||
|
@ -197,9 +197,17 @@ impl JsConsArgs {
|
||||
}
|
||||
|
||||
/// Helper method to emit an preprocessor change event from the visualisation.
|
||||
pub fn emit_preprocessor_change(&self, code: Option<String>, module: Option<String>) {
|
||||
pub fn emit_preprocessor_change(
|
||||
&self,
|
||||
module: Option<String>,
|
||||
method: Option<String>,
|
||||
args: Option<Vec<js_sys::JsString>>,
|
||||
) {
|
||||
let closure = &self.set_preprocessor;
|
||||
let preprocessor_config = PreprocessorConfiguration::from_options(code, module);
|
||||
let arguments: Option<Vec<String>> =
|
||||
args.map(|argss| argss.into_iter().map_into().collect());
|
||||
let preprocessor_config =
|
||||
PreprocessorConfiguration::from_options(module, method, arguments);
|
||||
(*closure)(preprocessor_config);
|
||||
}
|
||||
}
|
||||
|
@ -15,8 +15,9 @@ export class Visualization {
|
||||
// These go before `api` assignment so the `undefined` is not emitted to IDE.
|
||||
// First we will give deriving type a chance to overwrite them, then IDE will
|
||||
// invoke `__emitPreprocessorChange__()` on this.
|
||||
this.__preprocessorCode__ = null
|
||||
this.__preprocessorModule__ = null
|
||||
this.__preprocessorMethod__ = null
|
||||
this.__preprocessorArguments__ = null
|
||||
|
||||
this.dom = api.root()
|
||||
this.theme = api.theme()
|
||||
@ -29,56 +30,26 @@ export class Visualization {
|
||||
*/
|
||||
__emitPreprocessorChange__() {
|
||||
this.__api__.emit_preprocessor_change(
|
||||
this.__preprocessorCode__,
|
||||
this.__preprocessorModule__
|
||||
this.__preprocessorModule__,
|
||||
this.__preprocessorMethod__,
|
||||
this.__preprocessorArguments__
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current preprocessor code. See {@link setPreprocessorCode} for
|
||||
* more information about purpose of setting preprocessor code.
|
||||
* Get the current preprocessor method. See {@link setPreprocessor} for
|
||||
* more information about purpose of setting the preprocessor.
|
||||
*
|
||||
* @returns {string} Preprocessor code or `null` if no code was set.
|
||||
* @returns {string} Preprocessor method or `null` if no method was set.
|
||||
*/
|
||||
getPreprocessorCode() {
|
||||
return this.__preprocessorCode__
|
||||
}
|
||||
|
||||
/**
|
||||
* Set new preprocessor code.
|
||||
*
|
||||
* When visualization is attached to a node, each time a new value is produced from node,
|
||||
* the preprocessor shall be invoked with it. Result such call shall be serialized and
|
||||
* transported to visualization by invoking onDataReceived(data) method.
|
||||
*
|
||||
* Typically the preprocessor is a lambda, like the example below:
|
||||
*
|
||||
* `x -> x.to_default_visualization_data`
|
||||
*
|
||||
*
|
||||
* The code by default runs in the context of the current project's `Main` module.
|
||||
* If other context is needed (e.g. due to required import or other module-specific
|
||||
* context dependency), the {@link setPreprocessorModule} should be used to provide
|
||||
* the module's name.
|
||||
*
|
||||
* Please refer to [documentation]{@link https://dev.enso.org/docs/ide/product/visualizations.html#lazy-visualizations}
|
||||
* for details.
|
||||
*
|
||||
* @param {string} code text code in Enso. It must be invokable with one argument and return
|
||||
* JSON-compatible result. For example:
|
||||
* <pre><code>x -> x.to_default_visualization_data</code></pre>
|
||||
*/
|
||||
setPreprocessorCode(code) {
|
||||
if (code !== this.__preprocessorCode__) {
|
||||
this.__preprocessorCode__ = code
|
||||
this.__emitPreprocessorChange__()
|
||||
}
|
||||
getPreprocessorMethod() {
|
||||
return this.__preprocessorMethod__
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current preprocessor's context module.
|
||||
*
|
||||
* See the [setter documentation]{@link setPreprocessorModule} for more information.
|
||||
* See the [setter documentation]{@link setPreprocessor} for more information.
|
||||
*
|
||||
* @returns {string} Qualified name to preprocessor's context module.
|
||||
*/
|
||||
@ -87,43 +58,77 @@ export class Visualization {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set preprocessor's context module.
|
||||
* Get the current preprocessor's arguments.
|
||||
*
|
||||
* [Preprocessor code]{@link setPreprocessorCode} is executed in the context of
|
||||
* certain Enso module. This decides what symbols are visible and available to
|
||||
* preprocessor, as everything that preprocessor uses must defined or imported
|
||||
* in the context module.
|
||||
* See the [setter documentation]{@link setPreprocessor} for more information.
|
||||
*
|
||||
* If never set, Engine will use the current project's `Main` module as the context.
|
||||
*
|
||||
* @param module
|
||||
* @returns {Array} The list of preprocessor arguments or `null` if none were set.
|
||||
*/
|
||||
setPreprocessorModule(module) {
|
||||
if (module !== this.__preprocessorModule__) {
|
||||
this.__preprocessorModule__ = module
|
||||
getPreprocessorArguments() {
|
||||
return this.__preprocessorArguments__
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the arguments of the preprocessor function.
|
||||
*
|
||||
* Arguments should be strings representing valid Enso expressions that can
|
||||
* be evaluated in the preprocessor module. See the
|
||||
* [setter documentation]{@link setPreprocessor} for more information.
|
||||
*
|
||||
* @param arguments the arguments passed to the preprocessor function.
|
||||
*/
|
||||
setPreprocessorArguments(...args) {
|
||||
if (args !== this.__preprocessorArguments__) {
|
||||
this.__preprocessorArguments__ = args
|
||||
this.__emitPreprocessorChange__()
|
||||
} else {
|
||||
console.error('skipping, as', module, ' === ', this.__preprocessorModule__)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set both preprocessor's code and context module.
|
||||
* Set the preprocessor.
|
||||
*
|
||||
* This is like calling both {@link setPreprocessorModule} and
|
||||
* {@link setPreprocessorCode}, however may be more efficient, as it will emit
|
||||
* only one update request.
|
||||
* Sets the preprocessor method by providing the method pointer consisting of a
|
||||
* method name and a module name defining the preprocessor method.
|
||||
*
|
||||
* During the visualization construction phase no partial updates are emitted,
|
||||
* so using this method gives no additional benefit.
|
||||
* An example of setting a preprocessor without arguments.
|
||||
* @example
|
||||
* this.setPreprocessor('Standard.Visualization.Preprocessor', 'default_preprocessor')
|
||||
*
|
||||
* @param code preprocessor code to be set.
|
||||
* @param module context module for the preprocessor execution.
|
||||
* The arguments should be strings representing Enso expressions. These
|
||||
* expressions will be evaluated in the context of preprocessor module and
|
||||
* passed to the visualization function in the same order.
|
||||
*
|
||||
* For example, given the `prepare_visualization` Enso method defined as
|
||||
* @example
|
||||
* prepare_visualization data param_1=10 param_2=True param_3=Nothing = ...
|
||||
*
|
||||
* the `setPreprocessor` method call can look like
|
||||
* @example
|
||||
* this.setPreprocessor('Foo.Bar.Baz', 'prepare_visualization', '42', 'False')
|
||||
*
|
||||
* First argument of the visualization function is always the node value
|
||||
* (`data`), followed by the configuration parameters. In this example
|
||||
* `setPreprocessor` call passes the number `42` as `param_1` and boolean
|
||||
* `False` as `param_2`. `param_3` has been left unchanged with the default
|
||||
* value `Nothing` since it was not specified in the example
|
||||
* `setPreprocessor` call.
|
||||
*
|
||||
* @param module The qualified module containing the preprocessor method.
|
||||
* @param method The preprocessor method name. The method must be invocable
|
||||
* with one argument and return JSON-compatible result.
|
||||
* @param arguments Positional arguments passed to the preprocessor function.
|
||||
* Arguments should be strings representing valid Enso
|
||||
* expressions that can be evaluated in the preprocessor module.
|
||||
*/
|
||||
setPreprocessor(code, module) {
|
||||
if (code !== this.__preprocessorCode__ || code !== this.__preprocessorModule__) {
|
||||
this.__preprocessorCode__ = code
|
||||
setPreprocessor(module, method, ...args) {
|
||||
if (
|
||||
module !== this.__preprocessorModule__ ||
|
||||
method !== this.__preprocessorMethod__ ||
|
||||
args !== this.__preprocessorArguments__
|
||||
) {
|
||||
this.__preprocessorModule__ = module
|
||||
this.__preprocessorMethod__ = method
|
||||
this.__preprocessorArguments__ = args
|
||||
this.__emitPreprocessorChange__()
|
||||
}
|
||||
}
|
||||
|
@ -16,9 +16,12 @@ use ensogl::display::Scene;
|
||||
// === Constants ===
|
||||
// =================
|
||||
|
||||
/// An invocable language expression that serialize given input into JSON.
|
||||
pub const DEFAULT_VISUALIZATION_EXPRESSION: &str = "x -> x.to_default_visualization_data";
|
||||
|
||||
/// A module containing the default visualization function.
|
||||
pub const DEFAULT_VISUALIZATION_MODULE: &str = "Standard.Visualization.Preprocessor";
|
||||
/// A name of the default visualization function.
|
||||
pub const DEFAULT_VISUALIZATION_FUNCTION: &str = "default_preprocessor";
|
||||
/// A list of arguments passed to the default visualization function.
|
||||
const DEFAULT_VISUALIZATION_ARGUMENTS: Vec<enso::Code> = vec![];
|
||||
|
||||
|
||||
// ====================
|
||||
@ -57,44 +60,55 @@ impl ContextModule {
|
||||
/// Information on how the preprocessor should be set up for the visualization.
|
||||
#[derive(Clone, CloneRef, Debug, PartialEq, Eq)]
|
||||
pub struct PreprocessorConfiguration {
|
||||
/// The code of the preprocessor. Should be a lambda that transforms node value into whatever
|
||||
/// that visualizations expect.
|
||||
pub code: enso::Code,
|
||||
/// The module that provides context for `code` evaluation.
|
||||
pub module: ContextModule,
|
||||
/// The module containing the `method`.
|
||||
pub module: enso::Module,
|
||||
/// The method being invoked.
|
||||
pub method: enso::Method,
|
||||
/// A list of arguments to pass to the visualization expression.
|
||||
pub arguments: Rc<Vec<enso::Code>>,
|
||||
}
|
||||
|
||||
impl PreprocessorConfiguration {
|
||||
/// Like `new` but arguments are optional. If `None` is given, default value will be used.
|
||||
pub fn from_options(
|
||||
code: Option<impl Into<enso::Code>>,
|
||||
module: Option<impl Into<enso::Module>>,
|
||||
method: Option<impl Into<enso::Method>>,
|
||||
arguments: Option<Vec<impl Into<enso::Code>>>,
|
||||
) -> Self {
|
||||
let mut ret = Self::default();
|
||||
if let Some(code) = code {
|
||||
ret.code = code.into()
|
||||
}
|
||||
if let Some(module) = module {
|
||||
ret.module = ContextModule::Specific(module.into())
|
||||
ret.module = module.into();
|
||||
}
|
||||
if let Some(method) = method {
|
||||
ret.method = method.into();
|
||||
}
|
||||
if let Some(arguments) = arguments {
|
||||
ret.arguments = Rc::new(arguments.into_iter().map_into().collect());
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
/// Create a configuration that runs the given code in the context of the given module.
|
||||
pub fn new(
|
||||
code: impl Into<enso::Code>,
|
||||
module: impl Into<enso::Module>,
|
||||
method: impl Into<enso::Method>,
|
||||
arguments: Vec<impl Into<enso::Code>>,
|
||||
) -> PreprocessorConfiguration {
|
||||
PreprocessorConfiguration {
|
||||
module: ContextModule::Specific(module.into()),
|
||||
code: code.into(),
|
||||
module: module.into(),
|
||||
method: method.into(),
|
||||
arguments: Rc::new(arguments.into_iter().map_into().collect()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PreprocessorConfiguration {
|
||||
fn default() -> Self {
|
||||
Self { code: DEFAULT_VISUALIZATION_EXPRESSION.into(), module: default() }
|
||||
Self {
|
||||
module: DEFAULT_VISUALIZATION_MODULE.into(),
|
||||
method: DEFAULT_VISUALIZATION_FUNCTION.into(),
|
||||
arguments: Rc::new(DEFAULT_VISUALIZATION_ARGUMENTS),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,9 @@ pub mod enso {
|
||||
/// The Enso type representation. Can be a complex type, like `String|Int`.
|
||||
Type,
|
||||
|
||||
/// The Enso method name, like `main` or `my_cool_method`.
|
||||
Method,
|
||||
|
||||
/// The Enso module represented as qualified path, like `Project.Data.Vector`.
|
||||
Module,
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Options intended to be common for all developers.
|
||||
|
||||
wasm-size-limit: 14.44 MiB
|
||||
wasm-size-limit: 14.45 MiB
|
||||
|
||||
required-versions:
|
||||
cargo-watch: ^8.1.1
|
||||
|
@ -2,6 +2,7 @@ from Standard.Base import all
|
||||
|
||||
import Standard.Visualization.File_Upload
|
||||
import Standard.Visualization.Id
|
||||
import Standard.Visualization.Preprocessor
|
||||
|
||||
from Standard.Visualization.File_Upload export file_uploading
|
||||
export Standard.Visualization.Id
|
||||
|
@ -1,4 +1,14 @@
|
||||
x ->
|
||||
from Standard.Base import all
|
||||
|
||||
## PRIVATE
|
||||
|
||||
Default visualization preprocessor.
|
||||
default_preprocessor x = x.to_default_visualization_data
|
||||
|
||||
## PRIVATE
|
||||
|
||||
Error visualization preprocessor.
|
||||
error_preprocessor x =
|
||||
ok = '{ message: ""}'
|
||||
result = x.map_error err->
|
||||
message = err.to_display_text
|
@ -19,7 +19,7 @@ import Standard.Visualization.Helpers
|
||||
|
||||
In case of Database backed data, it materializes a fragment of the data.
|
||||
prepare_visualization : Any -> Integer -> Json
|
||||
prepare_visualization x max_rows = Helpers.recover_errors <| case x of
|
||||
prepare_visualization x max_rows=1000 = Helpers.recover_errors <| case x of
|
||||
Dataframe_Table.Table_Data _ ->
|
||||
dataframe = x.take (First max_rows)
|
||||
all_rows_count = x.row_count
|
||||
|
@ -391,6 +391,9 @@ interface VisualisationConfiguration {
|
||||
|
||||
/** An expression that creates a visualisation. */
|
||||
expression: String | MethodPointer;
|
||||
|
||||
/** A list of arguments to pass to the visualization expression. */
|
||||
positionalArgumentsExpressions?: string[];
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -59,19 +59,27 @@ object VisualisationConfiguration {
|
||||
*
|
||||
* @param contextId an execution context of the visualisation
|
||||
* @param expression a visualisation expression
|
||||
* @param positionalArgumentsExpressions the list of arguments that will
|
||||
* be passed to the visualisation expression
|
||||
* @return an instance of [[VisualisationConfiguration]]
|
||||
*/
|
||||
def apply(
|
||||
contextId: UUID,
|
||||
expression: MethodPointer
|
||||
expression: MethodPointer,
|
||||
positionalArgumentsExpressions: Vector[String]
|
||||
): VisualisationConfiguration =
|
||||
new VisualisationConfiguration(
|
||||
contextId,
|
||||
VisualisationExpression.ModuleMethod(expression)
|
||||
VisualisationExpression.ModuleMethod(
|
||||
expression,
|
||||
positionalArgumentsExpressions
|
||||
)
|
||||
)
|
||||
|
||||
private object CodecField {
|
||||
|
||||
val Arguments = "positionalArgumentsExpressions"
|
||||
|
||||
val Expression = "expression"
|
||||
|
||||
val ExecutionContextId = "executionContextId"
|
||||
@ -91,7 +99,14 @@ object VisualisationConfiguration {
|
||||
expression <- cursor
|
||||
.downField(CodecField.Expression)
|
||||
.as[MethodPointer]
|
||||
} yield VisualisationConfiguration(contextId, expression)
|
||||
arguments <- cursor
|
||||
.downField(CodecField.Arguments)
|
||||
.as[Option[Vector[String]]]
|
||||
} yield VisualisationConfiguration(
|
||||
contextId,
|
||||
expression,
|
||||
arguments.getOrElse(Vector())
|
||||
)
|
||||
|
||||
case Right(expression) =>
|
||||
for {
|
||||
@ -144,20 +159,29 @@ object VisualisationExpression {
|
||||
/** Visualization expression represented as a module method.
|
||||
*
|
||||
* @param methodPointer a pointer to a method definition
|
||||
* @param positionalArgumentsExpressions the list of arguments that will
|
||||
* be passed to the method
|
||||
*/
|
||||
case class ModuleMethod(methodPointer: MethodPointer)
|
||||
extends VisualisationExpression {
|
||||
case class ModuleMethod(
|
||||
methodPointer: MethodPointer,
|
||||
positionalArgumentsExpressions: Vector[String]
|
||||
) extends VisualisationExpression {
|
||||
|
||||
/** @inheritdoc */
|
||||
override val module: String = methodPointer.module
|
||||
|
||||
/** @inheritdoc */
|
||||
override def toApi: Api.VisualisationExpression =
|
||||
Api.VisualisationExpression.ModuleMethod(methodPointer.toApi)
|
||||
Api.VisualisationExpression.ModuleMethod(
|
||||
methodPointer.toApi,
|
||||
positionalArgumentsExpressions
|
||||
)
|
||||
|
||||
/** @inheritdoc */
|
||||
override def toLogString(shouldMask: Boolean): String =
|
||||
s"ModuleMethod(methodPointer=$methodPointer)"
|
||||
s"ModuleMethod(methodPointer=$methodPointer,positionalArgumentsExpressions=" +
|
||||
(if (shouldMask) STUB else positionalArgumentsExpressions) +
|
||||
s")"
|
||||
}
|
||||
|
||||
private object CodecField {
|
||||
|
@ -130,7 +130,7 @@ object ExecutionContextJsonMessages {
|
||||
}
|
||||
}
|
||||
"""
|
||||
case VisualisationExpression.ModuleMethod(methodPointer) =>
|
||||
case VisualisationExpression.ModuleMethod(methodPointer, Vector()) =>
|
||||
json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"method": "executionContext/executeExpression",
|
||||
@ -149,6 +149,26 @@ object ExecutionContextJsonMessages {
|
||||
}
|
||||
}
|
||||
"""
|
||||
case VisualisationExpression.ModuleMethod(methodPointer, arguments) =>
|
||||
json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"method": "executionContext/executeExpression",
|
||||
"id": $reqId,
|
||||
"params": {
|
||||
"visualisationId": $visualisationId,
|
||||
"expressionId": $expressionId,
|
||||
"visualisationConfig": {
|
||||
"executionContextId": ${configuration.executionContextId},
|
||||
"expression": {
|
||||
"module": ${methodPointer.module},
|
||||
"definedOnType": ${methodPointer.definedOnType},
|
||||
"name": ${methodPointer.name}
|
||||
},
|
||||
"positionalArgumentsExpressions": $arguments
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
}
|
||||
|
||||
def executionContextAttachVisualisationRequest(
|
||||
@ -174,7 +194,7 @@ object ExecutionContextJsonMessages {
|
||||
}
|
||||
}
|
||||
"""
|
||||
case VisualisationExpression.ModuleMethod(methodPointer) =>
|
||||
case VisualisationExpression.ModuleMethod(methodPointer, Vector()) =>
|
||||
json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"method": "executionContext/attachVisualisation",
|
||||
@ -193,6 +213,26 @@ object ExecutionContextJsonMessages {
|
||||
}
|
||||
}
|
||||
"""
|
||||
case VisualisationExpression.ModuleMethod(methodPointer, arguments) =>
|
||||
json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"method": "executionContext/attachVisualisation",
|
||||
"id": $reqId,
|
||||
"params": {
|
||||
"visualisationId": $visualisationId,
|
||||
"expressionId": $expressionId,
|
||||
"visualisationConfig": {
|
||||
"executionContextId": ${configuration.executionContextId},
|
||||
"expression": {
|
||||
"module": ${methodPointer.module},
|
||||
"definedOnType": ${methodPointer.definedOnType},
|
||||
"name": ${methodPointer.name}
|
||||
},
|
||||
"positionalArgumentsExpressions": $arguments
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
@ -280,7 +320,7 @@ object ExecutionContextJsonMessages {
|
||||
}
|
||||
}
|
||||
"""
|
||||
case VisualisationExpression.ModuleMethod(methodPointer) =>
|
||||
case VisualisationExpression.ModuleMethod(methodPointer, Vector()) =>
|
||||
json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"method": "executionContext/modifyVisualisation",
|
||||
@ -298,6 +338,25 @@ object ExecutionContextJsonMessages {
|
||||
}
|
||||
}
|
||||
"""
|
||||
case VisualisationExpression.ModuleMethod(methodPointer, arguments) =>
|
||||
json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"method": "executionContext/modifyVisualisation",
|
||||
"id": $reqId,
|
||||
"params": {
|
||||
"visualisationId": $visualisationId,
|
||||
"visualisationConfig": {
|
||||
"executionContextId": ${configuration.executionContextId},
|
||||
"expression": {
|
||||
"module": ${methodPointer.module},
|
||||
"definedOnType": ${methodPointer.definedOnType},
|
||||
"name": ${methodPointer.name}
|
||||
},
|
||||
"positionalArgumentsExpressions": $arguments
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,7 +55,7 @@ class VisualisationOperationsTest extends BaseServerTest {
|
||||
client.expectJson(ExecutionContextJsonMessages.ok(1))
|
||||
}
|
||||
|
||||
"allow attaching method pointer as a visualisation expression" in {
|
||||
"allow attaching method pointer without arguments as a visualisation expression" in {
|
||||
val visualisationId = UUID.randomUUID()
|
||||
val expressionId = UUID.randomUUID()
|
||||
|
||||
@ -70,7 +70,62 @@ class VisualisationOperationsTest extends BaseServerTest {
|
||||
visualisationModule,
|
||||
visualisationModule,
|
||||
visualisationMethod
|
||||
)
|
||||
),
|
||||
Vector()
|
||||
)
|
||||
|
||||
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.toApi
|
||||
config.visualisationModule shouldBe visualisationConfig.visualisationModule
|
||||
config.executionContextId shouldBe visualisationConfig.executionContextId
|
||||
requestId
|
||||
|
||||
case msg =>
|
||||
fail(s"Unexpected message: $msg")
|
||||
}
|
||||
|
||||
runtimeConnectorProbe.lastSender ! Api.Response(
|
||||
requestId,
|
||||
Api.VisualisationAttached()
|
||||
)
|
||||
client.expectJson(ExecutionContextJsonMessages.ok(1))
|
||||
}
|
||||
|
||||
"allow attaching method pointer with arguments as a visualisation expression" in {
|
||||
val visualisationId = UUID.randomUUID()
|
||||
val expressionId = UUID.randomUUID()
|
||||
|
||||
val client = getInitialisedWsClient()
|
||||
val contextId = createExecutionContext(client)
|
||||
val visualisationModule = "Foo.Bar"
|
||||
val visualisationMethod = "baz"
|
||||
val visualisationConfig =
|
||||
VisualisationConfiguration(
|
||||
contextId,
|
||||
MethodPointer(
|
||||
visualisationModule,
|
||||
visualisationModule,
|
||||
visualisationMethod
|
||||
),
|
||||
Vector("1", "2", "3")
|
||||
)
|
||||
|
||||
client.send(
|
||||
|
@ -515,16 +515,23 @@ object Runtime {
|
||||
/** Visualization expression represented as a module method.
|
||||
*
|
||||
* @param methodPointer a pointer to a method definition
|
||||
* @param positionalArgumentsExpressions the list of arguments that will
|
||||
* be passed to the method
|
||||
*/
|
||||
case class ModuleMethod(methodPointer: MethodPointer)
|
||||
extends VisualisationExpression {
|
||||
case class ModuleMethod(
|
||||
methodPointer: MethodPointer,
|
||||
positionalArgumentsExpressions: Vector[String]
|
||||
) extends VisualisationExpression {
|
||||
|
||||
/** @inheritdoc */
|
||||
override val module: String = methodPointer.module
|
||||
|
||||
/** @inheritdoc */
|
||||
override def toLogString(shouldMask: Boolean): String =
|
||||
s"ModuleMethod(methodPointer=$methodPointer)"
|
||||
s"ModuleMethod(methodPointer=$methodPointer," +
|
||||
s"positionalArgumentsExpressions=" +
|
||||
(if (shouldMask) STUB else positionalArgumentsExpressions) +
|
||||
s")"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,8 +20,6 @@ import java.io.{ByteArrayOutputStream, File}
|
||||
import java.nio.file.{Files, Path, Paths}
|
||||
import java.util.UUID
|
||||
|
||||
import scala.io.Source
|
||||
|
||||
@scala.annotation.nowarn("msg=multiarg infix syntax")
|
||||
class RuntimeVisualizationsTest
|
||||
extends AnyFlatSpec
|
||||
@ -253,22 +251,26 @@ class RuntimeVisualizationsTest
|
||||
object AnnotatedVisualisation {
|
||||
|
||||
val metadata = new Metadata
|
||||
val idIncY = metadata.addItem(50, 5)
|
||||
val idIncRes = metadata.addItem(66, 8)
|
||||
val idIncMethod = metadata.addItem(25, 58)
|
||||
val idIncY = metadata.addItem(111, 7)
|
||||
val idIncRes = metadata.addItem(129, 8)
|
||||
val idIncMethod = metadata.addItem(102, 43)
|
||||
|
||||
println(s"idIncY=$idIncY")
|
||||
println(s"idIncRes=$idIncRes")
|
||||
println(s"idIncMethod=$idIncMethod")
|
||||
|
||||
val code =
|
||||
metadata.appendToCode(
|
||||
"""import Standard.Base.IO
|
||||
|
|
||||
|incAndEncode x =
|
||||
| y = x + 1
|
||||
| res = encode y
|
||||
| res
|
||||
|
|
||||
|encode x =
|
||||
| IO.println "encoding..."
|
||||
| x.to_text
|
||||
|
|
||||
|incAndEncode x a=1 b=1 =
|
||||
| y = a*x + b
|
||||
| res = encode y
|
||||
| res
|
||||
|""".stripMargin.linesIterator.mkString("\n")
|
||||
)
|
||||
|
||||
@ -1921,10 +1923,11 @@ class RuntimeVisualizationsTest
|
||||
val moduleName = "Enso_Test.Test.Main"
|
||||
val metadata = new Metadata
|
||||
|
||||
val idMain = metadata.addItem(86, 28)
|
||||
val idMain = metadata.addItem(116, 28)
|
||||
|
||||
val code =
|
||||
"""import Standard.Base.Data.List
|
||||
|import Standard.Visualization
|
||||
|from Standard.Base.Error.Common import all
|
||||
|
|
||||
|main =
|
||||
@ -1934,9 +1937,8 @@ class RuntimeVisualizationsTest
|
||||
val mainFile = context.writeMain(contents)
|
||||
|
||||
// NOTE: below values need to be kept in sync with what is used internally by Rust IDE code
|
||||
val visualisationModule = "Standard.Base.Main"
|
||||
val visualisationCode =
|
||||
Source.fromResource("error_preprocessor.enso").mkString
|
||||
val visualisationModule = "Standard.Visualization.Preprocessor"
|
||||
val visualisationFunction = "error_preprocessor"
|
||||
|
||||
// create context
|
||||
context.send(Api.Request(requestId, Api.CreateContextRequest(contextId)))
|
||||
@ -1959,7 +1961,7 @@ class RuntimeVisualizationsTest
|
||||
context.send(
|
||||
Api.Request(requestId, Api.PushContextRequest(contextId, item1))
|
||||
)
|
||||
val pushContextResponses = context.receiveN(4)
|
||||
val pushContextResponses = context.receiveNIgnoreStdLib(3)
|
||||
pushContextResponses should contain allOf (
|
||||
Api.Response(requestId, Api.PushContextResponse(contextId)),
|
||||
TestMessages.error(
|
||||
@ -1969,11 +1971,6 @@ class RuntimeVisualizationsTest
|
||||
),
|
||||
context.executionComplete(contextId)
|
||||
)
|
||||
val loadedLibraries = pushContextResponses.collect {
|
||||
case Api.Response(None, Api.LibraryLoaded(namespace, name, _, _)) =>
|
||||
(namespace, name)
|
||||
}
|
||||
loadedLibraries should contain(("Standard", "Base"))
|
||||
|
||||
// attach visualisation
|
||||
context.send(
|
||||
@ -1984,9 +1981,13 @@ class RuntimeVisualizationsTest
|
||||
idMain,
|
||||
Api.VisualisationConfiguration(
|
||||
contextId,
|
||||
Api.VisualisationExpression.Text(
|
||||
visualisationModule,
|
||||
visualisationCode
|
||||
Api.VisualisationExpression.ModuleMethod(
|
||||
Api.MethodPointer(
|
||||
visualisationModule,
|
||||
visualisationModule,
|
||||
visualisationFunction
|
||||
),
|
||||
Vector()
|
||||
)
|
||||
)
|
||||
)
|
||||
@ -2015,7 +2016,7 @@ class RuntimeVisualizationsTest
|
||||
stringified shouldEqual """{ "kind": "Dataflow", "message": "The List is empty."}"""
|
||||
}
|
||||
|
||||
it should "attach method pointer visualisation" in {
|
||||
it should "attach method pointer visualisation without arguments" in {
|
||||
val idMainRes = context.Main.metadata.addItem(99, 1)
|
||||
val contents = context.Main.code
|
||||
val mainFile = context.writeMain(context.Main.code)
|
||||
@ -2079,7 +2080,8 @@ class RuntimeVisualizationsTest
|
||||
"Enso_Test.Test.Visualisation",
|
||||
"Enso_Test.Test.Visualisation",
|
||||
"incAndEncode"
|
||||
)
|
||||
),
|
||||
Vector()
|
||||
)
|
||||
)
|
||||
)
|
||||
@ -2133,6 +2135,174 @@ class RuntimeVisualizationsTest
|
||||
data2.sameElements("51".getBytes) shouldBe true
|
||||
}
|
||||
|
||||
it should "attach method pointer visualisation with arguments" in {
|
||||
val idMainRes = context.Main.metadata.addItem(99, 1)
|
||||
val contents = context.Main.code
|
||||
val mainFile = context.writeMain(context.Main.code)
|
||||
val moduleName = "Enso_Test.Test.Main"
|
||||
val visualisationFile =
|
||||
context.writeInSrcDir(
|
||||
"Visualisation",
|
||||
context.AnnotatedVisualisation.code
|
||||
)
|
||||
|
||||
context.send(
|
||||
Api.Request(
|
||||
Api.OpenFileNotification(
|
||||
visualisationFile,
|
||||
context.AnnotatedVisualisation.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))
|
||||
)
|
||||
|
||||
// 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.receiveNIgnoreStdLib(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, idMainRes, ConstantsGen.INTEGER),
|
||||
context.executionComplete(contextId)
|
||||
)
|
||||
context.consumeOut shouldEqual List()
|
||||
|
||||
// attach visualisation
|
||||
context.send(
|
||||
Api.Request(
|
||||
requestId,
|
||||
Api.AttachVisualisation(
|
||||
visualisationId,
|
||||
idMainRes,
|
||||
Api.VisualisationConfiguration(
|
||||
contextId,
|
||||
Api.VisualisationExpression.ModuleMethod(
|
||||
Api.MethodPointer(
|
||||
"Enso_Test.Test.Visualisation",
|
||||
"Enso_Test.Test.Visualisation",
|
||||
"incAndEncode"
|
||||
),
|
||||
Vector("2", "3")
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
val attachVisualisationResponses = context.receiveN(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`,
|
||||
`idMainRes`
|
||||
),
|
||||
data
|
||||
)
|
||||
) =>
|
||||
data
|
||||
}
|
||||
data.sameElements("103".getBytes) shouldBe true
|
||||
context.consumeOut shouldEqual List("encoding...")
|
||||
|
||||
// recompute
|
||||
context.send(
|
||||
Api.Request(requestId, Api.RecomputeContextRequest(contextId, None))
|
||||
)
|
||||
|
||||
val recomputeResponses = context.receiveN(3)
|
||||
recomputeResponses should contain allOf (
|
||||
Api.Response(requestId, Api.RecomputeContextResponse(contextId)),
|
||||
context.executionComplete(contextId)
|
||||
)
|
||||
val Some(data2) = recomputeResponses.collectFirst {
|
||||
case Api.Response(
|
||||
None,
|
||||
Api.VisualisationUpdate(
|
||||
Api.VisualisationContext(
|
||||
`visualisationId`,
|
||||
`contextId`,
|
||||
`idMainRes`
|
||||
),
|
||||
data
|
||||
)
|
||||
) =>
|
||||
data
|
||||
}
|
||||
data2.sameElements("103".getBytes) shouldBe true
|
||||
context.consumeOut shouldEqual List()
|
||||
|
||||
// modify visualisation
|
||||
context.send(
|
||||
Api.Request(
|
||||
requestId,
|
||||
Api.ModifyVisualisation(
|
||||
visualisationId,
|
||||
Api.VisualisationConfiguration(
|
||||
contextId,
|
||||
Api.VisualisationExpression.ModuleMethod(
|
||||
Api.MethodPointer(
|
||||
"Enso_Test.Test.Visualisation",
|
||||
"Enso_Test.Test.Visualisation",
|
||||
"incAndEncode"
|
||||
),
|
||||
Vector("2", "4")
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
val modifyVisualisationResponses = context.receiveN(2)
|
||||
modifyVisualisationResponses should contain(
|
||||
Api.Response(requestId, Api.VisualisationModified())
|
||||
)
|
||||
val Some(data3) =
|
||||
modifyVisualisationResponses.collectFirst {
|
||||
case Api.Response(
|
||||
None,
|
||||
Api.VisualisationUpdate(
|
||||
Api.VisualisationContext(
|
||||
`visualisationId`,
|
||||
`contextId`,
|
||||
`idMainRes`
|
||||
),
|
||||
data
|
||||
)
|
||||
) =>
|
||||
data
|
||||
}
|
||||
data3.sameElements("104".getBytes) shouldBe true
|
||||
context.consumeOut shouldEqual List("encoding...")
|
||||
}
|
||||
|
||||
it should "cache intermediate visualization expressions" in {
|
||||
val idMainRes = context.Main.metadata.addItem(99, 1)
|
||||
val contents = context.Main.code
|
||||
@ -2202,7 +2372,8 @@ class RuntimeVisualizationsTest
|
||||
"Enso_Test.Test.Visualisation",
|
||||
"Enso_Test.Test.Visualisation",
|
||||
"incAndEncode"
|
||||
)
|
||||
),
|
||||
Vector()
|
||||
)
|
||||
)
|
||||
)
|
||||
@ -2264,7 +2435,7 @@ class RuntimeVisualizationsTest
|
||||
visualisationFile,
|
||||
Seq(
|
||||
TextEdit(
|
||||
model.Range(model.Position(3, 12), model.Position(3, 13)),
|
||||
model.Range(model.Position(6, 21), model.Position(6, 22)),
|
||||
"2"
|
||||
)
|
||||
),
|
||||
|
@ -168,7 +168,7 @@ public class ExecutionService {
|
||||
interopLibrary.execute(call);
|
||||
} finally {
|
||||
context.getThreadManager().leave(p);
|
||||
listener.ifPresent(binding -> binding.dispose());
|
||||
listener.ifPresent(EventBinding::dispose);
|
||||
}
|
||||
}
|
||||
|
||||
@ -257,28 +257,27 @@ public class ExecutionService {
|
||||
/**
|
||||
* Calls a function with the given argument and attaching an execution instrument.
|
||||
*
|
||||
* @param cache the runtime cache
|
||||
* @param module the module providing scope for the function
|
||||
* @param function the function object
|
||||
* @param argument the argument applied to the function
|
||||
* @param cache the runtime cache
|
||||
* @param arguments the sequence of arguments applied to the function
|
||||
* @return the result of calling the function
|
||||
*/
|
||||
public Object callFunctionWithInstrument(
|
||||
Module module, Object function, Object argument, RuntimeCache cache)
|
||||
RuntimeCache cache, Module module, Object function, Object... arguments)
|
||||
throws UnsupportedTypeException, ArityException, UnsupportedMessageException {
|
||||
UUID nextExecutionItem = null;
|
||||
CallTarget entryCallTarget =
|
||||
(function instanceof Function) ? ((Function) function).getCallTarget() : null;
|
||||
MethodCallsCache methodCallsCache = new MethodCallsCache();
|
||||
UpdatesSynchronizationState syncState = new UpdatesSynchronizationState();
|
||||
Consumer<IdExecutionService.ExpressionCall> funCallCallback =
|
||||
(value) -> context.getLogger().finest("ON_CACHED_CALL " + value.getExpressionId());
|
||||
Consumer<IdExecutionService.ExpressionCall> funCallCallback = (value) -> {};
|
||||
Consumer<IdExecutionService.ExpressionValue> onComputedCallback =
|
||||
(value) -> context.getLogger().finest("ON_COMPUTED " + value.getExpressionId());
|
||||
(value) -> context.getLogger().finest("_ON_COMPUTED " + value.getExpressionId());
|
||||
Consumer<IdExecutionService.ExpressionValue> onCachedCallback =
|
||||
(value) -> context.getLogger().finest("ON_CACHED_VALUE " + value.getExpressionId());
|
||||
(value) -> context.getLogger().finest("_ON_CACHED_VALUE " + value.getExpressionId());
|
||||
Consumer<Exception> onExceptionalCallback =
|
||||
(value) -> context.getLogger().finest("ON_ERROR " + value);
|
||||
(value) -> context.getLogger().finest("_ON_ERROR " + value);
|
||||
|
||||
Optional<EventBinding<ExecutionEventListener>> listener =
|
||||
idExecutionInstrument.map(
|
||||
@ -296,7 +295,7 @@ public class ExecutionService {
|
||||
onExceptionalCallback));
|
||||
Object p = context.getThreadManager().enter();
|
||||
try {
|
||||
return interopLibrary.execute(function, argument);
|
||||
return interopLibrary.execute(function, arguments);
|
||||
} finally {
|
||||
context.getThreadManager().leave(p);
|
||||
listener.ifPresent(EventBinding::dispose);
|
||||
|
@ -21,5 +21,6 @@ case class Visualisation(
|
||||
module: Module,
|
||||
config: VisualisationConfiguration,
|
||||
visualisationExpressionId: Option[ExpressionId],
|
||||
callback: AnyRef
|
||||
callback: AnyRef,
|
||||
arguments: Vector[AnyRef]
|
||||
)
|
||||
|
@ -433,10 +433,10 @@ object ProgramExecutionSupport {
|
||||
s"Executing visualisation ${visualisation.expressionId}"
|
||||
)
|
||||
ctx.executionService.callFunctionWithInstrument(
|
||||
visualisation.cache,
|
||||
visualisation.module,
|
||||
visualisation.callback,
|
||||
expressionValue,
|
||||
visualisation.cache
|
||||
expressionValue +: visualisation.arguments: _*
|
||||
)
|
||||
}
|
||||
.flatMap {
|
||||
|
@ -62,14 +62,15 @@ class UpsertVisualisationJob(
|
||||
replyWithExpressionFailedError(message, result)
|
||||
None
|
||||
|
||||
case Right(EvaluationResult(module, callable)) =>
|
||||
case Right(EvaluationResult(module, callable, arguments)) =>
|
||||
val visualisation =
|
||||
UpsertVisualisationJob.updateVisualisation(
|
||||
visualisationId,
|
||||
expressionId,
|
||||
module,
|
||||
config,
|
||||
callable
|
||||
callable,
|
||||
arguments
|
||||
)
|
||||
ctx.endpoint.sendToClient(Api.Response(requestId, response))
|
||||
val stack = ctx.contextManager.getStack(config.executionContextId)
|
||||
@ -146,11 +147,23 @@ object UpsertVisualisationJob {
|
||||
failure: Option[Api.ExecutionResult.Diagnostic]
|
||||
) extends EvaluationFailure
|
||||
|
||||
case class EvaluationResult(module: Module, callback: AnyRef)
|
||||
/** The result of evaluating the method pointer and positional argument
|
||||
* expressions.
|
||||
*
|
||||
* @param module the resolved module
|
||||
* @param callback the Enso function
|
||||
* @param arguments the list of arguments that will be passed to the callback
|
||||
*/
|
||||
case class EvaluationResult(
|
||||
module: Module,
|
||||
callback: AnyRef,
|
||||
arguments: Vector[AnyRef]
|
||||
)
|
||||
|
||||
/** Upsert the provided visualisation.
|
||||
*
|
||||
* @param visualisation the visualisation to update
|
||||
* @param ctx the runtime context
|
||||
*/
|
||||
def upsertVisualisation(
|
||||
visualisation: Visualisation
|
||||
@ -167,7 +180,8 @@ object UpsertVisualisationJob {
|
||||
expressionId,
|
||||
result.module,
|
||||
visualisationConfig,
|
||||
result.callback
|
||||
result.callback,
|
||||
result.arguments
|
||||
)
|
||||
val stack =
|
||||
ctx.contextManager.getStack(visualisationConfig.executionContextId)
|
||||
@ -196,6 +210,7 @@ object UpsertVisualisationJob {
|
||||
* @param module the module where to evaluate the expression
|
||||
* @param expression the visualisation expression
|
||||
* @param retryCount the number of attempted retries
|
||||
* @param ctx the runtime context
|
||||
* @return either the evaluation result or an evaluation failure
|
||||
*/
|
||||
private def evaluateModuleExpression(
|
||||
@ -207,19 +222,29 @@ object UpsertVisualisationJob {
|
||||
): Either[EvaluationFailure, EvaluationResult] =
|
||||
Either
|
||||
.catchNonFatal {
|
||||
val callback = expression match {
|
||||
val (callback, arguments) = expression match {
|
||||
case Api.VisualisationExpression.Text(_, expression) =>
|
||||
ctx.executionService.evaluateExpression(module, expression)
|
||||
val callback = ctx.executionService.evaluateExpression(
|
||||
module,
|
||||
expression
|
||||
)
|
||||
val arguments = Vector()
|
||||
(callback, arguments)
|
||||
case Api.VisualisationExpression.ModuleMethod(
|
||||
Api.MethodPointer(_, definedOnType, name)
|
||||
Api.MethodPointer(_, definedOnType, name),
|
||||
argumentExpressions
|
||||
) =>
|
||||
ctx.executionService.prepareFunctionCall(
|
||||
val callback = ctx.executionService.prepareFunctionCall(
|
||||
module,
|
||||
QualifiedName.fromString(definedOnType).item,
|
||||
name
|
||||
)
|
||||
val arguments = argumentExpressions.map(
|
||||
ctx.executionService.evaluateExpression(module, _)
|
||||
)
|
||||
(callback, arguments)
|
||||
}
|
||||
EvaluationResult(module, callback)
|
||||
EvaluationResult(module, callback, arguments)
|
||||
}
|
||||
.leftFlatMap {
|
||||
case _: ThreadInterruptedException
|
||||
@ -265,6 +290,7 @@ object UpsertVisualisationJob {
|
||||
/** Evaluate the visualisation expression.
|
||||
*
|
||||
* @param expression the visualisation expression to evaluate
|
||||
* @param ctx the runtime context
|
||||
* @return either the evaluation result or an evaluation error
|
||||
*/
|
||||
private def evaluateVisualisationExpression(
|
||||
@ -285,6 +311,7 @@ object UpsertVisualisationJob {
|
||||
* @param module the module containing the visualisation
|
||||
* @param visualisationConfig the visualisation configuration
|
||||
* @param callback the visualisation callback function
|
||||
* @param arguments the list of arugments that will be passed to the callback
|
||||
* @param ctx the runtime context
|
||||
* @return the re-evaluated visualisation
|
||||
*/
|
||||
@ -293,7 +320,8 @@ object UpsertVisualisationJob {
|
||||
expressionId: ExpressionId,
|
||||
module: Module,
|
||||
visualisationConfig: VisualisationConfiguration,
|
||||
callback: AnyRef
|
||||
callback: AnyRef,
|
||||
arguments: Vector[AnyRef]
|
||||
)(implicit ctx: RuntimeContext): Visualisation = {
|
||||
val visualisationExpressionId =
|
||||
findVisualisationExpressionId(module, visualisationConfig.expression)
|
||||
@ -304,7 +332,8 @@ object UpsertVisualisationJob {
|
||||
module,
|
||||
visualisationConfig,
|
||||
visualisationExpressionId,
|
||||
callback
|
||||
callback,
|
||||
arguments
|
||||
)
|
||||
setCacheWeights(visualisation)
|
||||
ctx.contextManager.upsertVisualisation(
|
||||
@ -325,14 +354,18 @@ object UpsertVisualisationJob {
|
||||
visualisationExpression: VisualisationExpression
|
||||
): Option[ExpressionId] =
|
||||
visualisationExpression match {
|
||||
case VisualisationExpression.ModuleMethod(methodPointer) =>
|
||||
case VisualisationExpression.ModuleMethod(methodPointer, _) =>
|
||||
module.getIr.bindings
|
||||
.collect { case method: IR.Module.Scope.Definition.Method =>
|
||||
val methodReference = method.methodReference
|
||||
val methodReferenceName = methodReference.methodName.name
|
||||
val methodReferenceTypeOpt = methodReference.typePointer.map(_.name)
|
||||
|
||||
method.getExternalId.filter { _ =>
|
||||
val externalIdOpt = method.body match {
|
||||
case fun: IR.Function => fun.body.getExternalId
|
||||
case _ => method.getExternalId
|
||||
}
|
||||
externalIdOpt.filter { _ =>
|
||||
methodReferenceName == methodPointer.name &&
|
||||
methodReferenceTypeOpt.isEmpty
|
||||
}
|
||||
|
@ -315,6 +315,12 @@ macro_rules! im_string_newtype_without_serde {
|
||||
Self::new(t)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&$name> for String {
|
||||
fn from(t:&$name) -> Self {
|
||||
t.content.to_string()
|
||||
}
|
||||
}
|
||||
)*};
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user