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.')
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
if (size > limit) {
throw(`Output file size exceeds the limit (${size}MB > ${limit}MB).`)

View File

@ -401,10 +401,11 @@ impl EndpointInfo {
/// Handle providing graph controller interface.
#[derive(Clone,CloneRef,Debug)]
pub struct Handle {
/// Identifier of the graph accessed through this controller.
pub id : Rc<Id>,
/// Model of the module which this graph belongs to.
pub module : model::Module,
parser : Parser,
id : Rc<Id>,
logger : Logger,
}
@ -764,6 +765,7 @@ pub mod tests {
use ast::test_utils::expect_shape;
use data::text::Index;
use data::text::TextChange;
use enso_protocol::language_server::MethodPointer;
use parser::Parser;
use utils::test::ExpectTuple;
use wasm_bindgen_test::wasm_bindgen_test;
@ -782,19 +784,20 @@ pub mod tests {
/// node.
pub fn new() -> 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 : "main = 2 + 2".to_string(),
module_path : crate::test::mock::data::module_path(),
graph_id : crate::test::mock::data::graph_id(),
project_name : crate::test::mock::data::PROJECT_NAME.to_owned(),
code : crate::test::mock::data::CODE.to_owned(),
}
}
/// 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 Into<String>) -> Self {
pub fn new_inline(main_body:impl AsRef<str>) -> Self {
let definition_name = crate::test::mock::data::DEFINITION_NAME;
MockData {
code : format!("main = {}", main_body.into()),
code : format!("{} = {}",definition_name,main_body.as_ref()),
..Self::new()
}
}
@ -815,6 +818,10 @@ pub mod tests {
let id = self.graph_id.clone();
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 {

View File

@ -199,7 +199,7 @@ impl Handle {
// ============
#[cfg(test)]
mod tests {
pub mod tests {
use super::*;
use crate::executor::test_utils::TestWithLocalPoolExecutor;
@ -211,6 +211,30 @@ mod tests {
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.
#[wasm_bindgen_test]
fn dispatching_value_computed_notification() {

View File

@ -225,8 +225,7 @@ pub struct Searcher {
logger : Logger,
data : Rc<RefCell<Data>>,
notifier : notification::Publisher<Notification>,
module : model::module::Path,
position : Immutable<TextLocation>,
graph : controller::ExecutedGraph,
database : Rc<model::SuggestionDatabase>,
language_server : Rc<language_server::Connection>,
parser : Parser,
@ -234,16 +233,22 @@ pub struct Searcher {
impl Searcher {
/// Create new Searcher Controller.
pub fn new
pub async fn new
( parent : impl AnyLogger
, project : &model::Project
, module : model::module::Path
, position : TextLocation
) -> Self {
, method : language_server::MethodPointer
) -> FallibleResult<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 {
module,
position : Immutable(position),
logger : Logger::sub(parent,"Searcher Controller"),
logger,graph,
data : default(),
notifier : default(),
database : project.suggestion_db(),
@ -332,7 +337,9 @@ impl Searcher {
fn reload_list(&self) {
let next_completion = self.data.borrow().input.next_completion_id();
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
} else {
// TODO[ao] Requesting for argument.
@ -342,12 +349,17 @@ impl Searcher {
}
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>>)
-> FallibleResult<()> {
let ls = &self.language_server;
let module = self.module.file_path();
let graph = self.graph.graph();
let graph_id = &*graph.id;
let module_ast = graph.module.ast();
let file = graph.module.path().file_path();
let self_type = None;
let position = self.position.deref().into();
let request = ls.completion(module,&position,&self_type,&return_type,&tags);
let def_span = double_representation::module::definition_span(&module_ast,&graph_id)?;
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();
@ -374,10 +386,12 @@ impl Searcher {
data.borrow_mut().suggestions = new_suggestions;
notifier.publish(Notification::NewSuggestionList).await;
});
Ok(())
}
}
// =============
// === Tests ===
// =============
@ -387,8 +401,8 @@ mod test {
use super::*;
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 utils::test::traits::*;
@ -400,16 +414,16 @@ mod test {
}
impl Fixture {
fn new(client_setup:impl FnOnce(&mut language_server::MockClient)) -> Self {
fn new_custom<F>(client_setup:F) -> Self
where F : FnOnce(&mut ExecutedGraphMockData,&mut language_server::MockClient) {
let mut graph_data = controller::graph::executed::tests::MockData::default();
let mut client = language_server::MockClient::default();
let module_path = Path::from_mock_module_name("Test");
client_setup(&mut client);
client_setup(&mut graph_data,&mut client);
let searcher = Searcher {
logger : default(),
data : default(),
notifier : default(),
module : module_path,
position : Immutable(TextLocation::at_document_begin()),
graph : graph_data.controller(),
database : default(),
language_server : language_server::Connection::new_mock_rc(client),
parser : Parser::new_or_panic(),
@ -438,20 +452,27 @@ mod test {
let entry9 = searcher.database.get(9).unwrap();
Fixture{searcher,entry1,entry2,entry9}
}
fn new() -> Self {
Self::new_custom(|_,_| {})
}
}
#[wasm_bindgen_test]
fn loading_list() {
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 {
results: vec![1,5,9],
current_version: default(),
};
let file_end = data.module.code.chars().count();
let expected_location = TextLocation {line:0, column:file_end};
expect_call!(client.completion(
module = Path::from_mock_module_name("Test").file_path().clone(),
position = TextLocation::at_document_begin().into(),
module = data.module.path.file_path().clone(),
position = expected_location.into(),
self_type = None,
return_type = None,
tag = None
@ -540,7 +561,7 @@ mod test {
#[wasm_bindgen_test]
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);
// Picking first suggestion.

View File

@ -90,6 +90,8 @@ impl TestWithLocalPoolExecutor {
impl Drop for TestWithLocalPoolExecutor {
fn drop(&mut self) {
// We should be able to finish test.
if !std::thread::panicking() {
self.expect_finished();
}
}
}

View File

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

View File

@ -184,7 +184,6 @@ pub mod test {
use super::*;
use crate::double_representation::definition::DefinitionName;
use crate::constants::DEFAULT_PROJECT_NAME;
#[derive(Clone,Derivative)]
#[derivative(Debug)]
@ -205,9 +204,9 @@ pub mod test {
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(),
module_path : crate::test::mock::data::module_path(),
root_definition : crate::test::mock::data::definition_name(),
project_name : crate::test::mock::data::PROJECT_NAME.to_owned(),
}
}

View File

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

View File

@ -80,6 +80,8 @@ pub type Synchronized = synchronized::Project;
pub mod test {
use super::*;
use futures::future::ready;
/// Sets up parser expectation on the mock project.
pub fn expect_parser(project:&mut MockAPI, parser:&Parser) {
let parser = parser.clone_ref();
@ -91,6 +93,14 @@ pub mod test {
let module_path = module.path().clone_ref();
project.expect_module()
.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.");
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 {

View File

@ -9,7 +9,6 @@ use crate::model::module::NodeMetadata;
use crate::model::module::Position;
use crate::view::node_editor::NodeEditor;
use data::text::TextLocation;
use ensogl::data::color;
use ensogl::display;
use ensogl::display::Scene;
@ -89,12 +88,8 @@ impl NodeSearcher {
self.display_object.add_child(&self.text_field.display_object());
self.text_field.clear_content();
self.text_field.set_focus();
let module = self.node_editor.displayed_module();
//TODO[ao]: Now we use some predefined location, until this task will be done:
// 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 graph = self.node_editor.graph.controller().clone_ref();
let controller = controller::Searcher::new_from_graph_controller(&self.logger,&self.project,graph);
let logger = self.logger.clone_ref();
let weak = Rc::downgrade(&self.controller);
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)
}
/// 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
/// before range's end.
pub fn convert_byte_range(content:impl Str, range:&Range<ByteIndex>) -> Range<Self> {