Currently it loads and provides an initial list of suggestions. Additionally there is a raw integration with view: then the current Searcher is put into the screen, it logs all the suggestions retrieved from the Engine.

Original commit: d719cf0e56
This commit is contained in:
Adam Obuchowicz 2020-07-16 09:50:31 +02:00 committed by GitHub
parent 48b8fea226
commit 820f2c9553
12 changed files with 596 additions and 131 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.54
let limit = 3.55
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

@ -6,46 +6,55 @@ tags: [summary,contributing]
---
# Development & Contributing Guide
Thank you for your interest in contributing to the Enso IDE! We believe that only through community
involvement can Enso be the best it can be! There are a whole host of ways to contribute, and every
single one is appreciated.
Thank you for your interest in contributing to the Enso IDE! We believe that
only through community involvement can Enso be the best it can be! There are
a whole host of ways to contribute, and every single one is appreciated.
<br/>
## Reporting Issues
**If you are concerned that your bug publicly presents a security risk to the users of Enso, please
contact [security@enso.org](mailto:security@enso.org).**
**If you are concerned that your bug publicly presents a security risk to
the users of Enso, please contact
[security@enso.org](mailto:security@enso.org).**
While it's never great to find a bug, they are a reality of software and software development! We
can't fix or improve on the things that we don't know about, so report as many bugs as you can! If
you're not sure whether something is a bug, file it anyway!
While it's never great to find a bug, they are a reality of software and
software development! We can't fix or improve on the things that we don't
know about, so report as many bugs as you can! If you're not sure whether
something is a bug, file it anyway!
Even though GitHub search can be a bit hard to use sometimes, we'd appreciate if you could
[search](https://github.com/luna/enso/search?q=&type=Issues&utf8=%E2%9C%93) for your issue before
filing a bug as it's possible that someone else has already reported the issue. We know the search
isn't the best, and it can be hard to know what to search for, so we really don't mind if you _do_
submit a duplicate!
Even though GitHub search can be a bit hard to use sometimes, we'd
appreciate if you could
[search](https://github.com/luna/enso/search?q=&type=Issues&utf8=%E2%9C%93)
for your issue before filing a bug as it's possible that someone else has
already reported the issue. We know the search isn't the best, and it can be
hard to know what to search for, so we really don't mind if you _do_ submit
a duplicate!
Opening an issue is as easy as following [this link](https://github.com/luna/ide/issues/new?template=bug-report.md)
and filling out the fields. The template is intended to collect all the information we need to best
diagnose the issue, so please take the time to fill it out accurately.
Opening an issue is as easy as following
[this link](https://github.com/luna/ide/issues/new?template=bug-report.md)
and filling out the fields. The template is intended to collect all the
information we need to best diagnose the issue, so please take the time to
fill it out accurately.
The reproduction steps are particularly important, as the more easily we can reproduce it, the
faster we can fix the bug! It's also helpful to have the version of the IDE, as that will let us
know if the bug is Operating System or Architecture specific.
The reproduction steps are particularly important, as the more easily we can
reproduce it, the faster we can fix the bug! It's also helpful to have the
version of the IDE, as that will let us know if the bug is Operating System
or Architecture specific.
<br/>
## Development Environment
The project builds on MacOS, Windows, and Linux. Cross-platform targets work well on all of these
platforms, however, MacOS package will miss the right application icon if built on Linux or Windows
due to non-trival icon generation on these platforms. In order to develop the source code you will
The project builds on MacOS, Windows, and Linux. Cross-platform targets work
well on all of these platforms, however, MacOS package will miss the right
application icon if built on Linux or Windows due to non-trival icon
generation on these platforms. In order to develop the source code you will
need the following setup:
- **The Rust Toolchain (nightly-2019-11-04)**
This project uses several features available only in the nightly Rust toolchain. Please use the
This project uses several features available only in the nightly Rust
toolchain. Please use the
[the Rust toolchain installer](https://rustup.rs) to install it:
```bash
@ -59,52 +68,59 @@ need the following setup:
- **Node and Node Package Manager LTS**
In order to build the web and desktop applications you will need
[the latest LTS version of node and npm](https://nodejs.org/en/download). Even minor release
changes are known to cause serious issues, thus **we provide support for the latest LTS version
only. Please do not report build issues if you use other versions.** In case you run run MacOS or
[the latest LTS version of node and npm](https://nodejs.org/en/download). Even
minor release changes are known to cause serious issues, thus **we provide
support for the latest LTS version only. Please do not report build issues
if you use other versions.** In case you run run MacOS or
Linux the easiest way to set up the proper version is by installing the
[Node Version Manager](https://github.com/nvm-sh/nvm) and running
`nvm install --lts && nvm use --lts`.
- **(Optional) FlatBuffer compiler `flatc`**
This dependency is needed only if you need to update files generated by the FlatBuffer from the
Engine Services binary protocol description. Otherwise, relying on the generated files that are
being stored in this repository is fine.
This dependency is needed only if you need to update files generated by
the FlatBuffer from the Engine Services binary protocol description.
Otherwise, relying on the generated files that are being stored in this
repository is fine.
`flatc` must be in version *newer than 1.12* due to
[this bug](https://github.com/google/flatbuffers/issues/5055). As of writing this text there
are no official releases with this issue fixed, however current binaries can be obtained from the
project's CI
[build artifacts](https://github.com/google/flatbuffers/actions?query=branch%3Amaster). `flatc`
builds from 8 May 2020 onwards have been confirmed to work.
[this bug](https://github.com/google/flatbuffers/issues/5055). As of
writing this text there are no official releases with this issue fixed,
however current binaries can be obtained from the project's CI
[build artifacts](https://github.com/google/flatbuffers/actions?query=branch%3Amaster).
`flatc` builds from 8 May 2020 onwards have been confirmed to work.
After placing `flatc` in `PATH` you need to define the `ENSO_IDE_ENABLE_FLATC` environment
variable to explicitly enable regeneration of the interface files. The `flatc` is run as part of
After placing `flatc` in `PATH` you need to define the
`ENSO_IDE_ENABLE_FLATC` environment variable to explicitly enable
regeneration of the interface files. The `flatc` is run as part of
`build.rs` script of the `enso-protocol package.
<br/>
## Working with sources
Please note that you should not use a code auto-formatter in this codebase. Please read the
following documents to learn more about reasons behind this decision and the recommended code style
guide. Be sure to carefully read the
Please note that you should not use a code auto-formatter in this codebase.
Please read the following documents to learn more about reasons behind this
decision and the recommended code style guide. Be sure to carefully read the
[Rust style guide 1](./contributing/style-guide.md)
and the [Rust style guide 2](https://dev.enso.org/docs/style-guide/rust.html)
before contributing to the codebase.
### Setting up Engine Services
IDE requires a service named Project Picker to be running in the background on the local machine.
The service, being part of the Enso Engine, currently must be built from the sources on the [enso
repository](https://github.com/luna/enso). If the service is not running, the IDE will not start.
IDE requires a service named Project Picker to be running in the background
on the local machine. The service, being part of the Enso Engine, currently
must be built from the sources on the
[enso repository](https://github.com/luna/enso). If the service is not running,
the IDE will not start.
However, it is possible to hack on many components of the IDE without the service. The debug scenes
and tests will work even without the Project Picker.
However, it is possible to hack on many components of the IDE without the
service. The debug scenes and tests will work even without the Project Picker.
To run Project Picker, it must be first built — please follow the Enso Engine [contributing
guidelines](https://github.com/luna/enso/blob/main/docs/CONTRIBUTING.md) to obtain sources and
necessary tools. When they are in place, the service can be built and started by issuing the
following command in the [enso repository](https://github.com/luna/enso) root:
To run Project Picker, it must be first built — please follow the Enso
Engine
[contributing guidelines](https://github.com/luna/enso/blob/main/docs/CONTRIBUTING.md)
to obtain sources and necessary tools. When they are in place, the service
can be built and started by issuing the following command in the [enso
repository](https://github.com/luna/enso) root:
```
sbt -java-home $JAVA_HOME -J-Xss10M project-manager/run
```
@ -116,53 +132,61 @@ In future significant improvements to this process are planned, specifically:
* providing self-contained Project Picker service packages.
### Development
As this is a multi-part project with many complex dependencies, it is equipped with a build script
which both validates your working environment as well as takes care of providing the most suitable
compilation flags for a particular development stage. In order to run the build script simply run
`node ./run` in the root of the codebase. On MacOS and Linux you can use a simpler form of `./run`,
however, this doc will use the former form in order to stay cross-platform compatible. Run
`node ./run help` to learn about available commands and options. All arguments provided after the
`--` symbol will be passed to sub-commands. For example `node ./run build -- --dev` will pass the
`--dev` flag to `cargo` (Rust build tool). The most common options are presented below:
As this is a multi-part project with many complex dependencies, it is
equipped with a build script which both validates your working environment as
well as takes care of providing the most suitable compilation flags for a
particular development stage. In order to run the build script simply run
`node ./run` in the root of the codebase. On MacOS and Linux you can use a
simpler form of `./run`, however, this doc will use the former form in order
to stay cross-platform compatible. Run `node ./run help` to learn about
available commands and options. All arguments provided after the `--` symbol
will be passed to sub-commands. For example `node ./run build -- --dev` will
pass the `--dev` flag to `cargo` (Rust build tool). The most common options
are presented below:
- **Interactive mode**
Run `node ./run watch` to start a local web-server and a source-file watch utility which will
build the project on every change. Open `http://localhost:8080` (the port may vary and will be
reported in the terminal if `8080` was already in use) to run the application, or
`http://localhost:8080/debug` to open example demo scenes. Please remember to disable the cache in
your browser during the development! By default, the script disables heavyweight optimizations to
provide interactive development experience. The scripts are thin wrappers for
Run `node ./run watch` to start a local web-server and a source-file watch
utility which will build the project on every change. Open
`http://localhost:8080` (the port may vary and will be reported in the
terminal if `8080` was already in use) to run the application, or
`http://localhost:8080/debug` to open example demo scenes. Please remember
to disable the cache in your browser during the development! By default,
the script disables heavyweight optimizations to provide interactive
development experience. The scripts are thin wrappers for
[wasm-pack](https://github.com/rustwasm/wasm-pack) and accept the same
[command line arguments](https://rustwasm.github.io/wasm-pack/book/commands/build.html).
- **Production mode**
In order to compile in a production mode (enable all optimizations, strip WASM debug symbols,
minimize the output binaries, etc.), run `node ./run build`. To create platform-specific packages
and installers use `node ./run dist` instead. The final packages will be located at
In order to compile in a production mode (enable all optimizations, strip
WASM debug symbols, minimize the output binaries, etc.), run
`node ./run build`. To create platform-specific packages and installers use
`node ./run dist` instead. The final packages will be located at
`app/dist/native`.
### Testing, Linting, and Validation
After changing the code it's always a good idea to lint and test the code. We have prepared several
scripts which maximally automate the process:
After changing the code it's always a good idea to lint and test the code. We
have prepared several scripts which maximally automate the process:
- **Size Validation**
Use `node ./run check-size` to check if the size of the final binary did not grew too much in
comparison to the previous release. Watching the resulting binary size is one of the most
important responsibility of each contributor in order to keep the project small and suitable for
web-based usage.
Use `node ./run check-size` to check if the size of the final binary did
not grew too much in comparison to the previous release. Watching the
resulting binary size is one of the most important responsibility of each
contributor in order to keep the project small and suitable for web-based
usage.
- **Testing**
For the test suite to run you need a current version of Chrome installed.
Use `node ./run test` run both unit and web-based visual test.
- *Note for Windows users*:
there is a [known issue with wasm-pack](https://github.com/rustwasm/wasm-pack/issues/611) using the
wrong version of the chrome driver. There is
[a workaround](https://github.com/rustwasm/wasm-pack/issues/611#issuecomment-522093207) described in
the issue: download compatible ChromeDriver from the
[official source](https://chromedriver.chromium.org/downloads) and ensure it is in your `PATH`.
there is a [known issue with wasm-pack](https://github.com/rustwasm/wasm-pack/issues/611)
using the wrong version of the chrome driver. There is
[a workaround](https://github.com/rustwasm/wasm-pack/issues/611#issuecomment-522093207)
described in the issue: download compatible ChromeDriver from the
[official source](https://chromedriver.chromium.org/downloads) and ensure it
is in your `PATH`.
- **Linting**
Please be sure to fix all errors reported by `node ./run lint` before creating a pull request to
this repository.
Please be sure to fix all errors reported by `node ./run lint` before
creating a pull request to this repository.

View File

@ -7,6 +7,7 @@ tags: [doc-index]
# Enso IDE documentation
* [**Contributing guidelines**](./contributing/README.md) - helpful instructions for anyone who
wants to contribute.
* [**Product specification**](./product/README.md) - a specification from the user perspective.
* [**Contributing guidelines**](./contributing/README.md) - helpful
instructions for anyone who wants to contribute.
* [**Product specification**](./product/README.md) - a specification from
the user perspective.

View File

@ -6,8 +6,10 @@ tags: [product]
# Enso IDE Product Documentation
This section contains detailed specification of Enso IDE from the user perspective. The
implementation is documented in rust code and in the crate's `docs` directory.
This section contains detailed specification of Enso IDE from the user
perspective. The implementation is documented in rust code and in the crate's
`docs` directory.
* [**List of Shortcuts**](./shortcuts.md)
* [**Visualizations**](visualizations.md)
* [**Visualizations**](./visualizations.md)
* [**Searcher in Graph Editor**](./searcher.md)

View File

@ -0,0 +1,35 @@
---
layout: developer-doc
title: Searcher Panel In Graph Editor
category: product
tags: [product]
---
# Searcher Panel In Graph Editor
### Behaviour
The Searcher Panel can be brought to the screen in two different ways:
* when user starts to edit node's expression - the node becomes Searcher input
and panel appears below,
* when user press tab with mouse over Graph Editor Panel - the new Searcher
input appears with Searcher panel below. Additionally, if there is exactly
one node selected, the connection is displayed between the selected node
and the Searcher input.
TODO: Add more description when visual part will be implemented.
### Suggestion of Node Expressions
The suggestion list is obtained from the Engine using `search/completion` method
of Language Server. The parameters of the call depend on the Current searcher
input, suggestions picked suggestions so far, and if we are adding a node
connected to selection. The current implementation can be found in
`ide::controller::searcher` module.
### Suggestion Database
The `search/completion` method of Language Server returns a list of keys to
the Suggestion Database instead of the whole entries. The database is
retrieved by IDE on Project opening, and keeps it up to date. The Database
is implemented in `ide::model::suggestion_database` module.

View File

@ -7,33 +7,37 @@
## Crates
The crates structure is currently in process of refactoring, and there are still places where
code does not fit the crate it is placed. The work is tracked in #448 epic.
The crates structure is currently in process of refactoring, and there are still
places where code does not fit the crate it is placed. The work is tracked in
#448 epic.
The detailed description of each crate is placed in its documentation at top of its `lib.rs` file.
The detailed description of each crate is placed in its documentation at top
of its `lib.rs` file.
There are two main parts of our codebase: **EnsoGL library** and the **Enso IDE application**. The
crates used in common by those parts are put in `src/rust/lib`, and should be documented at the
top of their `lib.rs` file.
There are two main parts of our codebase: **EnsoGL library** and the **Enso
IDE application**. The crates used in common by those parts are put in `src
/rust/lib`, and should be documented at the top of their `lib.rs` file.
Other crates usually contains code shared by those two. The one noteworthy is the **prelude** crate
(`src/rust/prelude`) which gathers the commonly used imports across all crates in our repository.
Other crates usually contains code shared by those two. The one noteworthy
is the **prelude** crate (`src/rust/prelude`) which gathers the commonly used
imports across all crates in our repository.
### EnsoGL (`src/rust/ensogl`)
EnsoGL is a library providing fast 2D vector rendering engine with a rich set of
primitives and high-level GUI components. Its structure will be described after the planned
refactoring in #598.
primitives and high-level GUI components. Its structure will be described after
the planned refactoring in #598.
### Enso IDE (`src/rust/ide`)
The Enso IDE crate contains entry point function of application, and integrates two main parts
of it:
* IDE View (`src/rust/ide/lib/view`) - The visual part of IDE. It contains no logic and
connections to backend - instead it delivers FRP endpoints to be integrated with controllers. That
design allow us to measure of vis-part only performance.
* IDE Controllers (`src/rust/ide/lib/controller`) - The logic part of IDE, that is
synchronizing graph a text and communication with Engine. It should not
know anything about visual part. In the future we may consider creating some sort of CLI for
automatic integration tests.
The Enso IDE crate contains entry point function of application, and
integrates two main parts of it:
* IDE View (`src/rust/ide/lib/view`) - The visual part of IDE. It contains
no logic and connections to backend - instead it delivers FRP endpoints to
be integrated with controllers. That design allow us to measure of vis-part
only performance.
* IDE Controllers (`src/rust/ide/lib/controller`) - The logic part of IDE,
that is synchronizing graph a text and communication with Engine. It
should not know anything about visual part. In the future we may consider
creating some sort of CLI for automatic integration tests.

View File

@ -19,12 +19,14 @@ pub mod graph;
pub mod module;
pub mod text;
pub mod visualization;
pub mod searcher;
pub use graph::Handle as Graph;
pub use graph::executed::Handle as ExecutedGraph;
pub use module::Handle as Module;
pub use text::Handle as Text;
pub use visualization::Handle as Visualization;
pub use searcher::Searcher;

View File

@ -0,0 +1,266 @@
//! This module contains all structures related to Searcher Controller.
use crate::prelude::*;
use crate::notification;
use data::text::TextLocation;
use enso_protocol::language_server;
use flo_stream::Subscriber;
// =======================
// === Suggestion List ===
// =======================
/// A single suggestion on the Searcher suggestion list.
#[derive(Clone,CloneRef,Debug,Eq,PartialEq)]
pub enum Suggestion {
/// Suggestion for input completion: possible functions, arguments, etc.
Completion(Rc<model::suggestion_database::Entry>)
// In future, other suggestion types will be added (like suggestions of actions, etc.).
}
/// List of suggestions available in Searcher.
#[derive(Clone,CloneRef,Debug)]
pub enum Suggestions {
/// The suggestion list is still loading from the Language Server.
Loading,
/// The suggestion list is loaded.
#[allow(missing_docs)]
Loaded {
list : Rc<Vec<Suggestion>>
},
/// Loading suggestion list resulted in error.
Error(Rc<failure::Error>)
}
impl Suggestions {
/// Check if suggestion list is still loading.
pub fn is_loading(&self) -> bool {
match self {
Self::Loading => true,
_ => false,
}
}
/// Check if retrieving suggestion list was unsuccessful
pub fn is_error(&self) -> bool {
match self {
Self::Error(_) => true,
_ => false,
}
}
/// Get the list of suggestions. Returns None if still loading or error was returned.
pub fn list(&self) -> Option<&Vec<Suggestion>> {
match self {
Self::Loaded {list} => Some(list),
_ => None,
}
}
}
impl Default for Suggestions {
fn default() -> Self {
Self::Loading
}
}
// =====================
// === Notifications ===
// =====================
/// The notification emitted by Searcher Controller
#[derive(Copy,Clone,Debug,Eq,PartialEq)]
pub enum Notification {
/// A new Suggestion list is available.
NewSuggestionList
}
// ===========================
// === Searcher Controller ===
// ===========================
/// A controller state. Currently it caches the currently kept suggestions list and the current
/// searcher input.
#[derive(Clone,Debug,Default)]
struct Data {
current_input : String,
current_list : Suggestions,
}
/// Searcher Controller.
///
/// This is an object providing all required functionalities for Searcher View: mainly it is the
/// suggestion list to display depending on the searcher input, and actions of picking one or
/// accepting the Searcher input (pressing "Enter").
#[derive(Clone,CloneRef,Debug)]
pub struct Searcher {
logger : Logger,
data : Rc<RefCell<Data>>,
notifier : notification::Publisher<Notification>,
module : Rc<model::module::QualifiedName>,
position : Immutable<TextLocation>,
database : Rc<model::SuggestionDatabase>,
language_server : Rc<language_server::Connection>,
}
impl Searcher {
/// Create new Searcher Controller.
pub fn new
( parent : impl AnyLogger
, project : &model::Project
, module : model::module::Path
, position : TextLocation
) -> Self {
let this = Self {
position : Immutable(position),
logger : Logger::sub(parent,"Searcher Controller"),
data : default(),
notifier : default(),
module : Rc::new(project.qualified_module_name(&module)),
database : project.suggestion_db.clone_ref(),
language_server : project.language_server_rpc.clone_ref(),
};
this.reload_list();
this
}
/// Subscribe to controller's notifications.
pub fn subscribe(&self) -> Subscriber<Notification> {
self.notifier.subscribe()
}
/// Get the current suggestion list.
pub fn suggestions(&self) -> Suggestions {
self.data.borrow().current_list.clone_ref()
}
/// Set the Searcher Input.
///
/// This function should be called each time user modifies Searcher input in view. It may result
/// in a new suggestion list (the aprriopriate notification will be emitted).
pub fn set_input(&self, new_input:String) {
self.data.borrow_mut().current_input = new_input;
//TODO[ao] here goes refreshing suggestion list after input change.
}
/// Reload Suggestion List.
///
/// The current list will be set as "Loading" and Language Server will be requested for a new
/// list - once it be retrieved, the new list will be set and notification will be emitted.
fn reload_list(&self) {
let module = self.module.as_ref();
let self_type = None;
let return_type = None;
let tags = None;
let position = self.position.deref().into();
let request = self.language_server.completion(module,&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();
self.data.borrow_mut().current_list = Suggestions::Loading;
executor::global::spawn(async move {
info!(logger,"Requesting new suggestion list.");
let ls_response = request.await;
info!(logger,"Received suggestions from Language Server.");
let new_list = match ls_response {
Ok(list) => {
let entry_ids = list.results.into_iter();
let entries = entry_ids.filter_map(|id| {
let entry = database.get(id);
if entry.is_none() {
error!(logger,"Missing entry {id} in Suggestion Database.");
}
entry
});
let suggestions = entries.map(Suggestion::Completion);
Suggestions::Loaded {list:Rc::new(suggestions.collect())}
},
Err(error) => Suggestions::Error(Rc::new(error.into()))
};
data.borrow_mut().current_list = new_list;
notifier.publish(Notification::NewSuggestionList).await;
});
}
}
// =============
// === Tests ===
// =============
#[cfg(test)]
mod test {
use super::*;
use crate::executor::test_utils::TestWithLocalPoolExecutor;
use crate::model::module::Path;
use json_rpc::expect_call;
use utils::test::traits::*;
#[test]
fn reloading_list() {
let mut test = TestWithLocalPoolExecutor::set_up();
let client = language_server::MockClient::default();
let module_path = Path::from_mock_module_name("Test");
let completion_response = language_server::response::Completion {
results: vec![1,5,9],
current_version: default(),
};
expect_call!(client.completion(
module = "Test.Test".to_string(),
position = TextLocation::at_document_begin().into(),
self_type = None,
return_type = None,
tag = None
) => Ok(completion_response));
let searcher = Searcher {
logger : default(),
data : default(),
notifier : default(),
module : Rc::new(module_path.qualified_module_name("Test")),
position : Immutable(TextLocation::at_document_begin()),
database : default(),
language_server : language_server::Connection::new_mock_rc(client),
};
let entry1 = model::suggestion_database::Entry {
name : "TestFunction1".to_string(),
kind : model::suggestion_database::EntryKind::Function,
module : "Test.Test".to_string(),
arguments : vec![],
return_type : "Number".to_string(),
documentation : default(),
self_type : None
};
let entry2 = model::suggestion_database::Entry {
name : "TestFunction2".to_string(),
..entry1.clone()
};
searcher.database.put_entry(1,entry1);
let entry1 = searcher.database.get(1).unwrap();
searcher.database.put_entry(9,entry2);
let entry2 = searcher.database.get(9).unwrap();
let mut subscriber = searcher.subscribe();
searcher.reload_list();
assert!(searcher.suggestions().is_loading());
test.run_until_stalled();
let expected_list = vec![Suggestion::Completion(entry1),Suggestion::Completion(entry2)];
assert_eq!(searcher.suggestions().list(), Some(&expected_list));
let notification = subscriber.next().boxed_local().expect_ready();
assert_eq!(notification, Some(Notification::NewSuggestionList));
}
}

View File

@ -1,17 +1,92 @@
//! The module contains all structures for representing suggestions and their database.
//!
use crate::prelude::*;
use enso_protocol::language_server;
use language_server::types::SuggestionsDatabaseVersion;
use language_server::types::SuggestionDatabaseUpdateEvent;
pub use language_server::types::SuggestionEntry as Entry;
pub use language_server::types::SuggestionEntryArgument as Argument;
pub use language_server::types::SuggestionEntryId as EntryId;
pub use language_server::types::SuggestionsDatabaseUpdate as Update;
// =============
// === Entry ===
// =============
/// A type of suggestion entry.
#[derive(Copy,Clone,Debug,Eq,PartialEq)]
#[allow(missing_docs)]
pub enum EntryKind {
Atom,Function,Local,Method
}
/// The Suggestion Database Entry.
#[derive(Clone,Debug,Eq,PartialEq)]
pub struct Entry {
/// A name of suggested object.
pub name : String,
/// A type of suggestion.
pub kind : EntryKind,
/// A module where the suggested object is defined.
pub module : String,
/// Argument lists of suggested object (atom or function). If the object does not take any
/// arguments, the list is empty.
pub arguments : Vec<Argument>,
/// A type returned by the suggested object.
pub return_type : String,
/// A documentation associated with object.
pub documentation : Option<String>,
/// A type of the "self" argument. This field is `None` for non-method suggestions.
pub self_type : Option<String>,
}
impl Entry {
/// Create entry from the structure deserialized from the Language Server responses.
pub fn from_ls_entry(entry:language_server::types::SuggestionEntry) -> Self {
use language_server::types::SuggestionEntry::*;
match entry {
SuggestionEntryAtom {name,module,arguments,return_type,documentation} =>
Self {
name,module,arguments,return_type,documentation,
self_type : None,
kind : EntryKind::Atom,
},
SuggestionEntryMethod {name,module,arguments,self_type,return_type,documentation} =>
Self {
name,module,arguments,return_type,documentation,
self_type : Some(self_type),
kind : EntryKind::Method,
},
SuggestionEntryFunction {name,module,arguments,return_type,..} =>
Self {
name,module,arguments,return_type,
self_type : None,
documentation : default(),
kind : EntryKind::Function,
},
SuggestionEntryLocal {name,module,return_type,..} =>
Self {
name,module,return_type,
arguments : default(),
self_type : None,
documentation : default(),
kind : EntryKind::Local,
},
}
}
}
impl From<language_server::types::SuggestionEntry> for Entry {
fn from(entry:language_server::types::SuggestionEntry) -> Self {
Self::from_ls_entry(entry)
}
}
// ================
// === Database ===
// ================
@ -33,14 +108,14 @@ impl SuggestionDatabase {
pub async fn create_synchronized
(language_server:&language_server::Connection) -> FallibleResult<Self> {
let response = language_server.client.get_suggestions_database().await?;
Ok(Self::new_from_ls_response(response))
Ok(Self::from_ls_response(response))
}
/// Create a new database model from response received from the Language Server.
fn new_from_ls_response(response:language_server::response::GetSuggestionDatabase) -> Self {
fn from_ls_response(response:language_server::response::GetSuggestionDatabase) -> Self {
let mut entries = HashMap::new();
for entry in response.entries {
entries.insert(entry.id, Rc::new(entry.suggestion));
entries.insert(entry.id, Rc::new(Entry::from_ls_entry(entry.suggestion)));
}
Self {
entries : RefCell::new(entries),
@ -56,13 +131,27 @@ impl SuggestionDatabase {
/// Apply the update event to the database.
pub fn apply_update_event(&self, event:SuggestionDatabaseUpdateEvent) {
for update in event.updates {
let mut entries = self.entries.borrow_mut();
match update {
Update::Add {id,entry} => self.entries.borrow_mut().insert(id,Rc::new(entry)),
Update::Remove {id} => self.entries.borrow_mut().remove(&id),
Update::Add {id,entry} => entries.insert(id,Rc::new(entry.into())),
Update::Remove {id} => entries.remove(&id),
};
}
self.version.set(event.current_version);
}
/// Put the entry to the database. Using this function likely break the synchronization between
/// Language Server and IDE, and should be used only in tests.
#[cfg(test)]
pub fn put_entry(&self, id:EntryId, entry:Entry) {
self.entries.borrow_mut().insert(id,Rc::new(entry));
}
}
impl From<language_server::response::GetSuggestionDatabase> for SuggestionDatabase {
fn from(database:language_server::response::GetSuggestionDatabase) -> Self {
Self::from_ls_response(database)
}
}
@ -83,12 +172,12 @@ mod test {
entries : vec![],
current_version : 123
};
let db = SuggestionDatabase::new_from_ls_response(response);
let db = SuggestionDatabase::from_ls_response(response);
assert!(db.entries.borrow().is_empty());
assert_eq!(db.version.get() , 123);
// Non-empty db
let entry = Entry::SuggestionEntryAtom {
let entry = language_server::types::SuggestionEntry::SuggestionEntryAtom {
name : "TextAtom".to_string(),
module : "TestModule".to_string(),
arguments : vec![],
@ -100,29 +189,29 @@ mod test {
entries : vec![db_entry],
current_version : 456
};
let db = SuggestionDatabase::new_from_ls_response(response);
let db = SuggestionDatabase::from_ls_response(response);
assert_eq!(db.entries.borrow().len(), 1);
assert_eq!(*db.get(12).unwrap().name(), "TextAtom".to_string());
assert_eq!(*db.get(12).unwrap().name, "TextAtom".to_string());
assert_eq!(db.version.get(), 456);
}
#[test]
fn applying_update() {
let entry1 = Entry::SuggestionEntryAtom {
let entry1 = language_server::types::SuggestionEntry::SuggestionEntryAtom {
name : "Entry1".to_string(),
module : "TestModule".to_string(),
arguments : vec![],
return_type : "TestAtom".to_string(),
documentation : None
};
let entry2 = Entry::SuggestionEntryAtom {
let entry2 = language_server::types::SuggestionEntry::SuggestionEntryAtom {
name : "Entry2".to_string(),
module : "TestModule".to_string(),
arguments : vec![],
return_type : "TestAtom".to_string(),
documentation : None
};
let new_entry2 = Entry::SuggestionEntryAtom {
let new_entry2 = language_server::types::SuggestionEntry::SuggestionEntryAtom {
name : "NewEntry2".to_string(),
module : "TestModule".to_string(),
arguments : vec![],
@ -136,7 +225,7 @@ mod test {
entries : vec![db_entry1,db_entry2],
current_version : 1,
};
let db = SuggestionDatabase::new_from_ls_response(initial_response);
let db = SuggestionDatabase::from_ls_response(initial_response);
// Remove
let remove_update = Update::Remove {id:2};
@ -155,7 +244,7 @@ mod test {
current_version : 3,
};
db.apply_update_event(update);
assert_eq!(db.get(2).unwrap().name(), "NewEntry2");
assert_eq!(db.version.get(), 3 );
assert_eq!(db.get(2).unwrap().name, "NewEntry2");
assert_eq!(db.version.get(), 3 );
}
}

View File

@ -106,10 +106,10 @@ impl ViewLayout {
let logger = Logger::sub(logger,"ViewLayout");
let world = &application.display;
let text_editor = TextEditor::new(&logger,world,text_controller,kb_actions,fonts);
let node_editor = NodeEditor::new(&logger,application,graph_controller,project,
visualization_controller);
let node_editor = NodeEditor::new(&logger,application,graph_controller,
project.clone_ref(),visualization_controller);
let node_editor = node_editor.await?;
let node_searcher = NodeSearcher::new(world,&logger,node_editor.clone_ref(),fonts);
let node_searcher = NodeSearcher::new(world,&logger,node_editor.clone_ref(),project,fonts);
world.add_child(&text_editor.display_object());
world.add_child(&node_editor);
world.add_child(&node_searcher);
@ -133,6 +133,7 @@ impl ViewLayout {
let position = *layout.mouse_position_sampler.value();
//TODO[dg]: Test it when graph scene panning is working.
let node_searcher_position = Vector3::new(position.x,position.y,0.0);
layout.node_searcher.hide();
layout.node_searcher.set_position(node_searcher_position);
layout.node_searcher.show();
});

View File

@ -779,6 +779,11 @@ 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,6 +9,7 @@ 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::shape::text::glyph::font;
use ensogl::display::shape::text::text_field::TextField;
@ -22,6 +23,8 @@ use ensogl::traits::*;
pub struct NodeSearcher {
display_object : display::object::Instance,
node_editor : NodeEditor,
project : Rc<model::Project>,
controller : Rc<CloneCell<Option<controller::Searcher>>>,
text_field : TextField,
logger : Logger,
}
@ -31,6 +34,7 @@ impl NodeSearcher {
( world : &World
, logger : impl AnyLogger
, node_editor : NodeEditor
, project : Rc<model::Project>
, fonts : &mut font::Registry)
-> Self {
let scene = world.scene();
@ -45,8 +49,9 @@ impl NodeSearcher {
size : Vector2::new(screen.width,16.0),
};
let text_field = TextField::new(world,properties);
display_object.add_child(&text_field.display_object());
let searcher = NodeSearcher{node_editor,display_object,text_field,logger};
let controller = default();
let searcher = NodeSearcher{node_editor,display_object,project,controller,text_field,
logger};
searcher.initialize()
}
@ -77,15 +82,46 @@ impl NodeSearcher {
/// Show NodeSearcher if it is invisible.
pub fn show(&mut self) {
self.display_object.add_child(&self.text_field.display_object());
self.text_field.clear_content();
self.text_field.set_focus();
if !self.is_shown() {
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 logger = self.logger.clone_ref();
let weak = Rc::downgrade(&self.controller);
executor::global::spawn(controller.subscribe().for_each(move |notification| {
if let Some(opt_controller) = weak.upgrade() {
if let Some(controller) = opt_controller.get() {
match notification {
controller::searcher::Notification::NewSuggestionList => {
let list = controller.suggestions();
info!(logger,"New list in Searcher: {list:?}");
}
}
}
}
futures::future::ready(())
}));
self.controller.set(Some(controller))
}
}
/// Hide NodeSearcher if it is visible.
pub fn hide(&mut self) {
self.text_field.clear_content();
self.display_object.remove_child(&self.text_field.display_object());
if self.is_shown() {
self.text_field.clear_content();
self.controller.set(None);
self.display_object.remove_child(&self.text_field.display_object());
}
}
pub fn is_shown(&self) -> bool {
self.text_field.display_object().has_parent()
}
}