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:
Michał Wawrzyniec Urbańczyk 2020-06-22 12:17:20 +02:00 committed by GitHub
parent 0f018e61a0
commit cb6a16d402
14 changed files with 769 additions and 150 deletions

View File

@ -127,7 +127,7 @@ commands.build.rust = async function(argv) {
console.log('Checking the resulting WASM size.') console.log('Checking the resulting WASM size.')
let stats = fss.statSync(paths.dist.wasm.mainOptGz) 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 let size = Math.round(100 * stats.size / 1024 / 1024) / 100
if (size > limit) { if (size > limit) {
throw(`Output file size exceeds the limit (${size}MB > ${limit}MB).`) throw(`Output file size exceeds the limit (${size}MB > ${limit}MB).`)

View File

@ -23,7 +23,6 @@ use crate::types::Sha3_224;
use json_rpc::api::Result; use json_rpc::api::Result;
use json_rpc::Handler; use json_rpc::Handler;
use json_rpc::make_rpc_methods; use json_rpc::make_rpc_methods;
use futures::Stream;
use serde::Serialize; use serde::Serialize;
use serde::Deserialize; use serde::Deserialize;
use std::future::Future; use std::future::Future;

View File

@ -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] #[test]
fn test_execution_context() { fn test_execution_context() {
let root_id = uuid::Uuid::parse_str("00000000-0000-0000-0000-000000000000"); let root_id = uuid::Uuid::parse_str("00000000-0000-0000-0000-000000000000");

View File

@ -117,9 +117,35 @@ pub enum Notification {
/// to address this: https://github.com/luna/enso/issues/707 /// to address this: https://github.com/luna/enso/issues/707
// TODO [mwu] Update as the issue is resolved on way or another. // TODO [mwu] Update as the issue is resolved on way or another.
event:FileEvent, 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>,
}
// ================= // =================

View File

@ -9,7 +9,6 @@ use crate::types::UTCDateTime;
use json_rpc::api::Result; use json_rpc::api::Result;
use json_rpc::Handler; use json_rpc::Handler;
use json_rpc::make_rpc_methods; use json_rpc::make_rpc_methods;
use futures::Stream;
use serde::Serialize; use serde::Serialize;
use serde::Deserialize; use serde::Deserialize;
use std::future::Future; use std::future::Future;

View File

@ -43,6 +43,11 @@ macro_rules! make_rpc_methods {
fn $method<'a>(&'a self $(,$param_name:&'a $param_ty)*) fn $method<'a>(&'a self $(,$param_name:&'a $param_ty)*)
-> std::pin::Pin<Box<dyn Future<Output=Result<$result>>>>; -> 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 } 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 /// Returns a future that performs any background, asynchronous work needed
/// for this Client to correctly work. Should be continually run while the /// for this Client to correctly work. Should be continually run while the
/// `Client` is used. Will end once `Client` is dropped. /// `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); let result_fut = self.handler.borrow().open_request_with_json(name,&input_json);
Box::pin(result_fut) 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>, require_all_calls : Cell<bool>,
/// Expected calls handlers. /// Expected calls handlers.
pub expect : ExpectedCalls, pub expect : ExpectedCalls,
events : RefCell<Option<futures::channel::mpsc::UnboundedReceiver<Event>>>,
} }
impl API for Client { impl API for Client {
@ -149,6 +152,14 @@ macro_rules! make_rpc_methods {
let result = handler($($param_name),*); let result = handler($($param_name),*);
Box::pin(futures::future::ready(result)) 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 { impl Client {
@ -157,6 +168,13 @@ macro_rules! make_rpc_methods {
pub fn require_all_calls(&self) { pub fn require_all_calls(&self) {
self.require_all_calls.set(true); 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 { impl Drop for Client {
@ -202,6 +220,10 @@ macro_rules! make_rpc_methods {
/// ``` /// ```
#[macro_export] #[macro_export]
macro_rules! expect_call { 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) => { ($mock:ident.$method:ident($($param:ident),*) => $result:expr) => {
expect_call!($mock.$method($($param=$param),*) => $result) expect_call!($mock.$method($($param=$param),*) => $result)
}; };

View File

@ -751,41 +751,88 @@ mod tests {
use wasm_bindgen_test::wasm_bindgen_test; use wasm_bindgen_test::wasm_bindgen_test;
use ast::test_utils::expect_shape; use ast::test_utils::expect_shape;
struct GraphControllerFixture(TestWithLocalPoolExecutor); /// All the data needed to set up and run the graph controller in mock environment.
impl GraphControllerFixture { #[derive(Clone,Debug)]
pub fn set_up() -> GraphControllerFixture { 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(); let nested = TestWithLocalPoolExecutor::set_up();
Self(nested) Self(nested)
} }
pub fn run_graph_for_main<Test,Fut> pub fn run<Fut>
(&mut self, code:impl Str, function_name:impl Str, test:Test) ( &mut self
where Test : FnOnce(controller::Module,Handle) -> Fut + 'static, , data : MockData
Fut : Future<Output=()> { , test : impl FnOnce(controller::Module,Handle) -> Fut + 'static
let code = code.as_ref(); ) where Fut : Future<Output=()> {
let ls = language_server::Connection::new_mock_rc(default()); let (module,graph) = data.create_controllers();
let path = ModulePath::from_mock_module_name("Main"); self.run_task(async move {
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 {
test(module,graph).await 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) 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, where Test : FnOnce(controller::Module,Handle) -> Fut + 'static,
Fut : Future<Output=()> { Fut : Future<Output=()> {
let code = code.as_ref();
let ls = language_server::Connection::new_mock_rc(default()); let mut data = MockData::new(code);
let path = ModulePath::from_mock_module_name("Main"); data.graph_id = graph_id;
let parser = Parser::new_or_panic(); self.run(data,test);
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
})
} }
pub fn run_inline_graph<Test,Fut>(&mut self, definition_body:impl Str, test: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=()> { Fut : Future<Output=()> {
assert_eq!(definition_body.as_ref().contains('\n'), false); assert_eq!(definition_body.as_ref().contains('\n'), false);
let code = format!("main = {}", definition_body.as_ref()); let code = format!("main = {}", definition_body.as_ref());
let name = "main"; self.run_graph_for_main(code,test)
self.run_graph_for_main(code, name, test)
} }
} }
@ -820,8 +866,8 @@ mod tests {
#[wasm_bindgen_test] #[wasm_bindgen_test]
fn graph_controller_notification_relay() { fn graph_controller_notification_relay() {
let mut test = GraphControllerFixture::set_up(); let mut test = Fixture::set_up();
test.run_graph_for_main("main = 2 + 2", "main", |module, graph| async move { test.run_graph_for_main("main = 2 + 2", |module, graph| async move {
let text_change = TextChange::insert(Index::new(12), "2".into()); let text_change = TextChange::insert(Index::new(12), "2".into());
module.apply_code_change(text_change).unwrap(); module.apply_code_change(text_change).unwrap();
@ -833,7 +879,7 @@ mod tests {
#[wasm_bindgen_test] #[wasm_bindgen_test]
fn graph_controller_inline_definition() { fn graph_controller_inline_definition() {
let mut test = GraphControllerFixture::set_up(); let mut test = Fixture::set_up();
const EXPRESSION: &str = "2+2"; const EXPRESSION: &str = "2+2";
test.run_inline_graph(EXPRESSION, |_,graph| async move { test.run_inline_graph(EXPRESSION, |_,graph| async move {
let nodes = graph.nodes().unwrap(); let nodes = graph.nodes().unwrap();
@ -847,12 +893,12 @@ mod tests {
#[wasm_bindgen_test] #[wasm_bindgen_test]
fn graph_controller_block_definition() { fn graph_controller_block_definition() {
let mut test = GraphControllerFixture::set_up(); let mut test = Fixture::set_up();
let program = r" let program = r"
main = main =
foo = 2 foo = 2
print foo"; 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 nodes = graph.nodes().unwrap();
let (node1,node2) = nodes.expect_tuple(); let (node1,node2) = nodes.expect_tuple();
assert_eq!(node1.info.expression().repr(), "2"); assert_eq!(node1.info.expression().repr(), "2");
@ -862,9 +908,9 @@ main =
#[wasm_bindgen_test] #[wasm_bindgen_test]
fn graph_controller_parse_expression() { fn graph_controller_parse_expression() {
let mut test = GraphControllerFixture::set_up(); let mut test = Fixture::set_up();
let program = r"main = 0"; 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(); let foo = graph.parse_node_expression("foo").unwrap();
assert_eq!(expect_shape::<ast::Var>(&foo), &ast::Var {name:"foo".into()}); assert_eq!(expect_shape::<ast::Var>(&foo), &ast::Var {name:"foo".into()});
@ -878,9 +924,9 @@ main =
#[wasm_bindgen_test] #[wasm_bindgen_test]
fn graph_controller_used_names_in_inline_def() { 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"; 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 expected_name = LocatedName::new_root(NormalizedName::new("foo"));
let used_names = graph.used_names().unwrap(); let used_names = graph.used_names().unwrap();
assert_eq!(used_names, vec![expected_name]); assert_eq!(used_names, vec![expected_name]);
@ -889,7 +935,7 @@ main =
#[wasm_bindgen_test] #[wasm_bindgen_test]
fn graph_controller_nested_definition() { fn graph_controller_nested_definition() {
let mut test = GraphControllerFixture::set_up(); let mut test = Fixture::set_up();
const PROGRAM:&str = r"main = const PROGRAM:&str = r"main =
foo a = foo a =
bar b = 5 bar b = 5
@ -911,7 +957,7 @@ main =
fn graph_controller_doubly_nested_definition() { fn graph_controller_doubly_nested_definition() {
// Tests editing nested definition that requires transforming inline expression into // Tests editing nested definition that requires transforming inline expression into
// into a new block. // 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 // Not using multi-line raw string literals, as we don't want IntelliJ to automatically
// strip the trailing whitespace in the lines. // strip the trailing whitespace in the lines.
const PROGRAM:&str = "main =\n foo a =\n bar b = 5\n print foo"; const PROGRAM:&str = "main =\n foo a =\n bar b = 5\n print foo";
@ -927,12 +973,12 @@ main =
#[wasm_bindgen_test] #[wasm_bindgen_test]
fn graph_controller_node_operations_node() { fn graph_controller_node_operations_node() {
let mut test = GraphControllerFixture::set_up(); let mut test = Fixture::set_up();
const PROGRAM:&str = r" const PROGRAM:&str = r"
main = main =
foo = 2 foo = 2
print foo"; 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 === // === Initial nodes ===
let nodes = graph.nodes().unwrap(); let nodes = graph.nodes().unwrap();
let (node1,node2) = nodes.expect_tuple(); let (node1,node2) = nodes.expect_tuple();
@ -988,7 +1034,7 @@ main =
#[wasm_bindgen_test] #[wasm_bindgen_test]
fn graph_controller_connections_listing() { fn graph_controller_connections_listing() {
let mut test = GraphControllerFixture::set_up(); let mut test = Fixture::set_up();
const PROGRAM:&str = r" const PROGRAM:&str = r"
main = main =
x,y = get_pos x,y = get_pos
@ -997,7 +1043,7 @@ main =
print z print z
foo foo
print z"; 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 connections = graph.connections().unwrap();
let (node0,node1,node2,node3,node4) = graph.nodes().unwrap().expect_tuple(); let (node0,node1,node2,node3,node4) = graph.nodes().unwrap().expect_tuple();
@ -1052,7 +1098,7 @@ main =
impl Case { impl Case {
fn run(&self) { 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_prefix = format!("main = \n {} = foo\n ",self.src);
let main = format!("{}{}",main_prefix,self.dst); let main = format!("{}{}",main_prefix,self.dst);
let expected = format!("{}{}",main_prefix,self.expected); let expected = format!("{}{}",main_prefix,self.expected);
@ -1062,7 +1108,7 @@ main =
let src_port = src_port.to_vec(); let src_port = src_port.to_vec();
let dst_port = dst_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 (node0,node1) = graph.nodes().unwrap().expect_tuple();
let source = Endpoint::new(node0.info.id(),src_port.to_vec()); let source = Endpoint::new(node0.info.id(),src_port.to_vec());
let destination = Endpoint::new(node1.info.id(),dst_port.to_vec()); let destination = Endpoint::new(node1.info.id(),dst_port.to_vec());
@ -1086,7 +1132,7 @@ main =
#[wasm_bindgen_test] #[wasm_bindgen_test]
fn graph_controller_create_connection_reordering() { fn graph_controller_create_connection_reordering() {
let mut test = GraphControllerFixture::set_up(); let mut test = Fixture::set_up();
const PROGRAM:&str = r"main = const PROGRAM:&str = r"main =
sum = _ + _ sum = _ + _
a = 1 a = 1
@ -1095,7 +1141,7 @@ main =
a = 1 a = 1
b = 3 b = 3
sum = _ + b"; 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()); assert!(graph.connections().unwrap().connections.is_empty());
let (node0,_node1,node2) = graph.nodes().unwrap().expect_tuple(); let (node0,_node1,node2) = graph.nodes().unwrap().expect_tuple();
let connection_to_add = Connection { let connection_to_add = Connection {
@ -1118,7 +1164,7 @@ main =
#[wasm_bindgen_test] #[wasm_bindgen_test]
fn graph_controller_create_connection_introducing_var() { fn graph_controller_create_connection_introducing_var() {
let mut test = GraphControllerFixture::set_up(); let mut test = Fixture::set_up();
const PROGRAM:&str = r"main = const PROGRAM:&str = r"main =
calculate calculate
print _ print _
@ -1131,7 +1177,7 @@ main =
print calculate5 print calculate5
calculate1 = calculate2 calculate1 = calculate2
calculate3 calculate5 = calculate5 calculate4"; 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()); assert!(graph.connections().unwrap().connections.is_empty());
let (node0,node1,_) = graph.nodes().unwrap().expect_tuple(); let (node0,node1,_) = graph.nodes().unwrap().expect_tuple();
let connection_to_add = Connection { let connection_to_add = Connection {
@ -1187,13 +1233,13 @@ main =
impl Case { impl Case {
fn run(&self) { fn run(&self) {
let mut test = GraphControllerFixture::set_up(); let mut test = Fixture::set_up();
const MAIN_PREFIX:&str = "main = \n in = foo\n "; const MAIN_PREFIX:&str = "main = \n in = foo\n ";
let main = format!("{}{}",MAIN_PREFIX,self.dest_node_expr); let main = format!("{}{}",MAIN_PREFIX,self.dest_node_expr);
let expected = format!("{}{}",MAIN_PREFIX,self.dest_node_expected); let expected = format!("{}{}",MAIN_PREFIX,self.dest_node_expected);
let this = self.clone(); 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 connections = graph.connections().unwrap();
let connection = connections.connections.first().unwrap(); let connection = connections.connections.first().unwrap();
graph.disconnect(connection).unwrap(); graph.disconnect(connection).unwrap();

View File

@ -5,11 +5,35 @@
//! visualisations, retrieving types on ports, etc. //! visualisations, retrieving types on ports, etc.
use crate::prelude::*; use crate::prelude::*;
use crate::model::execution_context::ComputedValueInfoRegistry;
use crate::model::execution_context::Visualization; use crate::model::execution_context::Visualization;
use crate::model::execution_context::VisualizationId; use crate::model::execution_context::VisualizationId;
use crate::model::execution_context::VisualizationUpdateData; use crate::model::execution_context::VisualizationUpdateData;
use crate::model::synchronized::ExecutionContext; 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. /// Handle providing executed graph controller interface.
#[derive(Clone,CloneRef,Debug)] #[derive(Clone,CloneRef,Debug)]
pub struct Handle { pub struct Handle {
@ -47,7 +71,21 @@ impl Handle {
self.execution_ctx.detach_visualization(id).await 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 { impl Deref for Handle {
@ -57,3 +95,58 @@ impl Deref for Handle {
&self.graph &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 = &notification.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();
}
}

View File

@ -7,7 +7,6 @@ use crate::prelude::*;
use crate::controller::FilePath; use crate::controller::FilePath;
use crate::controller::Visualization; use crate::controller::Visualization;
use crate::model::execution_context::VisualizationId;
use crate::model::execution_context::VisualizationUpdateData; use crate::model::execution_context::VisualizationUpdateData;
use crate::model::module::QualifiedName as ModuleQualifiedName; use crate::model::module::QualifiedName as ModuleQualifiedName;
use crate::model::module::Path as ModulePath; use crate::model::module::Path as ModulePath;
@ -37,8 +36,8 @@ type ExecutionContextId = model::execution_context::Id;
#[allow(missing_docs)] #[allow(missing_docs)]
#[derive(Clone,Copy,Debug,Fail)] #[derive(Clone,Copy,Debug,Fail)]
#[fail(display="No visualization with id {} was found in the registry.", _0)] #[fail(display="No execution context with id {} was found in the registry.", _0)]
pub struct NoSuchVisualization(VisualizationId); pub struct NoSuchExecutionContext(ExecutionContextId);
// === Aliases === // === Aliases ===
@ -54,17 +53,34 @@ type ExecutionContextWeakMap = WeakValueHashMap<ExecutionContextId,Weak<Executio
pub struct ExecutionContextsRegistry(RefCell<ExecutionContextWeakMap>); pub struct ExecutionContextsRegistry(RefCell<ExecutionContextWeakMap>);
impl ExecutionContextsRegistry { 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 pub fn dispatch_visualization_update
(&self (&self
, context : VisualisationContext , context : VisualisationContext
, data : VisualizationUpdateData , data : VisualizationUpdateData
) -> FallibleResult<()> { ) -> FallibleResult<()> {
let context_id = context.context_id; self.with_context(context.context_id, |ctx| {
let visualization_id = context.visualization_id; ctx.dispatch_visualization_update(context.visualization_id,data)
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)
/// 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 /// 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"); let logger = Logger::sub(parent,"Project Controller");
info!(logger,"Creating a project controller for project {project_name.as_ref()}"); info!(logger,"Creating a project controller for project {project_name.as_ref()}");
let binary_protocol_events = language_server_binary.event_stream(); let binary_protocol_events = language_server_binary.event_stream();
let json_rpc_events = language_server_client.events();
let embedded_visualizations = default(); let embedded_visualizations = default();
let language_server_rpc = Rc::new(language_server_client); let language_server_rpc = Rc::new(language_server_client);
let language_server_bin = Rc::new(language_server_binary); let language_server_bin = Rc::new(language_server_binary);
@ -120,6 +137,10 @@ impl Handle {
let binary_handler = ret.binary_event_handler(); let binary_handler = ret.binary_event_handler();
crate::executor::global::spawn(binary_protocol_events.for_each(binary_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 ret
} }
@ -153,7 +174,7 @@ impl Handle {
} }
} }
Event::Closed => { Event::Closed => {
error!(logger,"Lost binary data connection!"); error!(logger,"Lost binary connection with the Language Server!");
// TODO [wmu] // TODO [wmu]
// The problem should be reported to the user and the connection should be // The problem should be reported to the user and the connection should be
// reestablished, see https://github.com/luna/ide/issues/145 // 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. /// Returns a text controller for a given file path.
/// ///
/// It supports both modules and plain text files. /// 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;
use wasm_bindgen_test::wasm_bindgen_test_configure; use wasm_bindgen_test::wasm_bindgen_test_configure;
use enso_protocol::language_server::CapabilityRegistration; use enso_protocol::language_server::CapabilityRegistration;
use enso_protocol::language_server::Event;
use enso_protocol::language_server::Notification;
use enso_protocol::types::Sha3_224; 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 = &notification.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 fn mock_calls_for_opening_text_file
(client:&language_server::MockClient, path:language_server::Path, content:&str) { (client:&language_server::MockClient, path:language_server::Path, content:&str) {
let content = content.to_string(); let content = content.to_string();

View File

@ -2,10 +2,12 @@
use crate::prelude::*; use crate::prelude::*;
use futures::executor;
use crate::executor::global::set_spawner; use crate::executor::global::set_spawner;
use crate::executor::global::spawn; 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 /// A fixture for tests which makes able to run part of tests as asynchronous tasks in
@ -44,7 +46,7 @@ impl TestWithLocalPoolExecutor {
/// in executor. /// in executor.
pub fn when_stalled<Callback>(&mut self, callback:Callback) pub fn when_stalled<Callback>(&mut self, callback:Callback)
where Callback : FnOnce() { where Callback : FnOnce() {
self.executor.run_until_stalled(); self.run_until_stalled();
if self.running_task_count.get() > 0 { if self.running_task_count.get() > 0 {
callback(); callback();
} }
@ -56,17 +58,38 @@ impl TestWithLocalPoolExecutor {
/// will be spawned, so we can test more specific asynchronous scenarios. /// will be spawned, so we can test more specific asynchronous scenarios.
pub fn when_stalled_run_task<Task>(&mut self, task : Task) pub fn when_stalled_run_task<Task>(&mut self, task : Task)
where Task : Future<Output=()> + 'static { where Task : Future<Output=()> + 'static {
self.executor.run_until_stalled(); self.run_until_stalled();
if self.running_task_count.get() > 0 { if self.running_task_count.get() > 0 {
self.run_task(task); 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 { impl Drop for TestWithLocalPoolExecutor {
fn drop(&mut self) { fn drop(&mut self) {
// We should be able to finish test. // We should be able to finish test.
self.executor.run_until_stalled(); self.expect_finished();
assert_eq!(0,self.running_task_count.get(),"Executor dropped before tasks are complete!");
} }
} }

View File

@ -6,6 +6,7 @@
#![feature(bool_to_option)] #![feature(bool_to_option)]
#![feature(cell_update)] #![feature(cell_update)]
#![feature(drain_filter)] #![feature(drain_filter)]
#![feature(exact_size_is_empty)]
#![feature(option_result_contains)] #![feature(option_result_contains)]
#![feature(trait_alias)] #![feature(trait_alias)]
#![recursion_limit="256"] #![recursion_limit="256"]

View File

@ -2,11 +2,17 @@
use crate::prelude::*; use crate::prelude::*;
use crate::model::module::QualifiedName as ModuleQualifiedName;
use crate::double_representation::definition::DefinitionName; 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;
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 enso_protocol::language_server::VisualisationConfiguration;
use flo_stream::MessagePublisher;
use flo_stream::Subscriber;
use std::collections::HashMap; use std::collections::HashMap;
use uuid::Uuid; 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 === // === VisualizationUpdateData ===
// =============================== // ===============================
@ -173,6 +268,8 @@ pub struct ExecutionContext {
stack:RefCell<Vec<LocalCall>>, stack:RefCell<Vec<LocalCall>>,
/// Set of active visualizations. /// Set of active visualizations.
visualizations: RefCell<HashMap<VisualizationId,AttachedVisualization>>, visualizations: RefCell<HashMap<VisualizationId,AttachedVisualization>>,
/// Storage for information about computed values (like their types).
pub computed_value_info_registry: ComputedValueInfoRegistry,
} }
impl ExecutionContext { impl ExecutionContext {
@ -181,18 +278,21 @@ impl ExecutionContext {
let logger = logger.into(); let logger = logger.into();
let stack = default(); let stack = default();
let visualizations = 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. /// Push a new stack item to execution context.
pub fn push(&self, stack_item:LocalCall) { pub fn push(&self, stack_item:LocalCall) {
self.stack.borrow_mut().push(stack_item); 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 /// Pop the last stack item from this context. It returns error when only root call
/// remains. /// remains.
pub fn pop(&self) -> FallibleResult<()> { pub fn pop(&self) -> FallibleResult<()> {
self.stack.borrow_mut().pop().ok_or_else(PopOnEmptyStack)?; self.stack.borrow_mut().pop().ok_or_else(PopOnEmptyStack)?;
self.computed_value_info_registry.clear();
Ok(()) Ok(())
} }
@ -241,4 +341,11 @@ impl ExecutionContext {
Err(InvalidVisualizationId(visualization_id).into()) 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(())
}
} }

View File

@ -3,6 +3,7 @@
use crate::prelude::*; use crate::prelude::*;
use crate::double_representation::definition::DefinitionName; use crate::double_representation::definition::DefinitionName;
use crate::model::execution_context::ComputedValueInfoRegistry;
use crate::model::execution_context::LocalCall; use crate::model::execution_context::LocalCall;
use crate::model::execution_context::Visualization; use crate::model::execution_context::Visualization;
use crate::model::execution_context::VisualizationUpdateData; use crate::model::execution_context::VisualizationUpdateData;
@ -139,16 +140,27 @@ impl ExecutionContext {
self.model.dispatch_visualization_update(visualization_id,data) 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. /// Create a mock which does no call on `language_server` during construction.
#[cfg(test)] #[cfg(test)]
pub fn new_mock pub fn new_mock
( id : model::execution_context::Id ( id : model::execution_context::Id
, path : model::module::Path , path : model::module::Path
, model : model::ExecutionContext , model : model::ExecutionContext
, language_server : language_server::MockClient , language_server : Rc<language_server::Connection>
) -> Self { ) -> Self {
let module_path = Rc::new(path); let module_path = Rc::new(path);
let language_server = language_server::Connection::new_mock_rc(language_server);
let logger = Logger::new("ExecuctionContext mock"); let logger = Logger::new("ExecuctionContext mock");
ExecutionContext {id,model,module_path,language_server,logger} ExecutionContext {id,model,module_path,language_server,logger}
} }
@ -175,51 +187,169 @@ impl Drop for ExecutionContext {
// ============= // =============
#[cfg(test)] #[cfg(test)]
mod test { pub mod tests {
use super::*; use super::*;
use crate::DEFAULT_PROJECT_NAME;
use crate::executor::test_utils::TestWithLocalPoolExecutor; use crate::executor::test_utils::TestWithLocalPoolExecutor;
use crate::model::module::QualifiedName as ModuleQualifiedName; use crate::model::module::QualifiedName as ModuleQualifiedName;
use enso_protocol::language_server::CapabilityRegistration; use enso_protocol::language_server::CapabilityRegistration;
use enso_protocol::language_server::response::CreateExecutionContext;
use json_rpc::expect_call; use json_rpc::expect_call;
use language_server::response;
use utils::test::ExpectTuple; use utils::test::ExpectTuple;
use utils::test::stream::StreamTestExt; use utils::test::stream::StreamTestExt;
trait ModelCustomizer = Fn(&mut model::ExecutionContext, &MockData) + 'static;
#[test] /// Set of data needed to create and operate mock execution context.
fn creating_context() { #[derive(Clone,Derivative)]
let path = Rc::new(model::module::Path::from_mock_module_name("Test")); #[derivative(Debug)]
let context_id = model::execution_context::Id::new_v4(); pub struct MockData {
let root_def = DefinitionName::new_plain("main"); pub module_path : model::module::Path,
let ls_client = language_server::MockClient::default(); 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 = let can_modify =
CapabilityRegistration::create_can_modify_execution_context(context_id); CapabilityRegistration::create_can_modify_execution_context(context_id);
let receives_updates = let receives_updates =
CapabilityRegistration::create_receives_execution_context_updates(context_id); CapabilityRegistration::create_receives_execution_context_updates(context_id);
ls_client.expect.create_execution_context(move || Ok(response::CreateExecutionContext { CreateExecutionContext {context_id,can_modify,receives_updates}
context_id,can_modify,receives_updates, }
}));
let method = language_server::MethodPointer { /// Sets up mock client expectations for context creation and destruction.
file : path.file_path().clone(), pub fn mock_create_destroy_calls(&self, ls:&mut language_server::MockClient) {
defined_on_type : "Test".to_string(), let id = self.context_id;
name : "main".to_string(), 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 { let root_frame = language_server::ExplicitCall {
method_pointer : method, method_pointer : self.main_method_pointer(),
this_argument_expression : None, this_argument_expression : None,
positional_arguments_expressions : vec![] positional_arguments_expressions : vec![]
}; };
let stack_item = language_server::StackItem::ExplicitCall(root_frame); 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.push_to_execution_context(id,stack_item) => Ok(()));
expect_call!(ls_client.destroy_execution_context(context_id) => 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(); ls_client.require_all_calls();
let connection = language_server::Connection::new_mock_rc(ls_client); let connection = language_server::Connection::new_mock_rc(ls_client);
let mut test = TestWithLocalPoolExecutor::set_up(); let mut test = TestWithLocalPoolExecutor::set_up();
test.run_task(async move { 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(); let context = context.await.unwrap();
assert_eq!(context_id , context.id); assert_eq!(context_id , context.id);
assert_eq!(path , context.module_path); assert_eq!(path , context.module_path);
@ -229,25 +359,19 @@ mod test {
#[test] #[test]
fn pushing_stack_item() { fn pushing_stack_item() {
let id = model::execution_context::Id::new_v4(); let mock_data = MockData::new();
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 ls = language_server::MockClient::default(); 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_call_frame = language_server::LocalCall{expression_id};
let expected_stack_item = language_server::StackItem::LocalCall(expected_call_frame); 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.push_to_execution_context(id,expected_stack_item) => Ok(()));
expect_call!(ls.destroy_execution_context(id) => Ok(())); let context = mock_data.create_context(ls);
let context = ExecutionContext::new_mock(id,path,model,ls);
let mut test = TestWithLocalPoolExecutor::set_up(); let mut test = TestWithLocalPoolExecutor::set_up();
test.run_task(async move { test.run_task(async move {
let item = LocalCall { let item = LocalCall {
call : expression_id, call : expression_id,
definition : definition.clone() definition : mock_data.definition_id(),
}; };
context.push(item.clone()).await.unwrap(); context.push(item.clone()).await.unwrap();
assert_eq!((item,), context.model.stack_items().expect_tuple()); assert_eq!((item,), context.model.stack_items().expect_tuple());
@ -256,19 +380,21 @@ mod test {
#[test] #[test]
fn popping_stack_item() { 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 { let item = LocalCall {
call : model::execution_context::ExpressionId::new_v4(), 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); 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(); let mut test = TestWithLocalPoolExecutor::set_up();
test.run_task(async move { test.run_task(async move {
@ -281,26 +407,23 @@ mod test {
#[test] #[test]
fn attaching_visualizations_and_notifying() { fn attaching_visualizations_and_notifying() {
let exe_id = model::execution_context::Id::new_v4(); let mock_data = MockData::new();
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 ls = language_server::MockClient::default(); let ls = language_server::MockClient::default();
let vis = Visualization { let vis = Visualization {
id : model::execution_context::VisualizationId::new_v4(), id : model::execution_context::VisualizationId::new_v4(),
ast_id : model::execution_context::ExpressionId::new_v4(), ast_id : model::execution_context::ExpressionId::new_v4(),
expression : "".to_string(), 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 vis_id = vis.id;
let ast_id = vis.ast_id; let ast_id = vis.ast_id;
let config = vis.config(exe_id); let config = vis.config(exe_id);
expect_call!(ls.attach_visualisation(vis_id,ast_id,config) => Ok(())); 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.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(); let mut test = TestWithLocalPoolExecutor::set_up();
test.run_task(async move { test.run_task(async move {

View File

@ -3,6 +3,7 @@
use crate::prelude::*; use crate::prelude::*;
use crate::controller::graph::NodeTrees; use crate::controller::graph::NodeTrees;
use crate::model::execution_context::ExpressionId;
use crate::model::execution_context::Visualization; use crate::model::execution_context::Visualization;
use crate::model::execution_context::VisualizationId; use crate::model::execution_context::VisualizationId;
use crate::model::execution_context::VisualizationUpdateData; use crate::model::execution_context::VisualizationUpdateData;
@ -172,8 +173,8 @@ impl GraphEditorIntegratedWithController {
frp::extend! {network frp::extend! {network
// Notifications from controller // Notifications from controller
let handle_notification = FencedAction::fence(&network, let handle_notification = FencedAction::fence(&network,
f!((notification:&Option<controller::graph::Notification>) f!((notification:&Option<controller::graph::executed::Notification>)
model.handle_controller_notification(*notification); model.handle_controller_notification(notification);
)); ));
// Changes in Graph Editor // Changes in Graph Editor
@ -192,9 +193,9 @@ impl GraphEditorIntegratedWithController {
fn connect_frp_to_controller_notifications fn connect_frp_to_controller_notifications
( model : &Rc<GraphEditorIntegratedWithControllerModel> ( 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 weak = Rc::downgrade(model);
let handler = process_stream_with_handle(stream,weak,move |notification,_model| { let handler = process_stream_with_handle(stream,weak,move |notification,_model| {
frp_endpoint.emit_event(&Some(notification)); frp_endpoint.emit_event(&Some(notification));
@ -257,6 +258,12 @@ impl GraphEditorIntegratedWithControllerModel {
Ok(()) 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 fn update_node_views
(&self, mut trees:HashMap<double_representation::node::Id,NodeTrees>) -> FallibleResult<()> { (&self, mut trees:HashMap<double_representation::node::Id,NodeTrees>) -> FallibleResult<()> {
let nodes = self.controller.graph.nodes()?; let nodes = self.controller.graph.nodes()?;
@ -392,9 +399,14 @@ impl GraphEditorIntegratedWithControllerModel {
impl GraphEditorIntegratedWithControllerModel { impl GraphEditorIntegratedWithControllerModel {
/// Handle notification received from controller. /// Handle notification received from controller.
pub fn handle_controller_notification 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 { 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 => { other => {
warning!(self.logger,"Handling notification {other:?} is not implemented; \ warning!(self.logger,"Handling notification {other:?} is not implemented; \
performing full invalidation"); performing full invalidation");
@ -590,6 +602,7 @@ impl GraphEditorIntegratedWithControllerModel {
/// Node Editor Panel integrated with Graph Controller. /// Node Editor Panel integrated with Graph Controller.
#[derive(Clone,CloneRef,Debug)] #[derive(Clone,CloneRef,Debug)]
pub struct NodeEditor { pub struct NodeEditor {
logger : Logger,
display_object : display::object::Instance, display_object : display::object::Instance,
#[allow(missing_docs)] #[allow(missing_docs)]
pub graph : Rc<GraphEditorIntegratedWithController>, pub graph : Rc<GraphEditorIntegratedWithController>,
@ -605,11 +618,13 @@ impl NodeEditor {
, controller : controller::ExecutedGraph , controller : controller::ExecutedGraph
, visualization : controller::Visualization) -> FallibleResult<Self> { , visualization : controller::Visualization) -> FallibleResult<Self> {
let logger = Logger::sub(logger,"NodeEditor"); let logger = Logger::sub(logger,"NodeEditor");
info!(logger, "Created.");
let display_object = display::object::Instance::new(&logger); 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); let graph = Rc::new(graph);
display_object.add_child(&graph.model.editor); 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> { async fn init(self) -> FallibleResult<Self> {
@ -623,6 +638,7 @@ impl NodeEditor {
}); });
visualization?; visualization?;
} }
info!(self.logger, "Initialized.");
Ok(self) Ok(self)
} }
} }