mirror of
https://github.com/enso-org/enso.git
synced 2024-12-28 03:01:43 +03:00
Searcher Controller (https://github.com/enso-org/ide/pull/654)
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:
parent
48b8fea226
commit
820f2c9553
@ -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).`)
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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)
|
35
gui/docs/product/searcher.md
Normal file
35
gui/docs/product/searcher.md
Normal 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.
|
@ -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.
|
||||
|
@ -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;
|
||||
|
||||
|
||||
|
||||
|
266
gui/src/rust/ide/src/controller/searcher.rs
Normal file
266
gui/src/rust/ide/src/controller/searcher.rs
Normal 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));
|
||||
}
|
||||
}
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
});
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user