mirror of
https://github.com/enso-org/enso.git
synced 2024-12-22 05:21:31 +03:00
Routing type information to the graph editor integration layer (https://github.com/enso-org/ide/pull/564)
Original commit: db036ff878
This commit is contained in:
parent
0f018e61a0
commit
cb6a16d402
@ -127,7 +127,7 @@ commands.build.rust = async function(argv) {
|
||||
|
||||
console.log('Checking the resulting WASM size.')
|
||||
let stats = fss.statSync(paths.dist.wasm.mainOptGz)
|
||||
let limit = 3.31
|
||||
let limit = 3.32
|
||||
let size = Math.round(100 * stats.size / 1024 / 1024) / 100
|
||||
if (size > limit) {
|
||||
throw(`Output file size exceeds the limit (${size}MB > ${limit}MB).`)
|
||||
|
@ -23,7 +23,6 @@ use crate::types::Sha3_224;
|
||||
use json_rpc::api::Result;
|
||||
use json_rpc::Handler;
|
||||
use json_rpc::make_rpc_methods;
|
||||
use futures::Stream;
|
||||
use serde::Serialize;
|
||||
use serde::Deserialize;
|
||||
use std::future::Future;
|
||||
|
@ -303,6 +303,52 @@ fn test_acquire_capability() {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_computed_value_update() {
|
||||
use utils::test::traits::*;
|
||||
use json_rpc::Event;
|
||||
use crate::language_server::Notification;
|
||||
|
||||
let context_id = Uuid::parse_str("b36dea0b-b75a-40cf-aaad-5fcdf29a0573").unwrap();
|
||||
let id = Uuid::parse_str("d4b540c0-3ef5-487c-9453-df9d3efd351c").unwrap();
|
||||
let short_value = "UnresolvedSymbol<foo>";
|
||||
let typename = "Number";
|
||||
let notification = json!({
|
||||
"jsonrpc" : "2.0",
|
||||
"method" : "executionContext/expressionValuesComputed",
|
||||
"params" : {
|
||||
"contextId" : context_id,
|
||||
"updates" : [{
|
||||
"id" : id,
|
||||
"type" : typename,
|
||||
"shortValue" : short_value,
|
||||
"methodCall" : null
|
||||
}]
|
||||
}
|
||||
});
|
||||
|
||||
let mut fixture = setup_language_server();
|
||||
let mut stream = fixture.client.events();
|
||||
stream.expect_pending();
|
||||
|
||||
fixture.transport.mock_peer_json_message(notification);
|
||||
fixture.executor.run_until_stalled();
|
||||
|
||||
let notification = stream.expect_next();
|
||||
match notification {
|
||||
Event::Notification(Notification::ExpressionValuesComputed(expression_value_update)) => {
|
||||
assert_eq!(expression_value_update.context_id, context_id);
|
||||
let update = &expression_value_update.updates.first().unwrap();
|
||||
assert_eq!(update.id, id);
|
||||
assert_eq!(update.typename.as_ref().map(|ty| ty.as_str()), Some(typename));
|
||||
assert_eq!(update.short_value.as_ref().map(|ty| ty.as_str()), Some(short_value));
|
||||
assert!(update.method_call.is_none());
|
||||
}
|
||||
_ => panic!("Expected Notification::ExpressionValuesComputed"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_execution_context() {
|
||||
let root_id = uuid::Uuid::parse_str("00000000-0000-0000-0000-000000000000");
|
||||
|
@ -117,9 +117,35 @@ pub enum Notification {
|
||||
/// to address this: https://github.com/luna/enso/issues/707
|
||||
// TODO [mwu] Update as the issue is resolved on way or another.
|
||||
event:FileEvent,
|
||||
}
|
||||
},
|
||||
|
||||
/// Sent from the server to the client to inform about new information for certain expressions becoming available.
|
||||
#[serde(rename = "executionContext/expressionValuesComputed")]
|
||||
ExpressionValuesComputed(ExpressionValuesComputed),
|
||||
}
|
||||
|
||||
/// Sent from the server to the client to inform about new information for certain expressions becoming available.
|
||||
#[derive(Clone,Debug,PartialEq)]
|
||||
#[derive(Serialize,Deserialize)]
|
||||
#[allow(missing_docs)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ExpressionValuesComputed {
|
||||
pub context_id : ContextId,
|
||||
pub updates : Vec<ExpressionValueUpdate>,
|
||||
}
|
||||
|
||||
/// The updates from `executionContext/expressionValuesComputed`.
|
||||
#[derive(Clone,Debug,PartialEq)]
|
||||
#[derive(Serialize,Deserialize)]
|
||||
#[allow(missing_docs)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ExpressionValueUpdate {
|
||||
pub id : ExpressionId,
|
||||
#[serde(rename = "type")] // To avoid collision with the `type` keyword.
|
||||
pub typename : Option<String>,
|
||||
pub short_value : Option<String>,
|
||||
pub method_call : Option<MethodPointer>,
|
||||
}
|
||||
|
||||
|
||||
// =================
|
||||
|
@ -9,7 +9,6 @@ use crate::types::UTCDateTime;
|
||||
use json_rpc::api::Result;
|
||||
use json_rpc::Handler;
|
||||
use json_rpc::make_rpc_methods;
|
||||
use futures::Stream;
|
||||
use serde::Serialize;
|
||||
use serde::Deserialize;
|
||||
use std::future::Future;
|
||||
|
@ -43,6 +43,11 @@ macro_rules! make_rpc_methods {
|
||||
fn $method<'a>(&'a self $(,$param_name:&'a $param_ty)*)
|
||||
-> std::pin::Pin<Box<dyn Future<Output=Result<$result>>>>;
|
||||
)*
|
||||
|
||||
/// Asynchronous event stream with notification and errors.
|
||||
///
|
||||
/// On a repeated call, previous stream is closed.
|
||||
fn events(&self) -> futures::stream::LocalBoxStream<'static,Event>;
|
||||
}
|
||||
|
||||
|
||||
@ -65,13 +70,6 @@ macro_rules! make_rpc_methods {
|
||||
Self { handler }
|
||||
}
|
||||
|
||||
/// Asynchronous event stream with notification and errors.
|
||||
///
|
||||
/// On a repeated call, previous stream is closed.
|
||||
pub fn events(&self) -> impl Stream<Item = Event> {
|
||||
self.handler.borrow_mut().handler_event_stream()
|
||||
}
|
||||
|
||||
/// Returns a future that performs any background, asynchronous work needed
|
||||
/// for this Client to correctly work. Should be continually run while the
|
||||
/// `Client` is used. Will end once `Client` is dropped.
|
||||
@ -96,6 +94,10 @@ macro_rules! make_rpc_methods {
|
||||
let result_fut = self.handler.borrow().open_request_with_json(name,&input_json);
|
||||
Box::pin(result_fut)
|
||||
})*
|
||||
|
||||
fn events(&self) -> futures::stream::LocalBoxStream<'static,Event> {
|
||||
self.handler.borrow_mut().handler_event_stream().boxed_local()
|
||||
}
|
||||
}
|
||||
|
||||
$(
|
||||
@ -138,6 +140,7 @@ macro_rules! make_rpc_methods {
|
||||
require_all_calls : Cell<bool>,
|
||||
/// Expected calls handlers.
|
||||
pub expect : ExpectedCalls,
|
||||
events : RefCell<Option<futures::channel::mpsc::UnboundedReceiver<Event>>>,
|
||||
}
|
||||
|
||||
impl API for Client {
|
||||
@ -149,6 +152,14 @@ macro_rules! make_rpc_methods {
|
||||
let result = handler($($param_name),*);
|
||||
Box::pin(futures::future::ready(result))
|
||||
})*
|
||||
|
||||
fn events(&self) -> futures::stream::LocalBoxStream<'static,Event> {
|
||||
if let Some(receiver) = self.events.borrow_mut().take() {
|
||||
receiver.boxed_local()
|
||||
} else {
|
||||
futures::stream::empty().boxed_local()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Client {
|
||||
@ -157,6 +168,13 @@ macro_rules! make_rpc_methods {
|
||||
pub fn require_all_calls(&self) {
|
||||
self.require_all_calls.set(true);
|
||||
}
|
||||
|
||||
/// Set up a channel that will feed `events` stream with events.
|
||||
pub fn setup_events(&self) -> futures::channel::mpsc::UnboundedSender<Event> {
|
||||
let (sender,receiver) = futures::channel::mpsc::unbounded();
|
||||
*self.events.borrow_mut() = Some(receiver);
|
||||
sender
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Client {
|
||||
@ -202,6 +220,10 @@ macro_rules! make_rpc_methods {
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! expect_call {
|
||||
($mock:ident.$method:ident() => $result:expr) => {
|
||||
let result = $result;
|
||||
$mock.expect.$method(move || result)
|
||||
};
|
||||
($mock:ident.$method:ident($($param:ident),*) => $result:expr) => {
|
||||
expect_call!($mock.$method($($param=$param),*) => $result)
|
||||
};
|
||||
|
@ -751,41 +751,88 @@ mod tests {
|
||||
use wasm_bindgen_test::wasm_bindgen_test;
|
||||
use ast::test_utils::expect_shape;
|
||||
|
||||
struct GraphControllerFixture(TestWithLocalPoolExecutor);
|
||||
impl GraphControllerFixture {
|
||||
pub fn set_up() -> GraphControllerFixture {
|
||||
/// All the data needed to set up and run the graph controller in mock environment.
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct MockData {
|
||||
pub module_path : model::module::Path,
|
||||
pub graph_id : Id,
|
||||
pub project_name : String,
|
||||
pub code : String,
|
||||
}
|
||||
|
||||
impl MockData {
|
||||
pub fn new(code:impl Str) -> Self {
|
||||
MockData {
|
||||
module_path : model::module::Path::from_mock_module_name("Main"),
|
||||
graph_id : Id::new_plain_name("main"),
|
||||
project_name : "MockProject".to_string(),
|
||||
code : code.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a mock data with the main function being an inline definition.
|
||||
///
|
||||
/// The single node's expression is taken as the argument.
|
||||
pub fn new_inline(main_body:impl Str) -> Self {
|
||||
Self::new(format!("main = {}", main_body.as_ref()))
|
||||
}
|
||||
|
||||
/// Creates module and graph controllers.
|
||||
pub fn create_controllers(&self) -> (controller::Module,Handle) {
|
||||
let ls = language_server::Connection::new_mock_rc(default());
|
||||
self.create_controllers_with_ls(ls)
|
||||
}
|
||||
|
||||
/// Like `create_controllers`, but allows passing a custom LS client (e.g. with some
|
||||
/// expectations already set or shared with other controllers).
|
||||
pub fn create_controllers_with_ls
|
||||
(&self, ls:Rc<language_server::Connection>) -> (controller::Module,Handle) {
|
||||
let id = self.graph_id.clone();
|
||||
let path = self.module_path.clone();
|
||||
let code = &self.code;
|
||||
let id_map = default();
|
||||
let parser = Parser::new_or_panic();
|
||||
let module = controller::Module::new_mock(path,code,id_map,ls,parser).unwrap();
|
||||
let graph = module.graph_controller(id).unwrap();
|
||||
(module,graph)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug,Shrinkwrap)]
|
||||
#[shrinkwrap(mutable)]
|
||||
pub struct Fixture(pub TestWithLocalPoolExecutor);
|
||||
impl Fixture {
|
||||
pub fn set_up() -> Fixture {
|
||||
let nested = TestWithLocalPoolExecutor::set_up();
|
||||
Self(nested)
|
||||
}
|
||||
|
||||
pub fn run_graph_for_main<Test,Fut>
|
||||
(&mut self, code:impl Str, function_name:impl Str, test:Test)
|
||||
where Test : FnOnce(controller::Module,Handle) -> Fut + 'static,
|
||||
Fut : Future<Output=()> {
|
||||
let code = code.as_ref();
|
||||
let ls = language_server::Connection::new_mock_rc(default());
|
||||
let path = ModulePath::from_mock_module_name("Main");
|
||||
let parser = Parser::new_or_panic();
|
||||
let module = controller::Module::new_mock(path,code,default(),ls,parser).unwrap();
|
||||
let graph_id = Id::new_single_crumb(DefinitionName::new_plain(function_name.into()));
|
||||
let graph = module.graph_controller(graph_id).unwrap();
|
||||
self.0.run_task(async move {
|
||||
pub fn run<Fut>
|
||||
( &mut self
|
||||
, data : MockData
|
||||
, test : impl FnOnce(controller::Module,Handle) -> Fut + 'static
|
||||
) where Fut : Future<Output=()> {
|
||||
let (module,graph) = data.create_controllers();
|
||||
self.run_task(async move {
|
||||
test(module,graph).await
|
||||
})
|
||||
}
|
||||
|
||||
pub fn run_graph_for_main<Test,Fut>
|
||||
(&mut self, code:impl Str, test:Test)
|
||||
where Test : FnOnce(controller::Module,Handle) -> Fut + 'static,
|
||||
Fut : Future<Output=()> {
|
||||
let data = MockData::new(code);
|
||||
self.run(data,test)
|
||||
}
|
||||
|
||||
pub fn run_graph_for<Test,Fut>(&mut self, code:impl Str, graph_id:Id, test:Test)
|
||||
where Test : FnOnce(controller::Module,Handle) -> Fut + 'static,
|
||||
Fut : Future<Output=()> {
|
||||
let code = code.as_ref();
|
||||
let ls = language_server::Connection::new_mock_rc(default());
|
||||
let path = ModulePath::from_mock_module_name("Main");
|
||||
let parser = Parser::new_or_panic();
|
||||
let module = controller::Module::new_mock(path, code, default(), ls, parser).unwrap();
|
||||
let graph = module.graph_controller(graph_id).unwrap();
|
||||
self.0.run_task(async move {
|
||||
test(module,graph).await
|
||||
})
|
||||
|
||||
let mut data = MockData::new(code);
|
||||
data.graph_id = graph_id;
|
||||
self.run(data,test);
|
||||
}
|
||||
|
||||
pub fn run_inline_graph<Test,Fut>(&mut self, definition_body:impl Str, test:Test)
|
||||
@ -793,8 +840,7 @@ mod tests {
|
||||
Fut : Future<Output=()> {
|
||||
assert_eq!(definition_body.as_ref().contains('\n'), false);
|
||||
let code = format!("main = {}", definition_body.as_ref());
|
||||
let name = "main";
|
||||
self.run_graph_for_main(code, name, test)
|
||||
self.run_graph_for_main(code,test)
|
||||
}
|
||||
}
|
||||
|
||||
@ -820,8 +866,8 @@ mod tests {
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn graph_controller_notification_relay() {
|
||||
let mut test = GraphControllerFixture::set_up();
|
||||
test.run_graph_for_main("main = 2 + 2", "main", |module, graph| async move {
|
||||
let mut test = Fixture::set_up();
|
||||
test.run_graph_for_main("main = 2 + 2", |module, graph| async move {
|
||||
let text_change = TextChange::insert(Index::new(12), "2".into());
|
||||
module.apply_code_change(text_change).unwrap();
|
||||
|
||||
@ -833,7 +879,7 @@ mod tests {
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn graph_controller_inline_definition() {
|
||||
let mut test = GraphControllerFixture::set_up();
|
||||
let mut test = Fixture::set_up();
|
||||
const EXPRESSION: &str = "2+2";
|
||||
test.run_inline_graph(EXPRESSION, |_,graph| async move {
|
||||
let nodes = graph.nodes().unwrap();
|
||||
@ -847,12 +893,12 @@ mod tests {
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn graph_controller_block_definition() {
|
||||
let mut test = GraphControllerFixture::set_up();
|
||||
let mut test = Fixture::set_up();
|
||||
let program = r"
|
||||
main =
|
||||
foo = 2
|
||||
print foo";
|
||||
test.run_graph_for_main(program, "main", |_, graph| async move {
|
||||
test.run_graph_for_main(program, |_, graph| async move {
|
||||
let nodes = graph.nodes().unwrap();
|
||||
let (node1,node2) = nodes.expect_tuple();
|
||||
assert_eq!(node1.info.expression().repr(), "2");
|
||||
@ -862,9 +908,9 @@ main =
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn graph_controller_parse_expression() {
|
||||
let mut test = GraphControllerFixture::set_up();
|
||||
let mut test = Fixture::set_up();
|
||||
let program = r"main = 0";
|
||||
test.run_graph_for_main(program, "main", |_, graph| async move {
|
||||
test.run_graph_for_main(program, |_, graph| async move {
|
||||
let foo = graph.parse_node_expression("foo").unwrap();
|
||||
assert_eq!(expect_shape::<ast::Var>(&foo), &ast::Var {name:"foo".into()});
|
||||
|
||||
@ -878,9 +924,9 @@ main =
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn graph_controller_used_names_in_inline_def() {
|
||||
let mut test = GraphControllerFixture::set_up();
|
||||
let mut test = Fixture::set_up();
|
||||
const PROGRAM:&str = r"main = foo";
|
||||
test.run_graph_for_main(PROGRAM, "main", |_, graph| async move {
|
||||
test.run_graph_for_main(PROGRAM, |_, graph| async move {
|
||||
let expected_name = LocatedName::new_root(NormalizedName::new("foo"));
|
||||
let used_names = graph.used_names().unwrap();
|
||||
assert_eq!(used_names, vec![expected_name]);
|
||||
@ -889,7 +935,7 @@ main =
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn graph_controller_nested_definition() {
|
||||
let mut test = GraphControllerFixture::set_up();
|
||||
let mut test = Fixture::set_up();
|
||||
const PROGRAM:&str = r"main =
|
||||
foo a =
|
||||
bar b = 5
|
||||
@ -911,7 +957,7 @@ main =
|
||||
fn graph_controller_doubly_nested_definition() {
|
||||
// Tests editing nested definition that requires transforming inline expression into
|
||||
// into a new block.
|
||||
let mut test = GraphControllerFixture::set_up();
|
||||
let mut test = Fixture::set_up();
|
||||
// Not using multi-line raw string literals, as we don't want IntelliJ to automatically
|
||||
// strip the trailing whitespace in the lines.
|
||||
const PROGRAM:&str = "main =\n foo a =\n bar b = 5\n print foo";
|
||||
@ -927,12 +973,12 @@ main =
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn graph_controller_node_operations_node() {
|
||||
let mut test = GraphControllerFixture::set_up();
|
||||
let mut test = Fixture::set_up();
|
||||
const PROGRAM:&str = r"
|
||||
main =
|
||||
foo = 2
|
||||
print foo";
|
||||
test.run_graph_for_main(PROGRAM, "main", |module, graph| async move {
|
||||
test.run_graph_for_main(PROGRAM, |module, graph| async move {
|
||||
// === Initial nodes ===
|
||||
let nodes = graph.nodes().unwrap();
|
||||
let (node1,node2) = nodes.expect_tuple();
|
||||
@ -988,7 +1034,7 @@ main =
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn graph_controller_connections_listing() {
|
||||
let mut test = GraphControllerFixture::set_up();
|
||||
let mut test = Fixture::set_up();
|
||||
const PROGRAM:&str = r"
|
||||
main =
|
||||
x,y = get_pos
|
||||
@ -997,7 +1043,7 @@ main =
|
||||
print z
|
||||
foo
|
||||
print z";
|
||||
test.run_graph_for_main(PROGRAM, "main", |_, graph| async move {
|
||||
test.run_graph_for_main(PROGRAM, |_, graph| async move {
|
||||
let connections = graph.connections().unwrap();
|
||||
|
||||
let (node0,node1,node2,node3,node4) = graph.nodes().unwrap().expect_tuple();
|
||||
@ -1052,7 +1098,7 @@ main =
|
||||
|
||||
impl Case {
|
||||
fn run(&self) {
|
||||
let mut test = GraphControllerFixture::set_up();
|
||||
let mut test = Fixture::set_up();
|
||||
let main_prefix = format!("main = \n {} = foo\n ",self.src);
|
||||
let main = format!("{}{}",main_prefix,self.dst);
|
||||
let expected = format!("{}{}",main_prefix,self.expected);
|
||||
@ -1062,7 +1108,7 @@ main =
|
||||
let src_port = src_port.to_vec();
|
||||
let dst_port = dst_port.to_vec();
|
||||
|
||||
test.run_graph_for_main(main, "main", |_, graph| async move {
|
||||
test.run_graph_for_main(main, |_, graph| async move {
|
||||
let (node0,node1) = graph.nodes().unwrap().expect_tuple();
|
||||
let source = Endpoint::new(node0.info.id(),src_port.to_vec());
|
||||
let destination = Endpoint::new(node1.info.id(),dst_port.to_vec());
|
||||
@ -1086,7 +1132,7 @@ main =
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn graph_controller_create_connection_reordering() {
|
||||
let mut test = GraphControllerFixture::set_up();
|
||||
let mut test = Fixture::set_up();
|
||||
const PROGRAM:&str = r"main =
|
||||
sum = _ + _
|
||||
a = 1
|
||||
@ -1095,7 +1141,7 @@ main =
|
||||
a = 1
|
||||
b = 3
|
||||
sum = _ + b";
|
||||
test.run_graph_for_main(PROGRAM, "main", |_, graph| async move {
|
||||
test.run_graph_for_main(PROGRAM, |_, graph| async move {
|
||||
assert!(graph.connections().unwrap().connections.is_empty());
|
||||
let (node0,_node1,node2) = graph.nodes().unwrap().expect_tuple();
|
||||
let connection_to_add = Connection {
|
||||
@ -1118,7 +1164,7 @@ main =
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn graph_controller_create_connection_introducing_var() {
|
||||
let mut test = GraphControllerFixture::set_up();
|
||||
let mut test = Fixture::set_up();
|
||||
const PROGRAM:&str = r"main =
|
||||
calculate
|
||||
print _
|
||||
@ -1131,7 +1177,7 @@ main =
|
||||
print calculate5
|
||||
calculate1 = calculate2
|
||||
calculate3 calculate5 = calculate5 calculate4";
|
||||
test.run_graph_for_main(PROGRAM, "main", |_, graph| async move {
|
||||
test.run_graph_for_main(PROGRAM, |_, graph| async move {
|
||||
assert!(graph.connections().unwrap().connections.is_empty());
|
||||
let (node0,node1,_) = graph.nodes().unwrap().expect_tuple();
|
||||
let connection_to_add = Connection {
|
||||
@ -1187,13 +1233,13 @@ main =
|
||||
|
||||
impl Case {
|
||||
fn run(&self) {
|
||||
let mut test = GraphControllerFixture::set_up();
|
||||
let mut test = Fixture::set_up();
|
||||
const MAIN_PREFIX:&str = "main = \n in = foo\n ";
|
||||
let main = format!("{}{}",MAIN_PREFIX,self.dest_node_expr);
|
||||
let expected = format!("{}{}",MAIN_PREFIX,self.dest_node_expected);
|
||||
let this = self.clone();
|
||||
|
||||
test.run_graph_for_main(main,"main",|_,graph| async move {
|
||||
test.run_graph_for_main(main,|_,graph| async move {
|
||||
let connections = graph.connections().unwrap();
|
||||
let connection = connections.connections.first().unwrap();
|
||||
graph.disconnect(connection).unwrap();
|
||||
|
@ -5,11 +5,35 @@
|
||||
//! visualisations, retrieving types on ports, etc.
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::model::execution_context::ComputedValueInfoRegistry;
|
||||
use crate::model::execution_context::Visualization;
|
||||
use crate::model::execution_context::VisualizationId;
|
||||
use crate::model::execution_context::VisualizationUpdateData;
|
||||
use crate::model::synchronized::ExecutionContext;
|
||||
|
||||
|
||||
|
||||
// ====================
|
||||
// === Notification ===
|
||||
// ====================
|
||||
|
||||
/// Notification about change in the executed graph.
|
||||
///
|
||||
/// It may pertain either the state of the graph itself or the notifications from the execution.
|
||||
#[derive(Clone,Debug,PartialEq)]
|
||||
pub enum Notification {
|
||||
/// The notification passed from the graph controller.
|
||||
Graph(crate::controller::graph::Notification),
|
||||
/// The notification from the execution context about the computed value information
|
||||
/// being updated.
|
||||
ComputedValueInfo(crate::model::execution_context::ComputedValueExpressions),
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==============
|
||||
// === Handle ===
|
||||
// ==============
|
||||
/// Handle providing executed graph controller interface.
|
||||
#[derive(Clone,CloneRef,Debug)]
|
||||
pub struct Handle {
|
||||
@ -47,7 +71,21 @@ impl Handle {
|
||||
self.execution_ctx.detach_visualization(id).await
|
||||
}
|
||||
|
||||
// TODO [mwu] Here goes the type/short_rep value access API
|
||||
/// See `expression_info_registry` in `ExecutionContext`.
|
||||
pub fn computed_value_info_registry(&self) -> &ComputedValueInfoRegistry {
|
||||
self.execution_ctx.computed_value_info_registry()
|
||||
}
|
||||
|
||||
/// Subscribe to updates about changes in this executed graph.
|
||||
///
|
||||
/// The stream of notification contains both notifications from the graph and from the execution
|
||||
/// context.
|
||||
pub fn subscribe(&self) -> impl Stream<Item=Notification> {
|
||||
let registry = self.execution_ctx.computed_value_info_registry();
|
||||
let value_stream = registry.subscribe().map(Notification::ComputedValueInfo);
|
||||
let graph_stream = self.graph.subscribe().map(Notification::Graph);
|
||||
futures::stream::select(value_stream,graph_stream)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Handle {
|
||||
@ -57,3 +95,58 @@ impl Deref for Handle {
|
||||
&self.graph
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============
|
||||
// === Test ===
|
||||
// ============
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::executor::test_utils::TestWithLocalPoolExecutor;
|
||||
|
||||
use enso_protocol::language_server;
|
||||
use utils::test::traits::*;
|
||||
use wasm_bindgen_test::wasm_bindgen_test;
|
||||
use wasm_bindgen_test::wasm_bindgen_test_configure;
|
||||
|
||||
wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
/// Test that checks that value computed notification is properly relayed by the executed graph.
|
||||
#[wasm_bindgen_test]
|
||||
fn dispatching_value_computed_notification() {
|
||||
// Setup the controller.
|
||||
let mut fixture = TestWithLocalPoolExecutor::set_up();
|
||||
let mut ls = language_server::MockClient::default();
|
||||
let execution_data = model::synchronized::execution_context::tests::MockData::new();
|
||||
let execution = execution_data.context_provider(&mut ls);
|
||||
let graph_data = controller::graph::tests::MockData::new_inline("1 + 2");
|
||||
let connection = language_server::Connection::new_mock_rc(ls);
|
||||
let (_,graph) = graph_data.create_controllers_with_ls(connection.clone_ref());
|
||||
let execution = Rc::new(execution(connection.clone_ref()));
|
||||
let executed_graph = Handle::new(graph,execution.clone_ref());
|
||||
|
||||
// Generate notification.
|
||||
let notification = execution_data.mock_values_computed_update();
|
||||
let update = ¬ification.updates[0];
|
||||
|
||||
// Notification not yet send.
|
||||
let registry = executed_graph.computed_value_info_registry();
|
||||
let mut notifications = executed_graph.subscribe().boxed_local();
|
||||
notifications.expect_pending();
|
||||
assert!(registry.get(&update.id).is_none());
|
||||
|
||||
// Sending notification.
|
||||
execution.handle_expression_values_computed(notification.clone()).unwrap();
|
||||
fixture.run_until_stalled();
|
||||
|
||||
// Observing that notification was relayed.
|
||||
let observed_notification = notifications.expect_next();
|
||||
assert_eq!(observed_notification,Notification::ComputedValueInfo(vec![update.id]));
|
||||
assert_eq!(registry.get(&update.id).unwrap().typename, update.typename);
|
||||
notifications.expect_pending();
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ use crate::prelude::*;
|
||||
|
||||
use crate::controller::FilePath;
|
||||
use crate::controller::Visualization;
|
||||
use crate::model::execution_context::VisualizationId;
|
||||
use crate::model::execution_context::VisualizationUpdateData;
|
||||
use crate::model::module::QualifiedName as ModuleQualifiedName;
|
||||
use crate::model::module::Path as ModulePath;
|
||||
@ -37,8 +36,8 @@ type ExecutionContextId = model::execution_context::Id;
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone,Copy,Debug,Fail)]
|
||||
#[fail(display="No visualization with id {} was found in the registry.", _0)]
|
||||
pub struct NoSuchVisualization(VisualizationId);
|
||||
#[fail(display="No execution context with id {} was found in the registry.", _0)]
|
||||
pub struct NoSuchExecutionContext(ExecutionContextId);
|
||||
|
||||
|
||||
// === Aliases ===
|
||||
@ -54,17 +53,34 @@ type ExecutionContextWeakMap = WeakValueHashMap<ExecutionContextId,Weak<Executio
|
||||
pub struct ExecutionContextsRegistry(RefCell<ExecutionContextWeakMap>);
|
||||
|
||||
impl ExecutionContextsRegistry {
|
||||
/// Routes the visualization update into the appropriate execution context.
|
||||
/// Retrieve the execution context with given Id and calls the given function with it.
|
||||
///
|
||||
/// Handles the error of context not being present in the registry.
|
||||
pub fn with_context<R>
|
||||
(&self, id:ExecutionContextId, f:impl FnOnce(Rc<ExecutionContext>) -> FallibleResult<R>)
|
||||
-> FallibleResult<R> {
|
||||
let ctx = self.0.borrow_mut().get(&id);
|
||||
let ctx = ctx.ok_or_else(|| NoSuchExecutionContext(id))?;
|
||||
f(ctx)
|
||||
}
|
||||
|
||||
/// Route the visualization update into the appropriate execution context.
|
||||
pub fn dispatch_visualization_update
|
||||
(&self
|
||||
, context : VisualisationContext
|
||||
, data : VisualizationUpdateData
|
||||
) -> FallibleResult<()> {
|
||||
let context_id = context.context_id;
|
||||
let visualization_id = context.visualization_id;
|
||||
let ctx = self.0.borrow_mut().get(&context_id);
|
||||
let ctx = ctx.ok_or_else(|| NoSuchVisualization(context_id))?;
|
||||
ctx.dispatch_visualization_update(visualization_id,data)
|
||||
self.with_context(context.context_id, |ctx| {
|
||||
ctx.dispatch_visualization_update(context.visualization_id,data)
|
||||
})
|
||||
}
|
||||
|
||||
/// Handles the update about expressions being computed.
|
||||
pub fn handle_expression_values_computed
|
||||
(&self, update:language_server::ExpressionValuesComputed) -> FallibleResult<()> {
|
||||
self.with_context(update.context_id, |ctx| {
|
||||
ctx.handle_expression_values_computed(update)
|
||||
})
|
||||
}
|
||||
|
||||
/// Registers a new ExecutionContext. It will be eligible for receiving future updates routed
|
||||
@ -105,6 +121,7 @@ impl Handle {
|
||||
let logger = Logger::sub(parent,"Project Controller");
|
||||
info!(logger,"Creating a project controller for project {project_name.as_ref()}");
|
||||
let binary_protocol_events = language_server_binary.event_stream();
|
||||
let json_rpc_events = language_server_client.events();
|
||||
let embedded_visualizations = default();
|
||||
let language_server_rpc = Rc::new(language_server_client);
|
||||
let language_server_bin = Rc::new(language_server_binary);
|
||||
@ -120,6 +137,10 @@ impl Handle {
|
||||
|
||||
let binary_handler = ret.binary_event_handler();
|
||||
crate::executor::global::spawn(binary_protocol_events.for_each(binary_handler));
|
||||
|
||||
let json_rpc_handler = ret.json_event_handler();
|
||||
crate::executor::global::spawn(json_rpc_events.for_each(json_rpc_handler));
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
@ -153,7 +174,7 @@ impl Handle {
|
||||
}
|
||||
}
|
||||
Event::Closed => {
|
||||
error!(logger,"Lost binary data connection!");
|
||||
error!(logger,"Lost binary connection with the Language Server!");
|
||||
// TODO [wmu]
|
||||
// The problem should be reported to the user and the connection should be
|
||||
// reestablished, see https://github.com/luna/ide/issues/145
|
||||
@ -166,6 +187,51 @@ impl Handle {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a handling function capable of processing updates from the json-rpc protocol.
|
||||
/// Such function will be then typically used to process events stream from the json-rpc
|
||||
/// connection handler.
|
||||
pub fn json_event_handler
|
||||
(&self) -> impl Fn(enso_protocol::language_server::Event) -> futures::future::Ready<()> {
|
||||
// TODO [mwu]
|
||||
// This handler for JSON-RPC notifications is very similar to the function above that handles
|
||||
// binary protocol notifications. However, it is not practical to generalize them, as the
|
||||
// underlying RPC handlers and their types are separate.
|
||||
// This generalization should be reconsidered once the old JSON-RPC handler is phased out.
|
||||
// See: https://github.com/luna/ide/issues/587
|
||||
let logger = self.logger.clone_ref();
|
||||
let weak_execution_contexts = Rc::downgrade(&self.execution_contexts);
|
||||
move |event| {
|
||||
debug!(logger, "Received an event from the json-rpc protocol: {event:?}");
|
||||
use enso_protocol::language_server::Event;
|
||||
use enso_protocol::language_server::Notification;
|
||||
match event {
|
||||
Event::Notification(Notification::ExpressionValuesComputed(update)) => {
|
||||
if let Some(execution_contexts) = weak_execution_contexts.upgrade() {
|
||||
let result = execution_contexts.handle_expression_values_computed(update);
|
||||
if let Err(error) = result {
|
||||
error!(logger,"Failed to handle the expression values computed update: \
|
||||
{error}.");
|
||||
}
|
||||
} else {
|
||||
error!(logger,"Received a `ExpressionValuesComputed` update despite \
|
||||
execution context being already dropped.");
|
||||
}
|
||||
}
|
||||
Event::Closed => {
|
||||
error!(logger,"Lost JSON-RPC connection with the Language Server!");
|
||||
// TODO [wmu]
|
||||
// The problem should be reported to the user and the connection should be
|
||||
// reestablished, see https://github.com/luna/ide/issues/145
|
||||
}
|
||||
Event::Error(error) => {
|
||||
error!(logger,"Error emitted by the binary data connection: {error}.");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
futures::future::ready(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a text controller for a given file path.
|
||||
///
|
||||
/// It supports both modules and plain text files.
|
||||
@ -258,6 +324,8 @@ mod test {
|
||||
use wasm_bindgen_test::wasm_bindgen_test;
|
||||
use wasm_bindgen_test::wasm_bindgen_test_configure;
|
||||
use enso_protocol::language_server::CapabilityRegistration;
|
||||
use enso_protocol::language_server::Event;
|
||||
use enso_protocol::language_server::Notification;
|
||||
use enso_protocol::types::Sha3_224;
|
||||
|
||||
|
||||
@ -340,6 +408,56 @@ mod test {
|
||||
});
|
||||
}
|
||||
|
||||
/// This tests checks mainly if:
|
||||
/// * project controller correctly creates execution context
|
||||
/// * created execution context appears in the registry
|
||||
/// * project controller correctly dispatches the LS notification with type information
|
||||
/// * the type information is correctly recorded and available in the execution context
|
||||
#[wasm_bindgen_test]
|
||||
fn execution_context_management() {
|
||||
// Setup project controller and mock LS client expectations.
|
||||
let mut test = TestWithLocalPoolExecutor::set_up();
|
||||
let data = model::synchronized::execution_context::tests::MockData::new();
|
||||
let mut sender = futures::channel::mpsc::unbounded().0;
|
||||
let project = setup_mock_project(|mock_json_client| {
|
||||
data.mock_create_push_destroy_calls(mock_json_client);
|
||||
sender = mock_json_client.setup_events();
|
||||
mock_json_client.require_all_calls();
|
||||
}, |_| {});
|
||||
|
||||
// No context present yet.
|
||||
let no_op = |_| Ok(());
|
||||
let result1 = project.execution_contexts.with_context(data.context_id,no_op);
|
||||
assert!(result1.is_err());
|
||||
|
||||
// Create execution context.
|
||||
let module_path = Rc::new(data.module_path.clone());
|
||||
let definition = data.root_definition.clone();
|
||||
let execution = project.create_execution_context(module_path,definition);
|
||||
let execution = test.expect_completion(execution).unwrap();
|
||||
|
||||
// Now context is in registry.
|
||||
let result1 = project.execution_contexts.with_context(data.context_id,no_op);
|
||||
assert!(result1.is_ok());
|
||||
|
||||
// Context has no information about type.
|
||||
let notification = data.mock_values_computed_update();
|
||||
let value_update = ¬ification.updates[0];
|
||||
let expression_id = value_update.id;
|
||||
let value_registry = execution.computed_value_info_registry();
|
||||
assert!(value_registry.get(&expression_id).is_none());
|
||||
|
||||
// Send notification with type information.
|
||||
let event = Event::Notification(Notification::ExpressionValuesComputed(notification.clone()));
|
||||
sender.unbounded_send(event).unwrap();
|
||||
test.run_until_stalled();
|
||||
|
||||
// Context now has the information about type.
|
||||
let value_info = value_registry.get(&expression_id).unwrap();
|
||||
assert_eq!(value_info.typename,value_update.typename);
|
||||
assert_eq!(value_info.method_call,value_update.method_call);
|
||||
}
|
||||
|
||||
fn mock_calls_for_opening_text_file
|
||||
(client:&language_server::MockClient, path:language_server::Path, content:&str) {
|
||||
let content = content.to_string();
|
||||
|
@ -2,10 +2,12 @@
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use futures::executor;
|
||||
use crate::executor::global::set_spawner;
|
||||
use crate::executor::global::spawn;
|
||||
|
||||
use futures::executor;
|
||||
use utils::test::traits::*;
|
||||
|
||||
|
||||
|
||||
/// A fixture for tests which makes able to run part of tests as asynchronous tasks in
|
||||
@ -44,7 +46,7 @@ impl TestWithLocalPoolExecutor {
|
||||
/// in executor.
|
||||
pub fn when_stalled<Callback>(&mut self, callback:Callback)
|
||||
where Callback : FnOnce() {
|
||||
self.executor.run_until_stalled();
|
||||
self.run_until_stalled();
|
||||
if self.running_task_count.get() > 0 {
|
||||
callback();
|
||||
}
|
||||
@ -56,17 +58,38 @@ impl TestWithLocalPoolExecutor {
|
||||
/// will be spawned, so we can test more specific asynchronous scenarios.
|
||||
pub fn when_stalled_run_task<Task>(&mut self, task : Task)
|
||||
where Task : Future<Output=()> + 'static {
|
||||
self.executor.run_until_stalled();
|
||||
self.run_until_stalled();
|
||||
if self.running_task_count.get() > 0 {
|
||||
self.run_task(task);
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs all tasks in the pool and returns if no more progress can be made on any task.
|
||||
pub fn run_until_stalled(&mut self) {
|
||||
self.executor.run_until_stalled();
|
||||
}
|
||||
|
||||
/// Runs all tasks until stalled. Panics, if some tasks remains then unfinished.
|
||||
pub fn expect_finished(&mut self) {
|
||||
self.run_until_stalled();
|
||||
assert_eq!(0,self.running_task_count.get(),"The tasks are not complete!");
|
||||
}
|
||||
|
||||
/// Runs all tasks until stalled and tries retrieving value from the future.
|
||||
/// If the future cannot complete, panics.
|
||||
///
|
||||
/// This function is useful when testing asynchronous code without using the `run_task` API
|
||||
/// (e.g. because we want to interleave the asynchronous task with other calls affecting its
|
||||
/// execution).
|
||||
pub fn expect_completion<R>(&mut self, fut:impl Future<Output=R>) -> R {
|
||||
self.run_until_stalled();
|
||||
fut.boxed_local().expect_ready()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TestWithLocalPoolExecutor {
|
||||
fn drop(&mut self) {
|
||||
// We should be able to finish test.
|
||||
self.executor.run_until_stalled();
|
||||
assert_eq!(0,self.running_task_count.get(),"Executor dropped before tasks are complete!");
|
||||
self.expect_finished();
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
#![feature(bool_to_option)]
|
||||
#![feature(cell_update)]
|
||||
#![feature(drain_filter)]
|
||||
#![feature(exact_size_is_empty)]
|
||||
#![feature(option_result_contains)]
|
||||
#![feature(trait_alias)]
|
||||
#![recursion_limit="256"]
|
||||
|
@ -2,11 +2,17 @@
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::model::module::QualifiedName as ModuleQualifiedName;
|
||||
use crate::double_representation::definition::DefinitionName;
|
||||
use crate::model::module::QualifiedName as ModuleQualifiedName;
|
||||
use crate::notification::Publisher;
|
||||
|
||||
use enso_protocol::language_server;
|
||||
use enso_protocol::language_server::ExpressionValueUpdate;
|
||||
use enso_protocol::language_server::ExpressionValuesComputed;
|
||||
use enso_protocol::language_server::MethodPointer;
|
||||
use enso_protocol::language_server::VisualisationConfiguration;
|
||||
use flo_stream::MessagePublisher;
|
||||
use flo_stream::Subscriber;
|
||||
use std::collections::HashMap;
|
||||
use uuid::Uuid;
|
||||
|
||||
@ -24,6 +30,95 @@ pub type ExpressionId = ast::Id;
|
||||
|
||||
|
||||
|
||||
// =========================
|
||||
// === ComputedValueInfo ===
|
||||
// =========================
|
||||
|
||||
/// Information about some computed value.
|
||||
///
|
||||
/// Contains "meta-data" like type or method pointer, not the computed value representation itself.
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct ComputedValueInfo {
|
||||
/// The string representing the typename of the computed value, e.g. "Number" or "Unit".
|
||||
pub typename : Option<String>,
|
||||
/// If the expression is a method call (i.e. can be entered), this points to the target method.
|
||||
pub method_call : Option<MethodPointer>,
|
||||
}
|
||||
|
||||
impl From<ExpressionValueUpdate> for ComputedValueInfo {
|
||||
fn from(update:ExpressionValueUpdate) -> Self {
|
||||
ComputedValueInfo {
|
||||
typename : update.typename,
|
||||
method_call : update.method_call,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Ids of expressions that were computed and received updates in this batch.
|
||||
pub type ComputedValueExpressions = Vec<ExpressionId>;
|
||||
|
||||
|
||||
|
||||
// =================================
|
||||
// === ComputedValueInfoRegistry ===
|
||||
// =================================
|
||||
|
||||
/// Registry that receives the `executionContext/expressionValuesComputed` notifications from the
|
||||
/// Language Server. Caches the received data. Emits notifications when the data is changed.
|
||||
#[derive(Clone,Default,Derivative)]
|
||||
#[derivative(Debug)]
|
||||
pub struct ComputedValueInfoRegistry {
|
||||
map : RefCell<HashMap<ExpressionId,Rc<ComputedValueInfo>>>,
|
||||
/// A publisher that emits an update every time a new batch of updates is received from language
|
||||
/// server.
|
||||
#[derivative(Debug="ignore")]
|
||||
updates : RefCell<Publisher<ComputedValueExpressions>>,
|
||||
}
|
||||
|
||||
impl ComputedValueInfoRegistry {
|
||||
fn emit(&self, update: ComputedValueExpressions) {
|
||||
let future = self.updates.borrow_mut().0.publish(update);
|
||||
executor::global::spawn(future);
|
||||
}
|
||||
|
||||
/// Clear the stored values.
|
||||
///
|
||||
/// When change is made to execution context (like adding or removing the call stack frame), the
|
||||
/// cache should be cleaned.
|
||||
fn clear(&self) {
|
||||
let removed_keys = self.map.borrow().keys().copied().collect_vec();
|
||||
self.map.borrow_mut().clear();
|
||||
if !removed_keys.is_empty() {
|
||||
self.emit(removed_keys);
|
||||
}
|
||||
}
|
||||
|
||||
/// Store the information from the given update received from the Language Server.
|
||||
pub fn apply_update(&self, values_computed:ExpressionValuesComputed) {
|
||||
let updated_expressions = values_computed.updates.iter().map(|update| update.id).collect();
|
||||
with(self.map.borrow_mut(), |mut map| {
|
||||
for update in values_computed.updates {
|
||||
let id = update.id;
|
||||
let info = Rc::new(ComputedValueInfo::from(update));
|
||||
map.insert(id,info);
|
||||
};
|
||||
});
|
||||
self.emit(updated_expressions);
|
||||
}
|
||||
|
||||
/// Subscribe to notifications about changes in the registry.
|
||||
pub fn subscribe(&self) -> Subscriber<ComputedValueExpressions> {
|
||||
self.updates.borrow_mut().subscribe()
|
||||
}
|
||||
|
||||
/// Look up the registry for information about given expression.
|
||||
pub fn get(&self, id:&ExpressionId) -> Option<Rc<ComputedValueInfo>> {
|
||||
self.map.borrow_mut().get(id).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===============================
|
||||
// === VisualizationUpdateData ===
|
||||
// ===============================
|
||||
@ -173,6 +268,8 @@ pub struct ExecutionContext {
|
||||
stack:RefCell<Vec<LocalCall>>,
|
||||
/// Set of active visualizations.
|
||||
visualizations: RefCell<HashMap<VisualizationId,AttachedVisualization>>,
|
||||
/// Storage for information about computed values (like their types).
|
||||
pub computed_value_info_registry: ComputedValueInfoRegistry,
|
||||
}
|
||||
|
||||
impl ExecutionContext {
|
||||
@ -181,18 +278,21 @@ impl ExecutionContext {
|
||||
let logger = logger.into();
|
||||
let stack = default();
|
||||
let visualizations = default();
|
||||
Self {logger,entry_point,stack,visualizations}
|
||||
let computed_value_info_registry = default();
|
||||
Self {logger,entry_point,stack,visualizations, computed_value_info_registry }
|
||||
}
|
||||
|
||||
/// Push a new stack item to execution context.
|
||||
pub fn push(&self, stack_item:LocalCall) {
|
||||
self.stack.borrow_mut().push(stack_item);
|
||||
self.computed_value_info_registry.clear();
|
||||
}
|
||||
|
||||
/// Pop the last stack item from this context. It returns error when only root call
|
||||
/// remains.
|
||||
pub fn pop(&self) -> FallibleResult<()> {
|
||||
self.stack.borrow_mut().pop().ok_or_else(PopOnEmptyStack)?;
|
||||
self.computed_value_info_registry.clear();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -241,4 +341,11 @@ impl ExecutionContext {
|
||||
Err(InvalidVisualizationId(visualization_id).into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles the update about expressions being computed.
|
||||
pub fn handle_expression_values_computed
|
||||
(&self, notification:ExpressionValuesComputed) -> FallibleResult<()> {
|
||||
self.computed_value_info_registry.apply_update(notification);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::double_representation::definition::DefinitionName;
|
||||
use crate::model::execution_context::ComputedValueInfoRegistry;
|
||||
use crate::model::execution_context::LocalCall;
|
||||
use crate::model::execution_context::Visualization;
|
||||
use crate::model::execution_context::VisualizationUpdateData;
|
||||
@ -139,16 +140,27 @@ impl ExecutionContext {
|
||||
self.model.dispatch_visualization_update(visualization_id,data)
|
||||
}
|
||||
|
||||
/// Handles the update about expressions being computed.
|
||||
pub fn handle_expression_values_computed
|
||||
(&self, notification:language_server::ExpressionValuesComputed) -> FallibleResult<()> {
|
||||
self.model.handle_expression_values_computed(notification)
|
||||
}
|
||||
|
||||
/// Access the registry of computed values information, like types or called method pointers.
|
||||
pub fn computed_value_info_registry(&self) -> &ComputedValueInfoRegistry {
|
||||
&self.model.computed_value_info_registry
|
||||
}
|
||||
|
||||
|
||||
/// Create a mock which does no call on `language_server` during construction.
|
||||
#[cfg(test)]
|
||||
pub fn new_mock
|
||||
( id : model::execution_context::Id
|
||||
, path : model::module::Path
|
||||
, model : model::ExecutionContext
|
||||
, language_server : language_server::MockClient
|
||||
, language_server : Rc<language_server::Connection>
|
||||
) -> Self {
|
||||
let module_path = Rc::new(path);
|
||||
let language_server = language_server::Connection::new_mock_rc(language_server);
|
||||
let logger = Logger::new("ExecuctionContext mock");
|
||||
ExecutionContext {id,model,module_path,language_server,logger}
|
||||
}
|
||||
@ -175,51 +187,169 @@ impl Drop for ExecutionContext {
|
||||
// =============
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::DEFAULT_PROJECT_NAME;
|
||||
use crate::executor::test_utils::TestWithLocalPoolExecutor;
|
||||
use crate::model::module::QualifiedName as ModuleQualifiedName;
|
||||
|
||||
use enso_protocol::language_server::CapabilityRegistration;
|
||||
use enso_protocol::language_server::response::CreateExecutionContext;
|
||||
use json_rpc::expect_call;
|
||||
use language_server::response;
|
||||
use utils::test::ExpectTuple;
|
||||
use utils::test::stream::StreamTestExt;
|
||||
|
||||
trait ModelCustomizer = Fn(&mut model::ExecutionContext, &MockData) + 'static;
|
||||
|
||||
#[test]
|
||||
fn creating_context() {
|
||||
let path = Rc::new(model::module::Path::from_mock_module_name("Test"));
|
||||
let context_id = model::execution_context::Id::new_v4();
|
||||
let root_def = DefinitionName::new_plain("main");
|
||||
let ls_client = language_server::MockClient::default();
|
||||
/// Set of data needed to create and operate mock execution context.
|
||||
#[derive(Clone,Derivative)]
|
||||
#[derivative(Debug)]
|
||||
pub struct MockData {
|
||||
pub module_path : model::module::Path,
|
||||
pub context_id : model::execution_context::Id,
|
||||
pub root_definition : DefinitionName,
|
||||
pub project_name : String,
|
||||
#[derivative(Debug="ignore")]
|
||||
pub customize_model : Rc<dyn ModelCustomizer>,
|
||||
}
|
||||
|
||||
impl Default for MockData {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl MockData {
|
||||
pub fn new() -> MockData {
|
||||
MockData {
|
||||
context_id : model::execution_context::Id::new_v4(),
|
||||
module_path : model::module::Path::from_mock_module_name("Test"),
|
||||
root_definition : DefinitionName::new_plain("main"),
|
||||
project_name : DEFAULT_PROJECT_NAME.to_string(),
|
||||
customize_model : Rc::new(|_,_| {})
|
||||
}
|
||||
}
|
||||
|
||||
/// WHat is expected server's response to a successful creation of this context.
|
||||
pub fn expected_creation_response(&self) -> CreateExecutionContext {
|
||||
let context_id = self.context_id;
|
||||
let can_modify =
|
||||
CapabilityRegistration::create_can_modify_execution_context(context_id);
|
||||
let receives_updates =
|
||||
CapabilityRegistration::create_receives_execution_context_updates(context_id);
|
||||
ls_client.expect.create_execution_context(move || Ok(response::CreateExecutionContext {
|
||||
context_id,can_modify,receives_updates,
|
||||
}));
|
||||
let method = language_server::MethodPointer {
|
||||
file : path.file_path().clone(),
|
||||
defined_on_type : "Test".to_string(),
|
||||
name : "main".to_string(),
|
||||
};
|
||||
CreateExecutionContext {context_id,can_modify,receives_updates}
|
||||
}
|
||||
|
||||
/// Sets up mock client expectations for context creation and destruction.
|
||||
pub fn mock_create_destroy_calls(&self, ls:&mut language_server::MockClient) {
|
||||
let id = self.context_id;
|
||||
let result = self.expected_creation_response();
|
||||
expect_call!(ls.create_execution_context() => Ok(result));
|
||||
expect_call!(ls.destroy_execution_context(id) => Ok(()));
|
||||
}
|
||||
|
||||
/// Sets up mock client expectations for context creation, initial frame push
|
||||
/// and destruction.
|
||||
pub fn mock_create_push_destroy_calls(&self, ls:&mut language_server::MockClient) {
|
||||
self.mock_create_destroy_calls(ls);
|
||||
let id = self.context_id;
|
||||
let root_frame = language_server::ExplicitCall {
|
||||
method_pointer : method,
|
||||
method_pointer : self.main_method_pointer(),
|
||||
this_argument_expression : None,
|
||||
positional_arguments_expressions : vec![]
|
||||
};
|
||||
let stack_item = language_server::StackItem::ExplicitCall(root_frame);
|
||||
expect_call!(ls_client.push_to_execution_context(context_id,stack_item) => Ok(()));
|
||||
expect_call!(ls_client.destroy_execution_context(context_id) => Ok(()));
|
||||
expect_call!(ls.push_to_execution_context(id,stack_item) => Ok(()));
|
||||
}
|
||||
|
||||
/// Generates a mock update for a random expression id.
|
||||
pub fn mock_expression_value_update() -> language_server::ExpressionValueUpdate {
|
||||
language_server::ExpressionValueUpdate {
|
||||
id : model::execution_context::ExpressionId::new_v4(),
|
||||
typename : Some("typename".into()),
|
||||
short_value : None,
|
||||
method_call : None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a mock update for a single expression.
|
||||
pub fn mock_values_computed_update(&self) -> language_server::ExpressionValuesComputed {
|
||||
language_server::ExpressionValuesComputed {
|
||||
context_id : self.context_id,
|
||||
updates : vec![Self::mock_expression_value_update()],
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows customizing initial state of the context's model.
|
||||
///
|
||||
/// Call and pass a function, and it will be called when setting up the model.
|
||||
pub fn customize_model(&mut self, f:impl Fn(&mut model::ExecutionContext, &MockData) + 'static) {
|
||||
self.customize_model = Rc::new(f);
|
||||
}
|
||||
|
||||
/// Create an exeuction context's model.
|
||||
pub fn create_model(&self) -> model::ExecutionContext {
|
||||
let logger = Logger::new("MockExecutionContextModel");
|
||||
model::ExecutionContext::new(logger, self.root_definition.clone())
|
||||
}
|
||||
|
||||
/// Create an exeuction context's controller.
|
||||
pub fn create_context(&self, mut ls:language_server::MockClient) -> ExecutionContext {
|
||||
self.context_provider(&mut ls)(language_server::Connection::new_mock_rc(ls))
|
||||
}
|
||||
|
||||
/// Generate a method that when called will generate the execution context.
|
||||
///
|
||||
/// Purpose of this method is to be able obtain provider without losing ownership of the
|
||||
/// MockClient. This allows setting up multiple mock controllers that share a single mocked
|
||||
/// client.
|
||||
pub fn context_provider
|
||||
(&self, ls:&mut language_server::MockClient)
|
||||
-> impl FnOnce(Rc<language_server::Connection>) -> ExecutionContext {
|
||||
let id = self.context_id;
|
||||
expect_call!(ls.destroy_execution_context(id) => Ok(()));
|
||||
|
||||
let data = self.clone();
|
||||
move |ls| {
|
||||
let mut model = data.create_model();
|
||||
(data.customize_model)(&mut model,&data);
|
||||
ExecutionContext::new_mock(data.context_id, data.module_path.clone(), model, ls)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn module_qualified_name(&self) -> ModuleQualifiedName {
|
||||
ModuleQualifiedName::from_path(&self.module_path,&self.project_name)
|
||||
}
|
||||
|
||||
pub fn definition_id(&self) -> model::execution_context::DefinitionId {
|
||||
model::execution_context::DefinitionId::new_single_crumb(self.root_definition.clone())
|
||||
}
|
||||
|
||||
pub fn main_method_pointer(&self) -> language_server::MethodPointer {
|
||||
language_server::MethodPointer {
|
||||
file : self.module_path.file_path().clone(),
|
||||
defined_on_type : self.module_path.module_name().to_string(),
|
||||
name : self.root_definition.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn creating_context() {
|
||||
let mock_data = MockData::new();
|
||||
let context_id = mock_data.context_id;
|
||||
let mut ls_client = language_server::MockClient::default();
|
||||
mock_data.mock_create_push_destroy_calls(&mut ls_client);
|
||||
ls_client.require_all_calls();
|
||||
let connection = language_server::Connection::new_mock_rc(ls_client);
|
||||
|
||||
let mut test = TestWithLocalPoolExecutor::set_up();
|
||||
test.run_task(async move {
|
||||
let context = ExecutionContext::create(Logger::default(),connection,path.clone(),root_def);
|
||||
let logger = Logger::default();
|
||||
let path = Rc::new(mock_data.module_path);
|
||||
let def = mock_data.root_definition;
|
||||
let context = ExecutionContext::create(logger,connection,path.clone_ref(),def);
|
||||
let context = context.await.unwrap();
|
||||
assert_eq!(context_id , context.id);
|
||||
assert_eq!(path , context.module_path);
|
||||
@ -229,25 +359,19 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn pushing_stack_item() {
|
||||
let id = model::execution_context::Id::new_v4();
|
||||
let definition = model::execution_context::DefinitionId::new_plain_name("foo");
|
||||
let expression_id = model::execution_context::ExpressionId::new_v4();
|
||||
let path = model::module::Path::from_mock_module_name("Test");
|
||||
let root_def = DefinitionName::new_plain("main");
|
||||
let model = model::ExecutionContext::new(Logger::default(),root_def);
|
||||
let mock_data = MockData::new();
|
||||
let ls = language_server::MockClient::default();
|
||||
let id = mock_data.context_id;
|
||||
let expression_id = model::execution_context::ExpressionId::new_v4();
|
||||
let expected_call_frame = language_server::LocalCall{expression_id};
|
||||
let expected_stack_item = language_server::StackItem::LocalCall(expected_call_frame);
|
||||
|
||||
expect_call!(ls.push_to_execution_context(id,expected_stack_item) => Ok(()));
|
||||
expect_call!(ls.destroy_execution_context(id) => Ok(()));
|
||||
let context = ExecutionContext::new_mock(id,path,model,ls);
|
||||
|
||||
let context = mock_data.create_context(ls);
|
||||
let mut test = TestWithLocalPoolExecutor::set_up();
|
||||
test.run_task(async move {
|
||||
let item = LocalCall {
|
||||
call : expression_id,
|
||||
definition : definition.clone()
|
||||
definition : mock_data.definition_id(),
|
||||
};
|
||||
context.push(item.clone()).await.unwrap();
|
||||
assert_eq!((item,), context.model.stack_items().expect_tuple());
|
||||
@ -256,19 +380,21 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn popping_stack_item() {
|
||||
let id = model::execution_context::Id::new_v4();
|
||||
let mock_data = MockData {
|
||||
customize_model : Rc::new(|model,data| {
|
||||
let item = LocalCall {
|
||||
call : model::execution_context::ExpressionId::new_v4(),
|
||||
definition : model::execution_context::DefinitionId::new_plain_name("foo"),
|
||||
definition : data.definition_id(),
|
||||
};
|
||||
let path = model::module::Path::from_mock_module_name("Test");
|
||||
let root_def = DefinitionName::new_plain("main");
|
||||
let ls = language_server::MockClient::default();
|
||||
let model = model::ExecutionContext::new(Logger::default(),root_def);
|
||||
expect_call!(ls.pop_from_execution_context(id) => Ok(()));
|
||||
expect_call!(ls.destroy_execution_context(id) => Ok(()));
|
||||
model.push(item);
|
||||
let context = ExecutionContext::new_mock(id,path,model,ls);
|
||||
}),
|
||||
..default()
|
||||
};
|
||||
|
||||
let ls = language_server::MockClient::default();
|
||||
let id = mock_data.context_id;
|
||||
expect_call!(ls.pop_from_execution_context(id) => Ok(()));
|
||||
let context = mock_data.create_context(ls);
|
||||
|
||||
let mut test = TestWithLocalPoolExecutor::set_up();
|
||||
test.run_task(async move {
|
||||
@ -281,26 +407,23 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn attaching_visualizations_and_notifying() {
|
||||
let exe_id = model::execution_context::Id::new_v4();
|
||||
let path = model::module::Path::from_mock_module_name("Test");
|
||||
let root_def = DefinitionName::new_plain("main");
|
||||
let model = model::ExecutionContext::new(Logger::default(),root_def);
|
||||
let mock_data = MockData::new();
|
||||
let ls = language_server::MockClient::default();
|
||||
let vis = Visualization {
|
||||
id : model::execution_context::VisualizationId::new_v4(),
|
||||
ast_id : model::execution_context::ExpressionId::new_v4(),
|
||||
expression : "".to_string(),
|
||||
visualisation_module : ModuleQualifiedName::from_path(&path,"PPPP"),
|
||||
visualisation_module : mock_data.module_qualified_name(),
|
||||
};
|
||||
let exe_id = mock_data.context_id;
|
||||
let vis_id = vis.id;
|
||||
let ast_id = vis.ast_id;
|
||||
let config = vis.config(exe_id);
|
||||
|
||||
expect_call!(ls.attach_visualisation(vis_id,ast_id,config) => Ok(()));
|
||||
expect_call!(ls.detach_visualisation(exe_id,vis_id,ast_id) => Ok(()));
|
||||
expect_call!(ls.destroy_execution_context(exe_id) => Ok(()));
|
||||
|
||||
let context = ExecutionContext::new_mock(exe_id,path,model,ls);
|
||||
let context = mock_data.create_context(ls);
|
||||
|
||||
let mut test = TestWithLocalPoolExecutor::set_up();
|
||||
test.run_task(async move {
|
||||
|
@ -3,6 +3,7 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::controller::graph::NodeTrees;
|
||||
use crate::model::execution_context::ExpressionId;
|
||||
use crate::model::execution_context::Visualization;
|
||||
use crate::model::execution_context::VisualizationId;
|
||||
use crate::model::execution_context::VisualizationUpdateData;
|
||||
@ -172,8 +173,8 @@ impl GraphEditorIntegratedWithController {
|
||||
frp::extend! {network
|
||||
// Notifications from controller
|
||||
let handle_notification = FencedAction::fence(&network,
|
||||
f!((notification:&Option<controller::graph::Notification>)
|
||||
model.handle_controller_notification(*notification);
|
||||
f!((notification:&Option<controller::graph::executed::Notification>)
|
||||
model.handle_controller_notification(notification);
|
||||
));
|
||||
|
||||
// Changes in Graph Editor
|
||||
@ -192,9 +193,9 @@ impl GraphEditorIntegratedWithController {
|
||||
|
||||
fn connect_frp_to_controller_notifications
|
||||
( model : &Rc<GraphEditorIntegratedWithControllerModel>
|
||||
, frp_endpoint : frp::Source<Option<controller::graph::Notification>>
|
||||
, frp_endpoint : frp::Source<Option<controller::graph::executed::Notification>>
|
||||
) {
|
||||
let stream = model.controller.graph.subscribe();
|
||||
let stream = model.controller.subscribe();
|
||||
let weak = Rc::downgrade(model);
|
||||
let handler = process_stream_with_handle(stream,weak,move |notification,_model| {
|
||||
frp_endpoint.emit_event(&Some(notification));
|
||||
@ -257,6 +258,12 @@ impl GraphEditorIntegratedWithControllerModel {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_relevant_type_information
|
||||
(&self, affected_asts:&[ExpressionId]) -> FallibleResult<()> {
|
||||
debug!(self.logger, "Will update type information: {affected_asts:?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_node_views
|
||||
(&self, mut trees:HashMap<double_representation::node::Id,NodeTrees>) -> FallibleResult<()> {
|
||||
let nodes = self.controller.graph.nodes()?;
|
||||
@ -392,9 +399,14 @@ impl GraphEditorIntegratedWithControllerModel {
|
||||
impl GraphEditorIntegratedWithControllerModel {
|
||||
/// Handle notification received from controller.
|
||||
pub fn handle_controller_notification
|
||||
(&self, notification:Option<controller::graph::Notification>) {
|
||||
(&self, notification:&Option<controller::graph::executed::Notification>) {
|
||||
use controller::graph::executed::Notification;
|
||||
use controller::graph::Notification::Invalidate;
|
||||
|
||||
let result = match notification {
|
||||
Some(controller::graph::Notification::Invalidate) => self.update_graph_view(),
|
||||
Some(Notification::Graph(Invalidate)) => self.update_graph_view(),
|
||||
Some(Notification::ComputedValueInfo(update)) =>
|
||||
self.update_relevant_type_information(update),
|
||||
other => {
|
||||
warning!(self.logger,"Handling notification {other:?} is not implemented; \
|
||||
performing full invalidation");
|
||||
@ -590,6 +602,7 @@ impl GraphEditorIntegratedWithControllerModel {
|
||||
/// Node Editor Panel integrated with Graph Controller.
|
||||
#[derive(Clone,CloneRef,Debug)]
|
||||
pub struct NodeEditor {
|
||||
logger : Logger,
|
||||
display_object : display::object::Instance,
|
||||
#[allow(missing_docs)]
|
||||
pub graph : Rc<GraphEditorIntegratedWithController>,
|
||||
@ -605,11 +618,13 @@ impl NodeEditor {
|
||||
, controller : controller::ExecutedGraph
|
||||
, visualization : controller::Visualization) -> FallibleResult<Self> {
|
||||
let logger = Logger::sub(logger,"NodeEditor");
|
||||
info!(logger, "Created.");
|
||||
let display_object = display::object::Instance::new(&logger);
|
||||
let graph = GraphEditorIntegratedWithController::new(logger,app,controller.clone_ref());
|
||||
let graph = GraphEditorIntegratedWithController::new(logger.clone_ref(),app,
|
||||
controller.clone_ref());
|
||||
let graph = Rc::new(graph);
|
||||
display_object.add_child(&graph.model.editor);
|
||||
Ok(NodeEditor {display_object,graph,controller,visualization}.init().await?)
|
||||
Ok(NodeEditor {logger,display_object,graph,controller,visualization}.init().await?)
|
||||
}
|
||||
|
||||
async fn init(self) -> FallibleResult<Self> {
|
||||
@ -623,6 +638,7 @@ impl NodeEditor {
|
||||
});
|
||||
visualization?;
|
||||
}
|
||||
info!(self.logger, "Initialized.");
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user