mirror of
https://github.com/enso-org/enso.git
synced 2024-12-24 01:11:51 +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.')
|
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).`)
|
||||||
|
@ -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;
|
||||||
|
@ -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");
|
||||||
|
@ -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>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// =================
|
// =================
|
||||||
|
@ -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;
|
||||||
|
@ -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)
|
||||||
};
|
};
|
||||||
|
@ -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();
|
||||||
|
@ -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 = ¬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::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 = ¬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
|
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();
|
||||||
|
@ -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!");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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"]
|
||||||
|
@ -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(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user