Searcher Controller has access to Executed Graph (https://github.com/enso-org/ide/pull/687)

Co-authored-by: Adam Obuchowicz <adam.obuchowicz@enso.org>

Original commit: 5ced6ee5bc
This commit is contained in:
Michał Wawrzyniec Urbańczyk 2020-07-23 03:12:16 +02:00 committed by GitHub
parent 568fa31b49
commit 9c52ff9db6
13 changed files with 153 additions and 62 deletions

View File

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

View File

@ -401,10 +401,11 @@ impl EndpointInfo {
/// Handle providing graph controller interface. /// Handle providing graph controller interface.
#[derive(Clone,CloneRef,Debug)] #[derive(Clone,CloneRef,Debug)]
pub struct Handle { pub struct Handle {
/// Identifier of the graph accessed through this controller.
pub id : Rc<Id>,
/// Model of the module which this graph belongs to. /// Model of the module which this graph belongs to.
pub module : model::Module, pub module : model::Module,
parser : Parser, parser : Parser,
id : Rc<Id>,
logger : Logger, logger : Logger,
} }
@ -764,6 +765,7 @@ pub mod tests {
use ast::test_utils::expect_shape; use ast::test_utils::expect_shape;
use data::text::Index; use data::text::Index;
use data::text::TextChange; use data::text::TextChange;
use enso_protocol::language_server::MethodPointer;
use parser::Parser; use parser::Parser;
use utils::test::ExpectTuple; use utils::test::ExpectTuple;
use wasm_bindgen_test::wasm_bindgen_test; use wasm_bindgen_test::wasm_bindgen_test;
@ -782,19 +784,20 @@ pub mod tests {
/// node. /// node.
pub fn new() -> Self { pub fn new() -> Self {
MockData { MockData {
module_path : model::module::Path::from_mock_module_name("Main"), module_path : crate::test::mock::data::module_path(),
graph_id : Id::new_plain_name("main"), graph_id : crate::test::mock::data::graph_id(),
project_name : "MockProject".to_string(), project_name : crate::test::mock::data::PROJECT_NAME.to_owned(),
code : "main = 2 + 2".to_string(), code : crate::test::mock::data::CODE.to_owned(),
} }
} }
/// Creates a mock data with the main function being an inline definition. /// Creates a mock data with the main function being an inline definition.
/// ///
/// The single node's expression is taken as the argument. /// The single node's expression is taken as the argument.
pub fn new_inline(main_body:impl Into<String>) -> Self { pub fn new_inline(main_body:impl AsRef<str>) -> Self {
let definition_name = crate::test::mock::data::DEFINITION_NAME;
MockData { MockData {
code : format!("main = {}", main_body.into()), code : format!("{} = {}",definition_name,main_body.as_ref()),
..Self::new() ..Self::new()
} }
} }
@ -815,6 +818,10 @@ pub mod tests {
let id = self.graph_id.clone(); let id = self.graph_id.clone();
Handle::new(logger,module,parser,id).unwrap() Handle::new(logger,module,parser,id).unwrap()
} }
pub fn method(&self) -> MethodPointer {
self.module_path.method_pointer(self.graph_id.to_string())
}
} }
impl Default for MockData { impl Default for MockData {

View File

@ -199,7 +199,7 @@ impl Handle {
// ============ // ============
#[cfg(test)] #[cfg(test)]
mod tests { pub mod tests {
use super::*; use super::*;
use crate::executor::test_utils::TestWithLocalPoolExecutor; use crate::executor::test_utils::TestWithLocalPoolExecutor;
@ -211,6 +211,30 @@ mod tests {
wasm_bindgen_test_configure!(run_in_browser); wasm_bindgen_test_configure!(run_in_browser);
#[derive(Debug,Default)]
pub struct MockData {
pub graph : controller::graph::tests::MockData,
pub module : model::module::test::MockData,
pub ctx : model::execution_context::plain::test::MockData,
}
impl MockData {
pub fn controller(&self) -> Handle {
let parser = parser::Parser::new_or_panic();
let module = self.module.plain(&parser);
let method = self.graph.method();
let mut project = model::project::MockAPI::new();
let ctx = Rc::new(self.ctx.create());
model::project::test::expect_parser(&mut project,&parser);
model::project::test::expect_module(&mut project,module);
model::project::test::expect_execution_ctx(&mut project,ctx);
let project = Rc::new(project);
Handle::new(Logger::default(),project.clone_ref(),method).boxed_local().expect_ok()
}
}
// Test that checks that value computed notification is properly relayed by the executed graph. // Test that checks that value computed notification is properly relayed by the executed graph.
#[wasm_bindgen_test] #[wasm_bindgen_test]
fn dispatching_value_computed_notification() { fn dispatching_value_computed_notification() {

View File

@ -225,8 +225,7 @@ pub struct Searcher {
logger : Logger, logger : Logger,
data : Rc<RefCell<Data>>, data : Rc<RefCell<Data>>,
notifier : notification::Publisher<Notification>, notifier : notification::Publisher<Notification>,
module : model::module::Path, graph : controller::ExecutedGraph,
position : Immutable<TextLocation>,
database : Rc<model::SuggestionDatabase>, database : Rc<model::SuggestionDatabase>,
language_server : Rc<language_server::Connection>, language_server : Rc<language_server::Connection>,
parser : Parser, parser : Parser,
@ -234,16 +233,22 @@ pub struct Searcher {
impl Searcher { impl Searcher {
/// Create new Searcher Controller. /// Create new Searcher Controller.
pub fn new pub async fn new
( parent : impl AnyLogger ( parent : impl AnyLogger
, project : &model::Project , project : &model::Project
, module : model::module::Path , method : language_server::MethodPointer
, position : TextLocation ) -> FallibleResult<Self> {
) -> Self { let graph = controller::ExecutedGraph::new(&parent,project.clone_ref(),method).await?;
Ok(Self::new_from_graph_controller(parent,project,graph))
}
/// Create new Searcher Controller, when you have Executed Graph Controller handy.
pub fn new_from_graph_controller
(parent:impl AnyLogger, project:&model::Project, graph:controller::ExecutedGraph)
-> Self {
let logger = Logger::sub(parent,"Searcher Controller");
let this = Self { let this = Self {
module, logger,graph,
position : Immutable(position),
logger : Logger::sub(parent,"Searcher Controller"),
data : default(), data : default(),
notifier : default(), notifier : default(),
database : project.suggestion_db(), database : project.suggestion_db(),
@ -332,7 +337,9 @@ impl Searcher {
fn reload_list(&self) { fn reload_list(&self) {
let next_completion = self.data.borrow().input.next_completion_id(); let next_completion = self.data.borrow().input.next_completion_id();
let new_suggestions = if next_completion == CompletedFragmentId::Function { let new_suggestions = if next_completion == CompletedFragmentId::Function {
self.get_suggestion_list_from_engine(None,None); if let Err(err) = self.get_suggestion_list_from_engine(None,None) {
error!(self.logger,"Cannot request engine for suggestions: {err}");
}
Suggestions::Loading Suggestions::Loading
} else { } else {
// TODO[ao] Requesting for argument. // TODO[ao] Requesting for argument.
@ -342,16 +349,21 @@ impl Searcher {
} }
fn get_suggestion_list_from_engine fn get_suggestion_list_from_engine
(&self, return_type:Option<String>, tags:Option<Vec<language_server::SuggestionEntryType>>) { (&self, return_type:Option<String>, tags:Option<Vec<language_server::SuggestionEntryType>>)
let ls = &self.language_server; -> FallibleResult<()> {
let module = self.module.file_path(); let ls = &self.language_server;
let self_type = None; let graph = self.graph.graph();
let position = self.position.deref().into(); let graph_id = &*graph.id;
let request = ls.completion(module,&position,&self_type,&return_type,&tags); let module_ast = graph.module.ast();
let data = self.data.clone_ref(); let file = graph.module.path().file_path();
let database = self.database.clone_ref(); let self_type = None;
let logger = self.logger.clone_ref(); let def_span = double_representation::module::definition_span(&module_ast,&graph_id)?;
let notifier = self.notifier.clone_ref(); let position = TextLocation::convert_span(module_ast.repr(),&def_span).end.into();
let request = ls.completion(&file,&position,&self_type,&return_type,&tags);
let data = self.data.clone_ref();
let database = self.database.clone_ref();
let logger = self.logger.clone_ref();
let notifier = self.notifier.clone_ref();
executor::global::spawn(async move { executor::global::spawn(async move {
info!(logger,"Requesting new suggestion list."); info!(logger,"Requesting new suggestion list.");
let response = request.await; let response = request.await;
@ -374,10 +386,12 @@ impl Searcher {
data.borrow_mut().suggestions = new_suggestions; data.borrow_mut().suggestions = new_suggestions;
notifier.publish(Notification::NewSuggestionList).await; notifier.publish(Notification::NewSuggestionList).await;
}); });
Ok(())
} }
} }
// ============= // =============
// === Tests === // === Tests ===
// ============= // =============
@ -387,8 +401,8 @@ mod test {
use super::*; use super::*;
use crate::executor::test_utils::TestWithLocalPoolExecutor; use crate::executor::test_utils::TestWithLocalPoolExecutor;
use crate::model::module::Path;
use controller::graph::executed::tests::MockData as ExecutedGraphMockData;
use json_rpc::expect_call; use json_rpc::expect_call;
use utils::test::traits::*; use utils::test::traits::*;
@ -400,16 +414,16 @@ mod test {
} }
impl Fixture { impl Fixture {
fn new(client_setup:impl FnOnce(&mut language_server::MockClient)) -> Self { fn new_custom<F>(client_setup:F) -> Self
let mut client = language_server::MockClient::default(); where F : FnOnce(&mut ExecutedGraphMockData,&mut language_server::MockClient) {
let module_path = Path::from_mock_module_name("Test"); let mut graph_data = controller::graph::executed::tests::MockData::default();
client_setup(&mut client); let mut client = language_server::MockClient::default();
client_setup(&mut graph_data,&mut client);
let searcher = Searcher { let searcher = Searcher {
logger : default(), logger : default(),
data : default(), data : default(),
notifier : default(), notifier : default(),
module : module_path, graph : graph_data.controller(),
position : Immutable(TextLocation::at_document_begin()),
database : default(), database : default(),
language_server : language_server::Connection::new_mock_rc(client), language_server : language_server::Connection::new_mock_rc(client),
parser : Parser::new_or_panic(), parser : Parser::new_or_panic(),
@ -438,20 +452,27 @@ mod test {
let entry9 = searcher.database.get(9).unwrap(); let entry9 = searcher.database.get(9).unwrap();
Fixture{searcher,entry1,entry2,entry9} Fixture{searcher,entry1,entry2,entry9}
} }
fn new() -> Self {
Self::new_custom(|_,_| {})
}
} }
#[wasm_bindgen_test] #[wasm_bindgen_test]
fn loading_list() { fn loading_list() {
let mut test = TestWithLocalPoolExecutor::set_up(); let mut test = TestWithLocalPoolExecutor::set_up();
let Fixture{searcher,entry1,entry9,..} = Fixture::new(|client| { let Fixture{searcher,entry1,entry9,..} = Fixture::new_custom(|data,client| {
let completion_response = language_server::response::Completion { let completion_response = language_server::response::Completion {
results: vec![1,5,9], results: vec![1,5,9],
current_version: default(), current_version: default(),
}; };
let file_end = data.module.code.chars().count();
let expected_location = TextLocation {line:0, column:file_end};
expect_call!(client.completion( expect_call!(client.completion(
module = Path::from_mock_module_name("Test").file_path().clone(), module = data.module.path.file_path().clone(),
position = TextLocation::at_document_begin().into(), position = expected_location.into(),
self_type = None, self_type = None,
return_type = None, return_type = None,
tag = None tag = None
@ -540,7 +561,7 @@ mod test {
#[wasm_bindgen_test] #[wasm_bindgen_test]
fn picked_completions_list_maintaining() { fn picked_completions_list_maintaining() {
let Fixture{searcher,entry1,entry2,..} = Fixture::new(|_|{}); let Fixture{searcher,entry1,entry2,..} = Fixture::new();
let frags_borrow = || Ref::map(searcher.data.borrow(),|d| &d.fragments_added_by_picking); let frags_borrow = || Ref::map(searcher.data.borrow(),|d| &d.fragments_added_by_picking);
// Picking first suggestion. // Picking first suggestion.

View File

@ -90,6 +90,8 @@ impl TestWithLocalPoolExecutor {
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.expect_finished(); if !std::thread::panicking() {
self.expect_finished();
}
} }
} }

View File

@ -21,15 +21,16 @@
#![warn(missing_debug_implementations)] #![warn(missing_debug_implementations)]
pub mod config; pub mod config;
pub mod constants;
pub mod controller; pub mod controller;
pub mod double_representation; pub mod double_representation;
pub mod executor; pub mod executor;
pub mod ide;
pub mod model; pub mod model;
pub mod notification; pub mod notification;
pub mod test;
pub mod transport; pub mod transport;
pub mod view; pub mod view;
pub mod constants;
pub mod ide;
pub use crate::ide::IdeInitializer; pub use crate::ide::IdeInitializer;

View File

@ -184,7 +184,6 @@ pub mod test {
use super::*; use super::*;
use crate::double_representation::definition::DefinitionName; use crate::double_representation::definition::DefinitionName;
use crate::constants::DEFAULT_PROJECT_NAME;
#[derive(Clone,Derivative)] #[derive(Clone,Derivative)]
#[derivative(Debug)] #[derivative(Debug)]
@ -205,9 +204,9 @@ pub mod test {
pub fn new() -> MockData { pub fn new() -> MockData {
MockData { MockData {
context_id : model::execution_context::Id::new_v4(), context_id : model::execution_context::Id::new_v4(),
module_path : model::module::Path::from_mock_module_name("Test"), module_path : crate::test::mock::data::module_path(),
root_definition : DefinitionName::new_plain("main"), root_definition : crate::test::mock::data::definition_name(),
project_name : DEFAULT_PROJECT_NAME.to_string(), project_name : crate::test::mock::data::PROJECT_NAME.to_owned(),
} }
} }

View File

@ -378,8 +378,8 @@ pub mod test {
impl Default for MockData { impl Default for MockData {
fn default() -> Self { fn default() -> Self {
Self { Self {
path : Path::from_mock_module_name("Main"), path : crate::test::mock::data::module_path(),
code : "main = 2 + 2".into(), code : crate::test::mock::data::CODE.to_owned(),
id_map : default(), id_map : default(),
metadata : default(), metadata : default(),
} }

View File

@ -80,6 +80,8 @@ pub type Synchronized = synchronized::Project;
pub mod test { pub mod test {
use super::*; use super::*;
use futures::future::ready;
/// Sets up parser expectation on the mock project. /// Sets up parser expectation on the mock project.
pub fn expect_parser(project:&mut MockAPI, parser:&Parser) { pub fn expect_parser(project:&mut MockAPI, parser:&Parser) {
let parser = parser.clone_ref(); let parser = parser.clone_ref();
@ -91,6 +93,14 @@ pub mod test {
let module_path = module.path().clone_ref(); let module_path = module.path().clone_ref();
project.expect_module() project.expect_module()
.withf_st (move |path| path == &module_path) .withf_st (move |path| path == &module_path)
.returning_st(move |_path| futures::future::ready(Ok(module.clone_ref())).boxed_local()); .returning_st(move |_path| ready(Ok(module.clone_ref())).boxed_local());
}
/// Sets up module expectation on the mock project, returning a give module.
pub fn expect_execution_ctx(project:&mut MockAPI, ctx:model::ExecutionContext) {
let ctx2 = ctx.clone_ref();
project.expect_create_execution_context()
.withf_st (move |root_definition| root_definition == &ctx.current_method())
.returning_st(move |_root_definition| ready(Ok(ctx2.clone_ref())).boxed_local());
} }
} }

View File

@ -0,0 +1,30 @@
//! Module for support code for writing tests.
//! Utilities for mocking IDE components.
#[cfg(test)]
pub mod mock {
//! Data used to create mock IDE components.
//!
//! Contains a number of constants and functions building more complex structures from them.
//! The purpose is to allow different parts of tests that mock different models using
//! consistent data.
#[allow(missing_docs)]
pub mod data {
pub const PROJECT_NAME : &str = "MockProject";
pub const MODULE_NAME : &str = "Mock_Module";
pub const CODE : &str = "main = 2 + 2";
pub const DEFINITION_NAME : &str = "main";
pub fn module_path() -> crate::model::module::Path {
crate::model::module::Path::from_mock_module_name(MODULE_NAME)
}
pub fn definition_name() -> crate::double_representation::definition::DefinitionName {
crate::double_representation::definition::DefinitionName::new_plain(DEFINITION_NAME)
}
pub fn graph_id() -> crate::double_representation::graph::Id {
crate::double_representation::graph::Id::new_plain_name(DEFINITION_NAME)
}
}
}

View File

@ -822,11 +822,6 @@ impl NodeEditor {
info!(self.logger, "Initialized."); info!(self.logger, "Initialized.");
Ok(self) Ok(self)
} }
/// The path to the module, which graph is currently displayed.
pub fn displayed_module(&self) -> model::module::Path {
self.graph.model.controller.graph().module.path().clone_ref()
}
} }
impl display::Object for NodeEditor { impl display::Object for NodeEditor {

View File

@ -9,7 +9,6 @@ use crate::model::module::NodeMetadata;
use crate::model::module::Position; use crate::model::module::Position;
use crate::view::node_editor::NodeEditor; use crate::view::node_editor::NodeEditor;
use data::text::TextLocation;
use ensogl::data::color; use ensogl::data::color;
use ensogl::display; use ensogl::display;
use ensogl::display::Scene; use ensogl::display::Scene;
@ -89,12 +88,8 @@ impl NodeSearcher {
self.display_object.add_child(&self.text_field.display_object()); self.display_object.add_child(&self.text_field.display_object());
self.text_field.clear_content(); self.text_field.clear_content();
self.text_field.set_focus(); self.text_field.set_focus();
let module = self.node_editor.displayed_module(); let graph = self.node_editor.graph.controller().clone_ref();
//TODO[ao]: Now we use some predefined location, until this task will be done: let controller = controller::Searcher::new_from_graph_controller(&self.logger,&self.project,graph);
// https://github.com/enso-org/ide/issues/653 . This code should be replaced with
// the proper Searcher view integration anyway.
let position = TextLocation { line:2, column:4 };
let controller = controller::Searcher::new(&self.logger,&self.project,module,position);
let logger = self.logger.clone_ref(); let logger = self.logger.clone_ref();
let weak = Rc::downgrade(&self.controller); let weak = Rc::downgrade(&self.controller);
executor::global::spawn(controller.subscribe().for_each(move |notification| { executor::global::spawn(controller.subscribe().for_each(move |notification| {

View File

@ -396,6 +396,13 @@ impl TextLocation {
Self::from_index(content,range.start)..Self::from_index(content,range.end) Self::from_index(content,range.start)..Self::from_index(content,range.end)
} }
/// Converts a span into a range of TextLocation. It iterates over all characters before range's
/// end.
pub fn convert_span(content:impl Str, span:&Span) -> Range<Self> {
let range = span.index..span.end();
Self::convert_range(content,&range)
}
/// Converts a range in bytes into a range of TextLocation. It iterates over all characters /// Converts a range in bytes into a range of TextLocation. It iterates over all characters
/// before range's end. /// before range's end.
pub fn convert_byte_range(content:impl Str, range:&Range<ByteIndex>) -> Range<Self> { pub fn convert_byte_range(content:impl Str, range:&Range<ByteIndex>) -> Range<Self> {