IDE uses new visualization API (#3661)

This commit is contained in:
Dmitry Bushev 2022-09-01 15:33:46 +03:00 committed by GitHub
parent 65140f48ca
commit de0a231417
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 934 additions and 444 deletions

View File

@ -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

View File

@ -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(),

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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();

View File

@ -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(

View File

@ -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();
});
}

View File

@ -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,

View File

@ -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));
}
}

View File

@ -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();

View File

@ -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) {

View File

@ -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() {

View File

@ -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) {

View File

@ -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.
*/

View File

@ -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)
}
/**

View File

@ -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) {

View File

@ -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) {

View File

@ -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.

View File

@ -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);
}
}

View File

@ -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__()
}
}

View File

@ -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),
}
}
}

View File

@ -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,
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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[];
}
```

View File

@ -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 {

View File

@ -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
}
}
}
"""
}
}

View File

@ -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(

View File

@ -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")"
}
}

View File

@ -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"
)
),

View File

@ -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);

View File

@ -21,5 +21,6 @@ case class Visualisation(
module: Module,
config: VisualisationConfiguration,
visualisationExpressionId: Option[ExpressionId],
callback: AnyRef
callback: AnyRef,
arguments: Vector[AnyRef]
)

View File

@ -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 {

View File

@ -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
}

View File

@ -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()
}
}
)*};
}