mirror of
https://github.com/enso-org/enso.git
synced 2024-12-24 08:12:06 +03:00
Execution Context (https://github.com/enso-org/ide/pull/419)
This PR introduces Executed Graph Controller, which is a Graph Controller with additional info about execution context.
Original commit: 026a2585ae
This commit is contained in:
parent
f8bd0d56e2
commit
7d4529885b
@ -130,14 +130,14 @@ trait API {
|
|||||||
fn destroy_execution_context(&self, context_id:ContextId) -> ();
|
fn destroy_execution_context(&self, context_id:ContextId) -> ();
|
||||||
|
|
||||||
/// Move the execution context to a new location deeper down the stack.
|
/// Move the execution context to a new location deeper down the stack.
|
||||||
#[MethodInput=PushExecutionContextInput,rpc_name="executionContext/push",
|
#[MethodInput=PushToExecutionContextInput,rpc_name="executionContext/push",
|
||||||
result=push_execution_context_result,set_result=set_push_execution_context_result]
|
result=push_to_execution_context_result,set_result=set_push_to_execution_context_result]
|
||||||
fn push_execution_context(&self, context_id:ContextId, stack_item:StackItem) -> ();
|
fn push_to_execution_context(&self, context_id:ContextId, stack_item:StackItem) -> ();
|
||||||
|
|
||||||
/// Move the execution context up the stack.
|
/// Move the execution context up the stack.
|
||||||
#[MethodInput=PopExecutionContextInput,rpc_name="executionContext/pop",
|
#[MethodInput=PopFromExecutionContextInput,rpc_name="executionContext/pop",
|
||||||
result=pop_execution_context_result,set_result=set_pop_execution_context_result]
|
result=pop_from_execution_context_result,set_result=set_pop_from_execution_context_result]
|
||||||
fn pop_execution_context(&self, context_id:ContextId) -> ();
|
fn pop_from_execution_context(&self, context_id:ContextId) -> ();
|
||||||
|
|
||||||
/// Attach a visualisation, potentially preprocessed by some arbitrary Enso code, to a given
|
/// Attach a visualisation, potentially preprocessed by some arbitrary Enso code, to a given
|
||||||
/// node in the program.
|
/// node in the program.
|
||||||
|
@ -52,6 +52,7 @@ pub struct OpenTextFile {
|
|||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
pub struct CreateExecutionContext {
|
pub struct CreateExecutionContext {
|
||||||
|
pub context_id : ContextId,
|
||||||
pub can_modify : CapabilityRegistration,
|
pub can_modify : CapabilityRegistration,
|
||||||
pub receives_updates : CapabilityRegistration
|
pub receives_updates : CapabilityRegistration
|
||||||
}
|
}
|
||||||
|
@ -322,12 +322,13 @@ fn test_execution_context() {
|
|||||||
let method = "executionContext/receivesUpdates".to_string();
|
let method = "executionContext/receivesUpdates".to_string();
|
||||||
let receives_updates = CapabilityRegistration{method,register_options};
|
let receives_updates = CapabilityRegistration{method,register_options};
|
||||||
let create_execution_context_response = response::CreateExecutionContext
|
let create_execution_context_response = response::CreateExecutionContext
|
||||||
{can_modify,receives_updates};
|
{context_id,can_modify,receives_updates};
|
||||||
test_request(
|
test_request(
|
||||||
|client| client.create_execution_context(),
|
|client| client.create_execution_context(),
|
||||||
"executionContext/create",
|
"executionContext/create",
|
||||||
json!({}),
|
json!({}),
|
||||||
json!({
|
json!({
|
||||||
|
"contextId" : "00000000-0000-0000-0000-000000000000",
|
||||||
"canModify" : {
|
"canModify" : {
|
||||||
"method" : "executionContext/canModify",
|
"method" : "executionContext/canModify",
|
||||||
"registerOptions" : {
|
"registerOptions" : {
|
||||||
@ -354,7 +355,7 @@ fn test_execution_context() {
|
|||||||
let local_call = LocalCall {expression_id};
|
let local_call = LocalCall {expression_id};
|
||||||
let stack_item = StackItem::LocalCall(local_call);
|
let stack_item = StackItem::LocalCall(local_call);
|
||||||
test_request(
|
test_request(
|
||||||
|client| client.push_execution_context(context_id,stack_item),
|
|client| client.push_to_execution_context(context_id,stack_item),
|
||||||
"executionContext/push",
|
"executionContext/push",
|
||||||
json!({
|
json!({
|
||||||
"contextId" : "00000000-0000-0000-0000-000000000000",
|
"contextId" : "00000000-0000-0000-0000-000000000000",
|
||||||
@ -367,7 +368,7 @@ fn test_execution_context() {
|
|||||||
()
|
()
|
||||||
);
|
);
|
||||||
test_request(
|
test_request(
|
||||||
|client| client.pop_execution_context(context_id),
|
|client| client.pop_from_execution_context(context_id),
|
||||||
"executionContext/pop",
|
"executionContext/pop",
|
||||||
json!({"contextId":"00000000-0000-0000-0000-000000000000"}),
|
json!({"contextId":"00000000-0000-0000-0000-000000000000"}),
|
||||||
unit_json.clone(),
|
unit_json.clone(),
|
||||||
|
@ -24,6 +24,7 @@ pub struct Path {
|
|||||||
pub root_id:Uuid,
|
pub root_id:Uuid,
|
||||||
/// Path's segments.
|
/// Path's segments.
|
||||||
pub segments:Vec<String>,
|
pub segments:Vec<String>,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Path {
|
impl Display for Path {
|
||||||
@ -34,12 +35,24 @@ impl Display for Path {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Path {
|
impl Path {
|
||||||
|
/// Returns the file name, i.e. the last segment if exists.
|
||||||
|
pub fn file_name(&self) -> Option<&String> {
|
||||||
|
self.segments.last()
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the file extension, i.e. the part of last path segment after the last dot.
|
/// Returns the file extension, i.e. the part of last path segment after the last dot.
|
||||||
/// Returns `None` is there is no segments or no dot in the last segment.
|
/// Returns `None` is there is no segments or no dot in the last segment.
|
||||||
pub fn extension(&self) -> Option<&str> {
|
pub fn extension(&self) -> Option<&str> {
|
||||||
let segment = self.segments.last()?;
|
let name = self.file_name()?;
|
||||||
let last_dot_index = segment.rfind('.')?;
|
let last_dot_index = name.rfind('.')?;
|
||||||
Some(&segment[last_dot_index + 1..])
|
Some(&name[last_dot_index + 1..])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the stem of filename, i.e. part of last segment without extension if present.
|
||||||
|
pub fn file_stem(&self) -> Option<&str> {
|
||||||
|
let name = self.file_name()?;
|
||||||
|
let name_length = name.rfind('.').unwrap_or_else(|| name.len());
|
||||||
|
Some(&name[..name_length])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a new path from given root ID and segments.
|
/// Constructs a new path from given root ID and segments.
|
||||||
|
@ -112,15 +112,18 @@ macro_rules! make_rpc_methods {
|
|||||||
/// Mock used for tests.
|
/// Mock used for tests.
|
||||||
#[derive(Debug,Default)]
|
#[derive(Debug,Default)]
|
||||||
pub struct MockClient {
|
pub struct MockClient {
|
||||||
$($method_result : RefCell<HashMap<($($param_ty),*),Result<$result>>>,)*
|
expect_all_calls : Cell<bool>,
|
||||||
|
$($method_result : RefCell<HashMap<($($param_ty),*),Vec<Result<$result>>>>,)*
|
||||||
}
|
}
|
||||||
|
|
||||||
impl API for MockClient {
|
impl API for MockClient {
|
||||||
$(fn $method(&self $(,$param_name:$param_ty)*)
|
$(fn $method(&self $(,$param_name:$param_ty)*)
|
||||||
-> std::pin::Pin<Box<dyn Future<Output=Result<$result>>>> {
|
-> std::pin::Pin<Box<dyn Future<Output=Result<$result>>>> {
|
||||||
let mut result = self.$method_result.borrow_mut();
|
let mut results = self.$method_result.borrow_mut();
|
||||||
let result = result.remove(&($($param_name),*)).unwrap();
|
let params = ($($param_name),*);
|
||||||
Box::pin(async move { result })
|
let result = results.get_mut(¶ms).and_then(|res| res.pop());
|
||||||
|
let err = format!("Unrecognized call {} with params {:?}",$rpc_name,params);
|
||||||
|
Box::pin(futures::future::ready(result.expect(err.as_str())))
|
||||||
})*
|
})*
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,9 +131,30 @@ macro_rules! make_rpc_methods {
|
|||||||
$(
|
$(
|
||||||
/// Sets `$method`'s result to be returned when it is called.
|
/// Sets `$method`'s result to be returned when it is called.
|
||||||
pub fn $set_result(&self $(,$param_name:$param_ty)*, result:Result<$result>) {
|
pub fn $set_result(&self $(,$param_name:$param_ty)*, result:Result<$result>) {
|
||||||
self.$method_result.borrow_mut().insert(($($param_name),*),result);
|
let mut results = self.$method_result.borrow_mut();
|
||||||
|
let mut entry = results.entry(($($param_name),*));
|
||||||
|
entry.or_default().push(result);
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
|
||||||
|
/// Mark all calls defined by `set_$method_result` as required. If client will be
|
||||||
|
/// dropped without calling the test will fail.
|
||||||
|
pub fn expect_all_calls(&self) {
|
||||||
|
self.expect_all_calls.set(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for MockClient {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if self.expect_all_calls.get() {
|
||||||
|
$(
|
||||||
|
for (params,results) in self.$method_result.borrow().iter() {
|
||||||
|
assert!(results.is_empty(), "Didn't make expected call {} with \
|
||||||
|
parameters {:?}",$rpc_name,params);
|
||||||
}
|
}
|
||||||
)*
|
)*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,16 @@ pub mod project;
|
|||||||
pub mod text;
|
pub mod text;
|
||||||
|
|
||||||
pub use graph::Handle as Graph;
|
pub use graph::Handle as Graph;
|
||||||
|
pub use graph::executed::Handle as ExecutedGraph;
|
||||||
pub use module::Handle as Module;
|
pub use module::Handle as Module;
|
||||||
pub use project::Handle as Project;
|
pub use project::Handle as Project;
|
||||||
pub use text::Handle as Text;
|
pub use text::Handle as Text;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ============
|
||||||
|
// === Path ===
|
||||||
|
// ============
|
||||||
|
|
||||||
|
/// Path to a file on disc, used across all controllers
|
||||||
|
pub type FilePath = enso_protocol::language_server::Path;
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
//!
|
//!
|
||||||
//! This controller provides access to a specific graph. It lives under a module controller, as
|
//! This controller provides access to a specific graph. It lives under a module controller, as
|
||||||
//! each graph belongs to some module.
|
//! each graph belongs to some module.
|
||||||
|
pub mod executed;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
@ -23,7 +24,6 @@ use span_tree::SpanTree;
|
|||||||
use ast::crumbs::InfixCrumb;
|
use ast::crumbs::InfixCrumb;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ==============
|
// ==============
|
||||||
// === Errors ===
|
// === Errors ===
|
||||||
// ==============
|
// ==============
|
||||||
@ -395,8 +395,6 @@ pub struct Handle {
|
|||||||
impl Handle {
|
impl Handle {
|
||||||
|
|
||||||
/// Creates a new controller. Does not check if id is valid.
|
/// Creates a new controller. Does not check if id is valid.
|
||||||
///
|
|
||||||
/// Requires global executor to spawn the events relay task.
|
|
||||||
pub fn new_unchecked(module:Rc<model::Module>, parser:Parser, id:Id) -> Handle {
|
pub fn new_unchecked(module:Rc<model::Module>, parser:Parser, id:Id) -> Handle {
|
||||||
let id = Rc::new(id);
|
let id = Rc::new(id);
|
||||||
let logger = Logger::new(format!("Graph Controller {}", id));
|
let logger = Logger::new(format!("Graph Controller {}", id));
|
||||||
@ -405,8 +403,6 @@ impl Handle {
|
|||||||
|
|
||||||
/// Creates a new graph controller. Given ID should uniquely identify a definition in the
|
/// Creates a new graph controller. Given ID should uniquely identify a definition in the
|
||||||
/// module. Fails if ID cannot be resolved.
|
/// module. Fails if ID cannot be resolved.
|
||||||
///
|
|
||||||
/// Requires global executor to spawn the events relay task.
|
|
||||||
pub fn new(module:Rc<model::Module>, parser:Parser, id:Id) -> FallibleResult<Handle> {
|
pub fn new(module:Rc<model::Module>, parser:Parser, id:Id) -> FallibleResult<Handle> {
|
||||||
let ret = Self::new_unchecked(module,parser,id);
|
let ret = Self::new_unchecked(module,parser,id);
|
||||||
// Get and discard definition info, we are just making sure it can be obtained.
|
// Get and discard definition info, we are just making sure it can be obtained.
|
||||||
@ -751,7 +747,7 @@ mod tests {
|
|||||||
Fut : Future<Output=()> {
|
Fut : Future<Output=()> {
|
||||||
let code = code.as_ref();
|
let code = code.as_ref();
|
||||||
let ls = language_server::Connection::new_mock_rc(default());
|
let ls = language_server::Connection::new_mock_rc(default());
|
||||||
let path = controller::module::Path::new(default(),&["Main"]);
|
let path = controller::module::Path::from_module_name("Main");
|
||||||
let parser = Parser::new_or_panic();
|
let parser = Parser::new_or_panic();
|
||||||
let module = controller::Module::new_mock(path,code,default(),ls,parser).unwrap();
|
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_id = Id::new_single_crumb(DefinitionName::new_plain(function_name.into()));
|
||||||
@ -766,7 +762,7 @@ mod tests {
|
|||||||
Fut : Future<Output=()> {
|
Fut : Future<Output=()> {
|
||||||
let code = code.as_ref();
|
let code = code.as_ref();
|
||||||
let ls = language_server::Connection::new_mock_rc(default());
|
let ls = language_server::Connection::new_mock_rc(default());
|
||||||
let path = controller::module::Path::new(default(),&["Main"]);
|
let path = controller::module::Path::from_module_name("Main");
|
||||||
let parser = Parser::new_or_panic();
|
let parser = Parser::new_or_panic();
|
||||||
let module = controller::Module::new_mock(path, code, default(), ls, parser).unwrap();
|
let module = controller::Module::new_mock(path, code, default(), ls, parser).unwrap();
|
||||||
let graph = module.graph_controller(graph_id).unwrap();
|
let graph = module.graph_controller(graph_id).unwrap();
|
||||||
|
30
gui/src/rust/ide/src/controller/graph/executed.rs
Normal file
30
gui/src/rust/ide/src/controller/graph/executed.rs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
//! A module with Executed Graph Controller.
|
||||||
|
//!
|
||||||
|
//! This controller provides operations on a specific graph with some execution context - these
|
||||||
|
//! operations usually involves retrieving values on nodes: that's are i.e. operations on
|
||||||
|
//! visualisations, retrieving types on ports, etc.
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
use crate::model::synchronized::ExecutionContext;
|
||||||
|
|
||||||
|
/// Handle providing executed graph controller interface.
|
||||||
|
#[derive(Clone,CloneRef,Debug)]
|
||||||
|
pub struct Handle {
|
||||||
|
/// A handle to basic graph operations.
|
||||||
|
pub graph : controller::Graph,
|
||||||
|
execution_ctx : Rc<ExecutionContext>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handle {
|
||||||
|
/// Create handle for given graph and execution context.
|
||||||
|
///
|
||||||
|
/// This takes ownership of execution context which will be shared between all copies of this
|
||||||
|
/// handle; when all copies will be dropped, the execution context will be dropped as well
|
||||||
|
/// (and will then removed from LanguageServer).
|
||||||
|
pub fn new(graph:controller::Graph, execution_ctx:ExecutionContext) -> Self {
|
||||||
|
let execution_ctx = Rc::new(execution_ctx);
|
||||||
|
Handle{graph,execution_ctx}
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO[ao] Here goes the methods requiring ContextId
|
||||||
|
}
|
@ -7,7 +7,9 @@
|
|||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
use crate::controller::FilePath;
|
||||||
use crate::double_representation::text::apply_code_change_to_id_map;
|
use crate::double_representation::text::apply_code_change_to_id_map;
|
||||||
|
use crate::model::synchronized::ExecutionContext;
|
||||||
|
|
||||||
use ast;
|
use ast;
|
||||||
use ast::HasIdMap;
|
use ast::HasIdMap;
|
||||||
@ -15,6 +17,23 @@ use data::text::*;
|
|||||||
use double_representation as dr;
|
use double_representation as dr;
|
||||||
use enso_protocol::language_server;
|
use enso_protocol::language_server;
|
||||||
use parser::Parser;
|
use parser::Parser;
|
||||||
|
use failure::_core::fmt::Formatter;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ==============
|
||||||
|
// === Errors ===
|
||||||
|
// ==============
|
||||||
|
|
||||||
|
/// Error returned when module path is invalid, i.e. cannot obtain module name from it.
|
||||||
|
#[derive(Clone,Copy,Debug,Fail)]
|
||||||
|
#[fail(display="Invalid module path.")]
|
||||||
|
pub struct InvalidModulePath {}
|
||||||
|
|
||||||
|
/// Error returned when graph id invalid.
|
||||||
|
#[derive(Clone,Debug,Fail)]
|
||||||
|
#[fail(display="Invalid graph id: {:?}.",_0)]
|
||||||
|
pub struct InvalidGraphId(controller::graph::Id);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -23,7 +42,55 @@ use parser::Parser;
|
|||||||
// ============
|
// ============
|
||||||
|
|
||||||
/// Path identifying module's file in the Language Server.
|
/// Path identifying module's file in the Language Server.
|
||||||
pub type Path = language_server::Path;
|
#[derive(Clone,Debug,Eq,Hash,PartialEq)]
|
||||||
|
pub struct Path {
|
||||||
|
file_path : FilePath,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Path {
|
||||||
|
/// Create a path from the file path. Returns None if given path is not a valid module file.
|
||||||
|
pub fn from_file_path(file_path:FilePath) -> Option<Self> {
|
||||||
|
let has_proper_ext = file_path.extension() == Some(constants::LANGUAGE_FILE_EXTENSION);
|
||||||
|
let capitalized_name = file_path.file_name()?.chars().next()?.is_uppercase();
|
||||||
|
let is_module = has_proper_ext && capitalized_name;
|
||||||
|
is_module.and_option_from(|| Some(Path{file_path}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the file path.
|
||||||
|
pub fn file_path(&self) -> &FilePath {
|
||||||
|
&self.file_path
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the module name from path.
|
||||||
|
///
|
||||||
|
/// The module name is a filename without extension.
|
||||||
|
pub fn module_name(&self) -> &str {
|
||||||
|
// The file stem existence should be checked during construction.
|
||||||
|
self.file_path.file_stem().unwrap()
|
||||||
|
}
|
||||||
|
/// Create a module path consisting of a single segment, based on a given module name.
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn from_module_name(name:impl Str) -> Self {
|
||||||
|
let name:String = name.into();
|
||||||
|
let file_name = format!("{}.{}",name,constants::LANGUAGE_FILE_EXTENSION);
|
||||||
|
let file_path = FilePath::new(default(),&[file_name]);
|
||||||
|
Self::from_file_path(file_path).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<FilePath> for Path {
|
||||||
|
type Error = InvalidModulePath;
|
||||||
|
|
||||||
|
fn try_from(value:FilePath) -> Result<Self, Self::Error> {
|
||||||
|
Path::from_file_path(value).ok_or(InvalidModulePath{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Path {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
fmt::Display::fmt(&self.file_path, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -59,7 +126,7 @@ impl Handle {
|
|||||||
/// Load or reload module content from file.
|
/// Load or reload module content from file.
|
||||||
pub async fn load_file(&self) -> FallibleResult<()> {
|
pub async fn load_file(&self) -> FallibleResult<()> {
|
||||||
self.logger.info(|| "Loading module file");
|
self.logger.info(|| "Loading module file");
|
||||||
let path = self.path.deref().clone();
|
let path = self.path.file_path().clone();
|
||||||
let content = self.language_server.client.read_file(path).await?.contents;
|
let content = self.language_server.client.read_file(path).await?.contents;
|
||||||
self.logger.info(|| "Parsing code");
|
self.logger.info(|| "Parsing code");
|
||||||
// TODO[ao] We should not fail here when metadata are malformed, but discard them and set
|
// TODO[ao] We should not fail here when metadata are malformed, but discard them and set
|
||||||
@ -73,7 +140,7 @@ impl Handle {
|
|||||||
|
|
||||||
/// Save the module to file.
|
/// Save the module to file.
|
||||||
pub fn save_file(&self) -> impl Future<Output=FallibleResult<()>> {
|
pub fn save_file(&self) -> impl Future<Output=FallibleResult<()>> {
|
||||||
let path = self.path.deref().clone();
|
let path = self.path.file_path().clone();
|
||||||
let ls = self.language_server.clone();
|
let ls = self.language_server.clone();
|
||||||
let content = self.model.source_as_string();
|
let content = self.model.source_as_string();
|
||||||
async move { Ok(ls.client.write_file(path,content?).await?) }
|
async move { Ok(ls.client.write_file(path,content?).await?) }
|
||||||
@ -122,6 +189,20 @@ impl Handle {
|
|||||||
controller::Graph::new(self.model.clone_ref(), self.parser.clone_ref(), id)
|
controller::Graph::new(self.model.clone_ref(), self.parser.clone_ref(), id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a executed graph controller for graph in this module's subtree identified by id.
|
||||||
|
/// The execution context will be rooted at definition of this graph.
|
||||||
|
///
|
||||||
|
/// This function wont check if the definition under id exists.
|
||||||
|
pub async fn executed_graph_controller_unchecked
|
||||||
|
(&self, id:dr::graph::Id) -> FallibleResult<controller::ExecutedGraph> {
|
||||||
|
let definition_name = id.crumbs.last().cloned().ok_or_else(|| InvalidGraphId(id.clone()))?;
|
||||||
|
let graph = self.graph_controller_unchecked(id);
|
||||||
|
let language_server = self.language_server.clone_ref();
|
||||||
|
let path = self.path.clone_ref();
|
||||||
|
let execution_ctx = ExecutionContext::create(language_server,path,definition_name).await?;
|
||||||
|
Ok(controller::ExecutedGraph::new(graph,execution_ctx))
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a graph controller for graph in this module's subtree identified by `id` without
|
/// Returns a graph controller for graph in this module's subtree identified by `id` without
|
||||||
/// checking if the graph exists.
|
/// checking if the graph exists.
|
||||||
pub fn graph_controller_unchecked(&self, id:dr::graph::Id) -> controller::Graph {
|
pub fn graph_controller_unchecked(&self, id:dr::graph::Id) -> controller::Graph {
|
||||||
@ -167,17 +248,28 @@ mod test {
|
|||||||
use ast::BlockLine;
|
use ast::BlockLine;
|
||||||
use ast::Ast;
|
use ast::Ast;
|
||||||
use data::text::Span;
|
use data::text::Span;
|
||||||
use enso_protocol::language_server;
|
|
||||||
use parser::Parser;
|
use parser::Parser;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use wasm_bindgen_test::wasm_bindgen_test;
|
use wasm_bindgen_test::wasm_bindgen_test;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn module_path_conversion() {
|
||||||
|
let path = FilePath::new(default(), &["src","Main.enso"]);
|
||||||
|
assert!(Path::from_file_path(path).is_some());
|
||||||
|
|
||||||
|
let path = FilePath::new(default(), &["src","Main.txt"]);
|
||||||
|
assert!(Path::from_file_path(path).is_none());
|
||||||
|
|
||||||
|
let path = FilePath::new(default(), &["src","main.txt"]);
|
||||||
|
assert!(Path::from_file_path(path).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
#[wasm_bindgen_test]
|
#[wasm_bindgen_test]
|
||||||
fn update_ast_after_text_change() {
|
fn update_ast_after_text_change() {
|
||||||
TestWithLocalPoolExecutor::set_up().run_task(async {
|
TestWithLocalPoolExecutor::set_up().run_task(async {
|
||||||
let ls = language_server::Connection::new_mock_rc(default());
|
let ls = language_server::Connection::new_mock_rc(default());
|
||||||
let parser = Parser::new().unwrap();
|
let parser = Parser::new().unwrap();
|
||||||
let location = Path{root_id:default(),segments:vec!["Test".into()]};
|
let location = Path::from_module_name("Test");
|
||||||
|
|
||||||
let uuid1 = Uuid::new_v4();
|
let uuid1 = Uuid::new_v4();
|
||||||
let uuid2 = Uuid::new_v4();
|
let uuid2 = Uuid::new_v4();
|
||||||
|
@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
use crate::controller::FilePath;
|
||||||
|
|
||||||
use enso_protocol::language_server;
|
use enso_protocol::language_server;
|
||||||
use parser::Parser;
|
use parser::Parser;
|
||||||
|
|
||||||
@ -40,9 +42,8 @@ impl Handle {
|
|||||||
/// 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.
|
||||||
pub async fn text_controller
|
pub async fn text_controller(&self, path:FilePath) -> FallibleResult<controller::Text> {
|
||||||
(&self, path:language_server::Path) -> FallibleResult<controller::Text> {
|
if let Some(path) = controller::module::Path::from_file_path(path.clone()) {
|
||||||
if is_path_to_module(&path) {
|
|
||||||
trace!(self.logger,"Obtaining controller for module {path}");
|
trace!(self.logger,"Obtaining controller for module {path}");
|
||||||
let module = self.module_controller(path).await?;
|
let module = self.module_controller(path).await?;
|
||||||
Ok(controller::Text::new_for_module(module))
|
Ok(controller::Text::new_for_module(module))
|
||||||
@ -77,16 +78,16 @@ impl Handle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if the given path looks like it is referring to module file.
|
|
||||||
fn is_path_to_module(path:&language_server::Path) -> bool {
|
|
||||||
path.extension() == Some(constants::LANGUAGE_FILE_EXTENSION)
|
// ============
|
||||||
}
|
// === Test ===
|
||||||
|
// ============
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use crate::controller::text::FilePath;
|
|
||||||
use crate::executor::test_utils::TestWithLocalPoolExecutor;
|
use crate::executor::test_utils::TestWithLocalPoolExecutor;
|
||||||
|
|
||||||
use language_server::response;
|
use language_server::response;
|
||||||
@ -96,28 +97,20 @@ mod test {
|
|||||||
|
|
||||||
wasm_bindgen_test_configure!(run_in_browser);
|
wasm_bindgen_test_configure!(run_in_browser);
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn is_path_to_module_test() {
|
|
||||||
let path = language_server::Path::new(default(), &["src","Main.enso"]);
|
|
||||||
assert!(is_path_to_module(&path));
|
|
||||||
|
|
||||||
let path = language_server::Path::new(default(), &["src","Main.txt"]);
|
|
||||||
assert_eq!(is_path_to_module(&path), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen_test]
|
#[wasm_bindgen_test]
|
||||||
fn obtain_module_controller() {
|
fn obtain_module_controller() {
|
||||||
let mut test = TestWithLocalPoolExecutor::set_up();
|
let mut test = TestWithLocalPoolExecutor::set_up();
|
||||||
test.run_task(async move {
|
test.run_task(async move {
|
||||||
let path = ModulePath{root_id:default(),segments:vec!["TestLocation".into()]};
|
let path = ModulePath::from_module_name("TestModule");
|
||||||
let another_path = ModulePath{root_id:default(),segments:vec!["TestLocation2".into()]};
|
let another_path = ModulePath::from_module_name("TestModule2");
|
||||||
|
|
||||||
let client = language_server::MockClient::default();
|
let client = language_server::MockClient::default();
|
||||||
let contents = "2+2".to_string();
|
let contents = "2+2".to_string();
|
||||||
client.set_file_read_result(path.clone(),Ok(response::Read{contents}));
|
let file_path = path.file_path().clone();
|
||||||
|
client.set_file_read_result(file_path,Ok(response::Read{contents}));
|
||||||
|
let file_path = another_path.file_path().clone();
|
||||||
let contents = "2 + 2".to_string();
|
let contents = "2 + 2".to_string();
|
||||||
client.set_file_read_result(another_path.clone(),Ok(response::Read{contents}));
|
client.set_file_read_result(file_path,Ok(response::Read{contents}));
|
||||||
let connection = language_server::Connection::new_mock(client);
|
let connection = language_server::Connection::new_mock(client);
|
||||||
let project = controller::Project::new(connection);
|
let project = controller::Project::new(connection);
|
||||||
let module = project.module_controller(path.clone()).await.unwrap();
|
let module = project.module_controller(path.clone()).await.unwrap();
|
||||||
@ -136,8 +129,8 @@ mod test {
|
|||||||
let connection = language_server::Connection::new_mock(default());
|
let connection = language_server::Connection::new_mock(default());
|
||||||
let project_ctrl = controller::Project::new(connection);
|
let project_ctrl = controller::Project::new(connection);
|
||||||
let root_id = default();
|
let root_id = default();
|
||||||
let path = FilePath{root_id,segments:vec!["TestPath".into()]};
|
let path = FilePath::new(root_id,&["TestPath"]);
|
||||||
let another_path = FilePath{root_id,segments:vec!["TestPath2".into()]};
|
let another_path = FilePath::new(root_id,&["TestPath2"]);
|
||||||
|
|
||||||
let text_ctrl = project_ctrl.text_controller(path.clone()).await.unwrap();
|
let text_ctrl = project_ctrl.text_controller(path.clone()).await.unwrap();
|
||||||
let another_ctrl = project_ctrl.text_controller(another_path.clone()).await.unwrap();
|
let another_ctrl = project_ctrl.text_controller(another_path.clone()).await.unwrap();
|
||||||
@ -156,7 +149,7 @@ mod test {
|
|||||||
let mut test = TestWithLocalPoolExecutor::set_up();
|
let mut test = TestWithLocalPoolExecutor::set_up();
|
||||||
test.run_task(async move {
|
test.run_task(async move {
|
||||||
let file_name = format!("test.{}",constants::LANGUAGE_FILE_EXTENSION);
|
let file_name = format!("test.{}",constants::LANGUAGE_FILE_EXTENSION);
|
||||||
let path = ModulePath{root_id:default(),segments:vec![file_name]};
|
let path = FilePath::new(default(),&[file_name]);
|
||||||
let contents = "2 + 2".to_string();
|
let contents = "2 + 2".to_string();
|
||||||
|
|
||||||
let client = language_server::MockClient::default();
|
let client = language_server::MockClient::default();
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
use crate::controller::FilePath;
|
||||||
use crate::notification;
|
use crate::notification;
|
||||||
|
|
||||||
use data::text::TextChange;
|
use data::text::TextChange;
|
||||||
@ -15,15 +16,6 @@ use std::pin::Pin;
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ============
|
|
||||||
// === Path ===
|
|
||||||
// ============
|
|
||||||
|
|
||||||
/// Path to a file on disc.
|
|
||||||
pub type FilePath = language_server::Path;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// =======================
|
// =======================
|
||||||
// === Text Controller ===
|
// === Text Controller ===
|
||||||
// =======================
|
// =======================
|
||||||
@ -68,7 +60,7 @@ impl Handle {
|
|||||||
pub fn file_path(&self) -> &FilePath {
|
pub fn file_path(&self) -> &FilePath {
|
||||||
match &self.file {
|
match &self.file {
|
||||||
FileHandle::PlainText{path,..} => &*path,
|
FileHandle::PlainText{path,..} => &*path,
|
||||||
FileHandle::Module{controller} => &*controller.path,
|
FileHandle::Module{controller} => controller.path.file_path()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,7 +154,7 @@ mod test {
|
|||||||
let mut test = TestWithLocalPoolExecutor::set_up();
|
let mut test = TestWithLocalPoolExecutor::set_up();
|
||||||
test.run_task(async move {
|
test.run_task(async move {
|
||||||
let ls = language_server::Connection::new_mock_rc(default());
|
let ls = language_server::Connection::new_mock_rc(default());
|
||||||
let path = FilePath{root_id:default(),segments:vec!["test".into()]};
|
let path = controller::module::Path::from_module_name("Test");
|
||||||
let parser = Parser::new().unwrap();
|
let parser = Parser::new().unwrap();
|
||||||
let module_res = controller::Module::new_mock(path,"main = 2+2",default(),ls,parser);
|
let module_res = controller::Module::new_mock(path,"main = 2+2",default(),ls,parser);
|
||||||
let module = module_res.unwrap();
|
let module = module_res.unwrap();
|
||||||
|
@ -13,6 +13,8 @@ use ast::known;
|
|||||||
use utils::fail::FallibleResult;
|
use utils::fail::FallibleResult;
|
||||||
use crate::double_representation::connection::Connection;
|
use crate::double_representation::connection::Connection;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// Graph uses the same `Id` as the definition which introduces the graph.
|
/// Graph uses the same `Id` as the definition which introduces the graph.
|
||||||
pub type Id = double_representation::definition::Id;
|
pub type Id = double_representation::definition::Id;
|
||||||
|
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
//! The module with structures describing models shared between different controllers.
|
//! The module with structures describing models shared between different controllers.
|
||||||
|
|
||||||
|
pub mod execution_context;
|
||||||
pub mod module;
|
pub mod module;
|
||||||
|
pub mod synchronized;
|
||||||
|
|
||||||
|
pub use execution_context::ExecutionContext;
|
||||||
pub use module::Module;
|
pub use module::Module;
|
||||||
|
93
gui/src/rust/ide/src/model/execution_context.rs
Normal file
93
gui/src/rust/ide/src/model/execution_context.rs
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
//! This module consists of all structures describing Execution Context.
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
use crate::double_representation::definition::DefinitionName;
|
||||||
|
|
||||||
|
use enso_protocol::language_server;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ==============
|
||||||
|
// === Errors ===
|
||||||
|
// ==============
|
||||||
|
|
||||||
|
/// Error then trying to pop stack item on ExecutionContext when there only root call remains.
|
||||||
|
#[derive(Clone,Copy,Debug,Fail)]
|
||||||
|
#[fail(display="Tried to pop an entry point")]
|
||||||
|
pub struct PopOnEmptyStack {}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// =================
|
||||||
|
// === StackItem ===
|
||||||
|
// =================
|
||||||
|
|
||||||
|
/// An identifier of called definition in module.
|
||||||
|
pub type DefinitionId = crate::double_representation::definition::Id;
|
||||||
|
/// An identifier of expression.
|
||||||
|
pub type ExpressionId = ast::Id;
|
||||||
|
|
||||||
|
/// A specific function call occurring within another function's definition body.
|
||||||
|
///
|
||||||
|
/// This is a single item in ExecutionContext stack.
|
||||||
|
#[derive(Clone,Debug,Eq,PartialEq)]
|
||||||
|
pub struct LocalCall {
|
||||||
|
/// An expression being a call.
|
||||||
|
pub call : ExpressionId,
|
||||||
|
/// A definition of function called in `call` expression.
|
||||||
|
pub definition : DefinitionId,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An identifier of ExecutionContext.
|
||||||
|
pub type Id = language_server::ContextId;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// =============
|
||||||
|
// === Model ===
|
||||||
|
// =============
|
||||||
|
|
||||||
|
/// Execution Context Model.
|
||||||
|
///
|
||||||
|
/// The execution context consists of the root call (which is a direct call of some function
|
||||||
|
/// definition) and stack of function calls (see `StackItem` definition and docs).
|
||||||
|
///
|
||||||
|
/// It implements internal mutability pattern, so the state may be shared between different
|
||||||
|
/// controllers.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ExecutionContext {
|
||||||
|
/// A name of definition which is a root call of this context.
|
||||||
|
pub entry_point : DefinitionName,
|
||||||
|
stack : RefCell<Vec<LocalCall>>,
|
||||||
|
//TODO[ao] I think we can put here info about visualisation set as well.
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExecutionContext {
|
||||||
|
/// Create new execution context
|
||||||
|
pub fn new(entry_point:DefinitionName) -> Self {
|
||||||
|
let stack = default();
|
||||||
|
Self {entry_point,stack}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Push a new stack item to execution context.
|
||||||
|
pub fn push(&self, stack_item:LocalCall) {
|
||||||
|
self.stack.borrow_mut().push(stack_item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pop the last stack item from this context. It returns error when only root call
|
||||||
|
/// remains.
|
||||||
|
pub fn pop(&self) -> FallibleResult<()> {
|
||||||
|
self.stack.borrow_mut().pop().ok_or(PopOnEmptyStack{})?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get an iterator over stack items.
|
||||||
|
///
|
||||||
|
/// Because this struct implements _internal mutability pattern_, the stack can actually change
|
||||||
|
/// during iteration. It should not panic, however might give an unpredictable result.
|
||||||
|
pub fn stack_items<'a>(&'a self) -> impl Iterator<Item=LocalCall> + 'a {
|
||||||
|
let stack_size = self.stack.borrow().len();
|
||||||
|
(0..stack_size).filter_map(move |i| self.stack.borrow().get(i).cloned())
|
||||||
|
}
|
||||||
|
}
|
@ -168,7 +168,7 @@ mod test {
|
|||||||
let state = Rc::new(model::Module::new(ast.try_into().unwrap(),default()));
|
let state = Rc::new(model::Module::new(ast.try_into().unwrap(),default()));
|
||||||
let registry = Rc::new(Registry::default());
|
let registry = Rc::new(Registry::default());
|
||||||
let expected = state.clone_ref();
|
let expected = state.clone_ref();
|
||||||
let path = ModulePath::new(default(),&["test"]);
|
let path = ModulePath::from_module_name("Test");
|
||||||
|
|
||||||
let loader = async move { Ok(state) };
|
let loader = async move { Ok(state) };
|
||||||
let module = registry.get_or_load(path.clone(),loader).await.unwrap();
|
let module = registry.get_or_load(path.clone(),loader).await.unwrap();
|
||||||
@ -188,7 +188,7 @@ mod test {
|
|||||||
let state2 = state1.clone_ref();
|
let state2 = state1.clone_ref();
|
||||||
let registry1 = Rc::new(Registry::default());
|
let registry1 = Rc::new(Registry::default());
|
||||||
let registry2 = registry1.clone_ref();
|
let registry2 = registry1.clone_ref();
|
||||||
let path1 = ModulePath::new(default(),&["test"]);
|
let path1 = ModulePath::from_module_name("Test");
|
||||||
let path2 = path1.clone();
|
let path2 = path1.clone();
|
||||||
|
|
||||||
let (loaded_send, loaded_recv) = futures::channel::oneshot::channel::<()>();
|
let (loaded_send, loaded_recv) = futures::channel::oneshot::channel::<()>();
|
||||||
|
6
gui/src/rust/ide/src/model/synchronized.rs
Normal file
6
gui/src/rust/ide/src/model/synchronized.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
//! This module contains synchronising wrappers for models whose state are a reflection of
|
||||||
|
//! Language Server state, e.g. modules, execution contexts etc. These wrappers synchronize both
|
||||||
|
//! states by notifying Language Server of every change and listening on LanguageServer.
|
||||||
|
pub mod execution_context;
|
||||||
|
|
||||||
|
pub use execution_context::ExecutionContext;
|
222
gui/src/rust/ide/src/model/synchronized/execution_context.rs
Normal file
222
gui/src/rust/ide/src/model/synchronized/execution_context.rs
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
//! A ExecutionContext model which is synchronized with LanguageServer state.
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
use crate::double_representation::definition::DefinitionName;
|
||||||
|
use crate::model::execution_context::LocalCall;
|
||||||
|
|
||||||
|
use enso_protocol::language_server;
|
||||||
|
use json_rpc::error::RpcError;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ==========================
|
||||||
|
// === Synchronized Model ===
|
||||||
|
// ==========================
|
||||||
|
|
||||||
|
/// An ExecutionContext model synchronized with LanguageServer. It will be automatically removed
|
||||||
|
/// from LS once dropped.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ExecutionContext {
|
||||||
|
id : model::execution_context::Id,
|
||||||
|
model : model::ExecutionContext,
|
||||||
|
module_path : Rc<controller::module::Path>,
|
||||||
|
language_server : Rc<language_server::Connection>,
|
||||||
|
logger : Logger,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExecutionContext {
|
||||||
|
/// Create new ExecutionContext. It will be created in LanguageServer and the ExplicitCall
|
||||||
|
/// stack frame will be pushed.
|
||||||
|
pub async fn create
|
||||||
|
( language_server : Rc<language_server::Connection>
|
||||||
|
, module_path : Rc<controller::module::Path>
|
||||||
|
, root_definition : DefinitionName
|
||||||
|
) -> FallibleResult<Self> {
|
||||||
|
let logger = Logger::new("ExecutionContext");
|
||||||
|
let model = model::ExecutionContext::new(root_definition);
|
||||||
|
trace!(logger,"Creating Execution Context.");
|
||||||
|
let id = language_server.client.create_execution_context().await?.context_id;
|
||||||
|
trace!(logger,"Execution Context created. Id:{id}");
|
||||||
|
let this = Self {id,module_path,model,language_server,logger};
|
||||||
|
this.push_root_frame().await?;
|
||||||
|
trace!(this.logger,"Pushed root frame");
|
||||||
|
Ok(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_root_frame(&self) -> impl Future<Output=FallibleResult<()>> {
|
||||||
|
let method_pointer = language_server::MethodPointer {
|
||||||
|
file : self.module_path.file_path().clone(),
|
||||||
|
defined_on_type : self.module_path.module_name().to_string(),
|
||||||
|
name : self.model.entry_point.name.item.clone(),
|
||||||
|
};
|
||||||
|
let this_argument_expression = default();
|
||||||
|
let positional_arguments_expressions = default();
|
||||||
|
|
||||||
|
let call = language_server::ExplicitCall {method_pointer,this_argument_expression,
|
||||||
|
positional_arguments_expressions};
|
||||||
|
let frame = language_server::StackItem::ExplicitCall(call);
|
||||||
|
let result = self.language_server.push_to_execution_context(self.id,frame);
|
||||||
|
result.map(|res| res.map_err(|err| err.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Push a new stack item to execution context.
|
||||||
|
pub fn push(&self, stack_item: LocalCall) -> impl Future<Output=Result<(),RpcError>> {
|
||||||
|
let expression_id = stack_item.call;
|
||||||
|
let call = language_server::LocalCall{expression_id};
|
||||||
|
let frame = language_server::StackItem::LocalCall(call);
|
||||||
|
self.model.push(stack_item);
|
||||||
|
self.language_server.push_to_execution_context(self.id,frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pop the last stack item from this context. It returns error when only root call
|
||||||
|
/// remains.
|
||||||
|
pub async fn pop(&self) -> FallibleResult<()> {
|
||||||
|
self.model.pop()?;
|
||||||
|
self.language_server.pop_from_execution_context(self.id).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a mock which does no call on `language_server` during construction.
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn new_mock
|
||||||
|
( id : model::execution_context::Id
|
||||||
|
, path : controller::module::Path
|
||||||
|
, model : model::ExecutionContext
|
||||||
|
, language_server : language_server::MockClient
|
||||||
|
) -> Self {
|
||||||
|
let module_path = Rc::new(path);
|
||||||
|
let language_server = language_server::Connection::new_mock_rc(language_server);
|
||||||
|
let logger = Logger::new("ExecuctionContext mock");
|
||||||
|
ExecutionContext {id,model,module_path,language_server,logger}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for ExecutionContext {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let id = self.id;
|
||||||
|
let ls = self.language_server.clone_ref();
|
||||||
|
let logger = self.logger.clone_ref();
|
||||||
|
executor::global::spawn(async move {
|
||||||
|
let result = ls.client.destroy_execution_context(id).await;
|
||||||
|
if result.is_err() {
|
||||||
|
error!(logger,"Error when destroying Execution Context: {result:?}.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// =============
|
||||||
|
// === Tests ===
|
||||||
|
// =============
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use crate::executor::test_utils::TestWithLocalPoolExecutor;
|
||||||
|
|
||||||
|
use language_server::response;
|
||||||
|
use utils::test::ExpectTuple;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn creating_context() {
|
||||||
|
let path = Rc::new(controller::module::Path::from_module_name("Test"));
|
||||||
|
let context_id = model::execution_context::Id::new_v4();
|
||||||
|
let root_def = DefinitionName::new_plain("main");
|
||||||
|
let ls_client = language_server::MockClient::default();
|
||||||
|
ls_client.set_create_execution_context_result(Ok(response::CreateExecutionContext {
|
||||||
|
context_id,
|
||||||
|
can_modify : create_capability("executionContext/canModify",context_id),
|
||||||
|
receives_updates : create_capability("executionContext/receivesUpdates",context_id),
|
||||||
|
}));
|
||||||
|
let expected_method = language_server::MethodPointer {
|
||||||
|
file : path.file_path().clone(),
|
||||||
|
defined_on_type : "Test".to_string(),
|
||||||
|
name : "main".to_string(),
|
||||||
|
};
|
||||||
|
let expected_root_frame = language_server::ExplicitCall {
|
||||||
|
method_pointer : expected_method,
|
||||||
|
this_argument_expression : None,
|
||||||
|
positional_arguments_expressions : vec![]
|
||||||
|
};
|
||||||
|
let expected_stack_item = language_server::StackItem::ExplicitCall(expected_root_frame);
|
||||||
|
ls_client.set_push_to_execution_context_result(context_id,expected_stack_item,Ok(()));
|
||||||
|
ls_client.set_destroy_execution_context_result(context_id,Ok(()));
|
||||||
|
ls_client.expect_all_calls();
|
||||||
|
let connection = language_server::Connection::new_mock_rc(ls_client);
|
||||||
|
|
||||||
|
let mut test = TestWithLocalPoolExecutor::set_up();
|
||||||
|
test.run_task(async move {
|
||||||
|
let context = ExecutionContext::create(connection,path.clone(),root_def).await.unwrap();
|
||||||
|
assert_eq!(context_id , context.id);
|
||||||
|
assert_eq!(path , context.module_path);
|
||||||
|
assert_eq!(Vec::<LocalCall>::new(), context.model.stack_items().collect_vec());
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_capability
|
||||||
|
(method:impl Str, context_id:model::execution_context::Id)
|
||||||
|
-> language_server::CapabilityRegistration {
|
||||||
|
language_server::CapabilityRegistration {
|
||||||
|
method : method.into(),
|
||||||
|
register_options : language_server::RegisterOptions::ExecutionContextId {context_id},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pushing_stack_item() {
|
||||||
|
let id = model::execution_context::Id::new_v4();
|
||||||
|
let definition = model::execution_context::DefinitionId::new_plain_name("foo");
|
||||||
|
let expression_id = model::execution_context::ExpressionId::new_v4();
|
||||||
|
let path = controller::module::Path::from_module_name("Test");
|
||||||
|
let root_def = DefinitionName::new_plain("main");
|
||||||
|
let model = model::ExecutionContext::new(root_def);
|
||||||
|
let ls = language_server::MockClient::default();
|
||||||
|
let expected_call_frame = language_server::LocalCall{expression_id};
|
||||||
|
let expected_stack_item = language_server::StackItem::LocalCall(expected_call_frame);
|
||||||
|
|
||||||
|
ls.set_push_to_execution_context_result(id,expected_stack_item,Ok(()));
|
||||||
|
ls.set_destroy_execution_context_result(id,Ok(()));
|
||||||
|
let context = ExecutionContext::new_mock(id,path.clone(),model,ls);
|
||||||
|
|
||||||
|
let mut test = TestWithLocalPoolExecutor::set_up();
|
||||||
|
test.run_task(async move {
|
||||||
|
let item = LocalCall {
|
||||||
|
call : expression_id,
|
||||||
|
definition : definition.clone()
|
||||||
|
};
|
||||||
|
context.push(item.clone()).await.unwrap();
|
||||||
|
assert_eq!((item,), context.model.stack_items().expect_tuple());
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn popping_stack_item() {
|
||||||
|
let id = model::execution_context::Id::new_v4();
|
||||||
|
let item = LocalCall {
|
||||||
|
call : model::execution_context::ExpressionId::new_v4(),
|
||||||
|
definition : model::execution_context::DefinitionId::new_plain_name("foo"),
|
||||||
|
};
|
||||||
|
let path = controller::module::Path::from_module_name("Test");
|
||||||
|
let root_def = DefinitionName::new_plain("main");
|
||||||
|
let ls = language_server::MockClient::default();
|
||||||
|
let model = model::ExecutionContext::new(root_def);
|
||||||
|
ls.set_pop_from_execution_context_result(id,Ok(()));
|
||||||
|
ls.set_destroy_execution_context_result(id,Ok(()));
|
||||||
|
model.push(item);
|
||||||
|
let context = ExecutionContext::new_mock(id,path.clone(),model,ls);
|
||||||
|
|
||||||
|
let mut test = TestWithLocalPoolExecutor::set_up();
|
||||||
|
test.run_task(async move {
|
||||||
|
context.pop().await.unwrap();
|
||||||
|
assert_eq!(Vec::<LocalCall>::new(), context.model.stack_items().collect_vec());
|
||||||
|
// Pop on empty stack.
|
||||||
|
assert!(context.pop().await.is_err());
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -91,15 +91,15 @@ impl ViewLayout {
|
|||||||
( logger : &Logger
|
( logger : &Logger
|
||||||
, kb_actions : &mut keyboard::Actions
|
, kb_actions : &mut keyboard::Actions
|
||||||
, application : &Application
|
, application : &Application
|
||||||
, text_controller : controller::text::Handle
|
, text_controller : controller::Text
|
||||||
, graph_controller : controller::graph::Handle
|
, graph_controller : controller::ExecutedGraph
|
||||||
, fonts : &mut FontRegistry
|
, fonts : &mut FontRegistry
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let logger = logger.sub("ViewLayout");
|
let logger = logger.sub("ViewLayout");
|
||||||
let world = &application.display;
|
let world = &application.display;
|
||||||
let text_editor = TextEditor::new(&logger,world,text_controller,kb_actions,fonts);
|
let text_editor = TextEditor::new(&logger,world,text_controller,kb_actions,fonts);
|
||||||
let node_editor = NodeEditor::new(&logger,application,graph_controller.clone());
|
let node_editor = NodeEditor::new(&logger,application,graph_controller.clone_ref());
|
||||||
let node_searcher = NodeSearcher::new(world,&logger,graph_controller,fonts);
|
let node_searcher = NodeSearcher::new(world,&logger,graph_controller.graph.clone_ref(),fonts);
|
||||||
world.add_child(&text_editor.display_object());
|
world.add_child(&text_editor.display_object());
|
||||||
world.add_child(&node_editor);
|
world.add_child(&node_editor);
|
||||||
world.add_child(&node_searcher);
|
world.add_child(&node_searcher);
|
||||||
|
@ -27,7 +27,7 @@ use weak_table::weak_value_hash_map::Entry::{Occupied, Vacant};
|
|||||||
pub struct GraphEditorIntegration {
|
pub struct GraphEditorIntegration {
|
||||||
pub logger : Logger,
|
pub logger : Logger,
|
||||||
pub editor : GraphEditor,
|
pub editor : GraphEditor,
|
||||||
pub controller : controller::Graph,
|
pub controller : controller::ExecutedGraph,
|
||||||
id_to_node : RefCell<WeakValueHashMap<ast::Id,WeakNode>>,
|
id_to_node : RefCell<WeakValueHashMap<ast::Id,WeakNode>>,
|
||||||
node_to_id : RefCell<WeakKeyHashMap<WeakNode,ast::Id>>,
|
node_to_id : RefCell<WeakKeyHashMap<WeakNode,ast::Id>>,
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ pub struct GraphEditorIntegration {
|
|||||||
|
|
||||||
impl GraphEditorIntegration {
|
impl GraphEditorIntegration {
|
||||||
/// Constructor. It creates GraphEditor panel and connect it with given controller handle.
|
/// Constructor. It creates GraphEditor panel and connect it with given controller handle.
|
||||||
pub fn new(logger:Logger, app:&Application, controller:controller::Graph) -> Rc<Self> {
|
pub fn new(logger:Logger, app:&Application, controller:controller::ExecutedGraph) -> Rc<Self> {
|
||||||
let editor = app.views.new::<GraphEditor>();
|
let editor = app.views.new::<GraphEditor>();
|
||||||
let id_to_node = default();
|
let id_to_node = default();
|
||||||
let node_to_id = default();
|
let node_to_id = default();
|
||||||
@ -43,12 +43,15 @@ impl GraphEditorIntegration {
|
|||||||
Self::setup_controller_event_handling(&this);
|
Self::setup_controller_event_handling(&this);
|
||||||
Self::setup_keyboard_event_handling(&this);
|
Self::setup_keyboard_event_handling(&this);
|
||||||
Self::setup_mouse_event_handling(&this);
|
Self::setup_mouse_event_handling(&this);
|
||||||
|
if let Err(err) = this.invalidate_graph() {
|
||||||
|
error!(this.logger,"Error while initializing graph display: {err}");
|
||||||
|
}
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reloads whole displayed content to be up to date with module state.
|
/// Reloads whole displayed content to be up to date with module state.
|
||||||
pub fn invalidate_graph(&self) -> FallibleResult<()> {
|
pub fn invalidate_graph(&self) -> FallibleResult<()> {
|
||||||
let nodes = self.controller.nodes()?;
|
let nodes = self.controller.graph.nodes()?;
|
||||||
let ids = nodes.iter().map(|node| node.info.id() ).collect();
|
let ids = nodes.iter().map(|node| node.info.id() ).collect();
|
||||||
self.retain_ids(&ids);
|
self.retain_ids(&ids);
|
||||||
for (i,node_info) in nodes.iter().enumerate() {
|
for (i,node_info) in nodes.iter().enumerate() {
|
||||||
@ -71,7 +74,7 @@ impl GraphEditorIntegration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn setup_controller_event_handling(this:&Rc<Self>) {
|
fn setup_controller_event_handling(this:&Rc<Self>) {
|
||||||
let stream = this.controller.subscribe();
|
let stream = this.controller.graph.subscribe();
|
||||||
let weak = Rc::downgrade(this);
|
let weak = Rc::downgrade(this);
|
||||||
let handler = process_stream_with_handle(stream,weak,move |notification,this| {
|
let handler = process_stream_with_handle(stream,weak,move |notification,this| {
|
||||||
let result = match notification {
|
let result = match notification {
|
||||||
@ -97,7 +100,7 @@ impl GraphEditorIntegration {
|
|||||||
this.editor.nodes.selected.for_each(|node_id| {
|
this.editor.nodes.selected.for_each(|node_id| {
|
||||||
let id = this.node_to_id.borrow().get(&node_id.0).cloned(); // FIXME .0
|
let id = this.node_to_id.borrow().get(&node_id.0).cloned(); // FIXME .0
|
||||||
if let Some(id) = id {
|
if let Some(id) = id {
|
||||||
if let Err(err) = this.controller.remove_node(id) {
|
if let Err(err) = this.controller.graph.remove_node(id) {
|
||||||
this.logger.error(|| format!("ERR: {:?}", err));
|
this.logger.error(|| format!("ERR: {:?}", err));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -119,7 +122,7 @@ impl GraphEditorIntegration {
|
|||||||
if let Some((node_pos,this)) = node_pos.and_then(|n| this.map(|t| (n,t))) {
|
if let Some((node_pos,this)) = node_pos.and_then(|n| this.map(|t| (n,t))) {
|
||||||
let id = this.node_to_id.borrow().get(&node_id.0).cloned(); // FIXME .0
|
let id = this.node_to_id.borrow().get(&node_id.0).cloned(); // FIXME .0
|
||||||
if let Some(id) = id {
|
if let Some(id) = id {
|
||||||
this.controller.module.with_node_metadata(id, |md| {
|
this.controller.graph.module.with_node_metadata(id, |md| {
|
||||||
md.position = Some(model::module::Position::new(node_pos.x, node_pos.y));
|
md.position = Some(model::module::Position::new(node_pos.x, node_pos.y));
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -153,12 +156,12 @@ impl GraphEditorIntegration {
|
|||||||
pub struct NodeEditor {
|
pub struct NodeEditor {
|
||||||
display_object : display::object::Instance,
|
display_object : display::object::Instance,
|
||||||
graph : Rc<GraphEditorIntegration>,
|
graph : Rc<GraphEditorIntegration>,
|
||||||
controller : controller::graph::Handle,
|
controller : controller::ExecutedGraph,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NodeEditor {
|
impl NodeEditor {
|
||||||
/// Create Node Editor Panel.
|
/// Create Node Editor Panel.
|
||||||
pub fn new(logger:&Logger, app:&Application, controller:controller::graph::Handle) -> Self {
|
pub fn new(logger:&Logger, app:&Application, controller:controller::ExecutedGraph) -> Self {
|
||||||
let logger = logger.sub("NodeEditor");
|
let logger = logger.sub("NodeEditor");
|
||||||
let graph = GraphEditorIntegration::new(logger,app,controller.clone_ref());
|
let graph = GraphEditorIntegration::new(logger,app,controller.clone_ref());
|
||||||
let display_object = display::object::Instance::new(&graph.logger);
|
let display_object = display::object::Instance::new(&graph.logger);
|
||||||
|
@ -73,12 +73,13 @@ impl ProjectView {
|
|||||||
pub async fn new(logger:&Logger, controller:controller::Project)
|
pub async fn new(logger:&Logger, controller:controller::Project)
|
||||||
-> FallibleResult<Self> {
|
-> FallibleResult<Self> {
|
||||||
let root_id = controller.language_server_rpc.content_root();
|
let root_id = controller.language_server_rpc.content_root();
|
||||||
let path = controller::module::Path::new(root_id,&INITIAL_FILE_PATH);
|
let path = controller::FilePath::new(root_id,&INITIAL_FILE_PATH);
|
||||||
let text_controller = controller.text_controller(path.clone()).await?;
|
let text_controller = controller.text_controller(path.clone()).await?;
|
||||||
let main_name = DefinitionName::new_plain(MAIN_DEFINITION_NAME);
|
let main_name = DefinitionName::new_plain(MAIN_DEFINITION_NAME);
|
||||||
let graph_id = controller::graph::Id::new_single_crumb(main_name);
|
let graph_id = controller::graph::Id::new_single_crumb(main_name);
|
||||||
let module_controller = controller.module_controller(path).await?;
|
let module_controller = controller.module_controller(path.try_into()?).await?;
|
||||||
let graph_controller = module_controller.graph_controller_unchecked(graph_id);
|
let graph_controller = module_controller.executed_graph_controller_unchecked(graph_id);
|
||||||
|
let graph_controller = graph_controller.await?;
|
||||||
let application = Application::new(&web::get_html_element_by_id("root").unwrap());
|
let application = Application::new(&web::get_html_element_by_id("root").unwrap());
|
||||||
let _world = &application.display;
|
let _world = &application.display;
|
||||||
// graph::register_shapes(&world);
|
// graph::register_shapes(&world);
|
||||||
@ -88,10 +89,10 @@ impl ProjectView {
|
|||||||
let mut keyboard_actions = keyboard::Actions::new(&keyboard);
|
let mut keyboard_actions = keyboard::Actions::new(&keyboard);
|
||||||
let resize_callback = None;
|
let resize_callback = None;
|
||||||
let mut fonts = FontRegistry::new();
|
let mut fonts = FontRegistry::new();
|
||||||
let layout = ViewLayout::new
|
let layout = ViewLayout::new(&logger,&mut keyboard_actions,&application,
|
||||||
(&logger,&mut keyboard_actions,&application,text_controller,graph_controller,&mut fonts);
|
text_controller,graph_controller,&mut fonts);
|
||||||
let data = ProjectViewData
|
let data = ProjectViewData {application,layout,resize_callback,controller,keyboard,
|
||||||
{application,layout,resize_callback,controller,keyboard,keyboard_bindings,keyboard_actions};
|
keyboard_bindings,keyboard_actions};
|
||||||
Ok(Self::new_from_data(data).init())
|
Ok(Self::new_from_data(data).init())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,10 +101,10 @@ async fn file_operations() {
|
|||||||
let explicit_call = ExplicitCall
|
let explicit_call = ExplicitCall
|
||||||
{method_pointer,positional_arguments_expressions,this_argument_expression};
|
{method_pointer,positional_arguments_expressions,this_argument_expression};
|
||||||
let stack_item = StackItem::ExplicitCall(explicit_call);
|
let stack_item = StackItem::ExplicitCall(explicit_call);
|
||||||
let response = client.push_execution_context(execution_context_id,stack_item).await;
|
let response = client.push_to_execution_context(execution_context_id,stack_item).await;
|
||||||
response.expect("Couldn't push execution context.");
|
response.expect("Couldn't push execution context.");
|
||||||
|
|
||||||
let response = client.pop_execution_context(execution_context_id).await;
|
let response = client.pop_from_execution_context(execution_context_id).await;
|
||||||
response.expect("Couldn't pop execution context.");
|
response.expect("Couldn't pop execution context.");
|
||||||
|
|
||||||
let visualisation_id = uuid::Uuid::new_v4();
|
let visualisation_id = uuid::Uuid::new_v4();
|
||||||
|
Loading…
Reference in New Issue
Block a user