Skip to content
This repository has been archived by the owner on Dec 28, 2021. It is now read-only.

Commit

Permalink
Opening Projects in IDE (#1587)
Browse files Browse the repository at this point in the history
  • Loading branch information
farmaazon committed Jun 1, 2021
1 parent 1f0cdbc commit 8f21727
Show file tree
Hide file tree
Showing 10 changed files with 296 additions and 121 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

#### Visual Environment

- [Opening projects in application graphical interface][1587]. Press `cmd`+`o`
to bring the list of projects. Select a project on the list to open it.

#### EnsoGL (rendering engine)

<br/>![Bug Fixes](/docs/assets/tags/bug_fixes.svg)
Expand All @@ -16,6 +19,7 @@
#### Enso Compiler

[1577]: https://github.com/enso-org/ide/pull/1577
[1587]: https://github.com/enso-org/ide/pull/1587

# Enso 2.0.0-alpha.5 (2021-05-14)

Expand Down
1 change: 1 addition & 0 deletions docs/product/shortcuts.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ further investigation.
| -------- | ------ |
| <kbd>cmd</kbd>+<kbd>alt</kbd>+<kbd>shift</kbd>+<kbd>t</kbd> | Toggle light/dark application style. Currently doesn't work properly, as the Theme Switcher is not created yet. (https://github.com/enso-org/ide/issues/795)|
| <kbd>ctrl</kbd>+<kbd>`</kbd> | Show Code Editor. Please note that the Code Editor implementation is in a very early stage and you should not use it. Even just openning it can cause errors in the IDE. Do not try using the graph editor while having the code editor tab openned. |
| <kbd>cmd</kbd>+<kbd>o</kbd> | Open project |
| <kbd>meta</kbd>+<kbd>s</kbd> | Save module |
| <kbd>cmd</kbd>+<kbd>q</kbd> | Close the application (MacOS) |
| <kbd>ctrl</kbd>+<kbd>q</kbd> | Close the application (Linux) |
Expand Down
18 changes: 9 additions & 9 deletions src/rust/ide/lib/enso-protocol/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,20 @@ pub mod handler;

#[allow(missing_docs)]
pub mod prelude {
pub use enso_prelude::*;
pub use json_rpc::prelude::*;
pub use utils::fail::FallibleResult;
pub use uuid::Uuid;

pub use crate::traits::*;
pub use enso_logger::*;
pub use enso_logger::DefaultWarningLogger as Logger;
pub use crate::traits::*;

pub use std::future::Future;
pub use enso_logger::DefaultWarningLogger as Logger;
/// We always use local futures in our single-threaded environment
pub use futures::future::LocalBoxFuture as BoxFuture;
pub use futures::FutureExt;
pub use futures::Stream;
pub use futures::StreamExt;
pub use utils::fail::FallibleResult;
pub use uuid::Uuid;
pub use std::future::Future;

/// We want most our futures to be static. Otherwise, they would automatically inherit
/// lifetime of the client, which is not the desired behavior.
Expand All @@ -43,9 +46,6 @@ pub mod prelude {
/// We want all our streams to be static. Otherwise, the would automatically inherit
/// lifetime of the client, which is not the desired behavior.
pub type StaticBoxStream<T> = futures::stream::LocalBoxStream<'static,T>;
pub use futures::FutureExt;
pub use futures::Stream;
pub use futures::StreamExt;

#[cfg(test)] pub use utils::test::traits::*;
}
Expand Down
17 changes: 16 additions & 1 deletion src/rust/ide/src/controller/ide.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use crate::prelude::*;

use crate::notification;

pub use enso_protocol::project_manager::ProjectMetadata;
pub use enso_protocol::project_manager::ProjectName;
use mockall::automock;
use parser::Parser;

Expand Down Expand Up @@ -90,7 +92,9 @@ impl StatusNotificationPublisher {
#[derive(Copy,Clone,Debug)]
pub enum Notification {
/// User created a new project. The new project is opened in IDE.
NewProjectCreated
NewProjectCreated,
/// User opened an existing project.
ProjectOpened,
}


Expand All @@ -99,6 +103,8 @@ pub enum Notification {
// === API ===
// ===========

// === Managing API ===

/// The API of all project management operations.
///
/// It is a separate trait, because those methods are not supported in some environments (see also
Expand All @@ -107,8 +113,17 @@ pub trait ManagingProjectAPI {

/// Create a new unnamed project and open it in the IDE.
fn create_new_project(&self) -> BoxFuture<FallibleResult>;

/// Return a list of existing projects.
fn list_projects(&self) -> BoxFuture<FallibleResult<Vec<ProjectMetadata>>>;

/// Open the project with given id and name.
fn open_project(&self, id:Uuid, name:ProjectName) -> BoxFuture<FallibleResult>;
}


// === Main API ===

/// The API of IDE Controller.
#[automock]
pub trait API:Debug {
Expand Down
19 changes: 19 additions & 0 deletions src/rust/ide/src/controller/ide/desktop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use crate::notification;
use enso_protocol::project_manager;
use enso_protocol::project_manager::MissingComponentAction;
use enso_protocol::project_manager::ProjectName;
use enso_protocol::project_manager::ProjectMetadata;
use parser::Parser;


Expand Down Expand Up @@ -110,4 +111,22 @@ impl ManagingProjectAPI for Handle {
Ok(())
}.boxed_local()
}

fn list_projects(&self) -> BoxFuture<FallibleResult<Vec<ProjectMetadata>>> {
async move {
let pm_response = self.project_manager.list_projects(&None).await?;
Ok(pm_response.projects)
}.boxed_local()
}

fn open_project(&self, id: Uuid, name: ProjectName) -> BoxFuture<FallibleResult> {
async move {
let logger = &self.logger;
let project_mgr = self.project_manager.clone_ref();
let new_project = model::project::Synchronized::new_opened(logger,project_mgr,id,name);
self.current_project.set(new_project.await?);
executor::global::spawn(self.notifications.publish(Notification::ProjectOpened));
Ok(())
}.boxed_local()
}
}
108 changes: 83 additions & 25 deletions src/rust/ide/src/controller/searcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ pub struct NotASuggestion {

#[allow(missing_docs)]
#[derive(Debug,Fail)]
#[fail(display="An action \"{}\" is not supported (...)", action_label)]
#[fail(display="An action \"{}\" is not supported: {}", action_label, reason)]
pub struct NotSupported {
action_label : String,
reason : failure::Error,
Expand All @@ -72,6 +72,13 @@ pub struct NotSupported {
#[fail(display="An action cannot be executed when searcher is in \"edit node\" mode.")]
pub struct CannotExecuteWhenEditingNode;

#[allow(missing_docs)]
#[derive(Copy,Clone,Debug,Fail)]
#[fail(display="Cannot commit expression in current mode ({:?}).", mode)]
pub struct CannotCommitExpression {
mode : Mode
}


// =====================
// === Notifications ===
Expand Down Expand Up @@ -359,6 +366,8 @@ pub enum Mode {
NewNode {position:Option<Position>},
/// Searcher should edit existing node's expression.
EditNode {node_id:ast::Id},
/// Searcher should show only projects to open.
OpenProject,
}

/// A fragment filled by single picked suggestion.
Expand Down Expand Up @@ -509,7 +518,10 @@ impl Searcher {
language_server : project.json_rpc(),
position_in_code : Immutable(position),
};
ret.reload_list();
match mode {
Mode::OpenProject => ret.load_project_list(),
_ => ret.reload_list(),
}
Ok(ret)
}

Expand All @@ -535,7 +547,12 @@ impl Searcher {

self.data.borrow_mut().input = parsed_input;
self.invalidate_fragments_added_by_picking();
if old_expr != new_expr {
let expression_changed = old_expr != new_expr;
// We don't do the reloading in OpenProject mode: the reloading is meant for providing new
// code suggestions for arguments of picked method. But in OpenProject mode the input is
// for filtering only.
let mode_with_reloading = !matches!(*self.mode, Mode::OpenProject);
if expression_changed && mode_with_reloading {
debug!(self.logger, "Reloading list.");
self.reload_list();
} else if let Actions::Loaded {list} = self.data.borrow().actions.clone_ref() {
Expand Down Expand Up @@ -625,23 +642,29 @@ impl Searcher {
Mode::NewNode {position} => self.add_example(&example,position).map(Some),
_ => Err(CannotExecuteWhenEditingNode.into())
}
Action::CreateNewProject => {
Action::ProjectManagement(action) => {
match self.ide.manage_projects() {
Ok(_) => {
let ide = self.ide.clone_ref();
let logger = self.logger.clone_ref();
executor::global::spawn(async move {
// We checked that manage_projects returns Some just a moment ago, so
// unwrapping is safe.
let result = ide.manage_projects().unwrap().create_new_project().await;
if let Err(err) = result {
let manage_projects = ide.manage_projects().unwrap();
let result = match action {
action::ProjectManagement::CreateNewProject =>
manage_projects.create_new_project(),
action::ProjectManagement::OpenProject {id,name} =>
manage_projects.open_project(*id,name.to_string().into()),
};
if let Err(err) = result.await {
error!(logger, "Error when creating new project: {err}");
}
});
Ok(None)
},
Err(err) => Err(NotSupported{
action_label : Action::CreateNewProject.to_string(),
action_label : Action::ProjectManagement(action).to_string(),
reason : err,
}.into())
}
Expand Down Expand Up @@ -680,39 +703,45 @@ impl Searcher {
/// expression, otherwise a new node is added. This will also add all imports required by
/// picked suggestions.
pub fn commit_node(&self) -> FallibleResult<ast::Id> {
let input_chain = self.data.borrow().input.as_prefix_chain(self.ide.parser());

let expression = match (self.this_var(),input_chain) {
(Some(this_var),Some(input)) =>
apply_this_argument(this_var,&input.wrapped.into_ast()).repr(),
(None,Some(input)) => input.wrapped.into_ast().repr(),
(_,None) => "".to_owned(),
let expr_and_method = || {
let input_chain = self.data.borrow().input.as_prefix_chain(self.ide.parser());

let expression = match (self.this_var(),input_chain) {
(Some(this_var),Some(input)) =>
apply_this_argument(this_var,&input.wrapped.into_ast()).repr(),
(None,Some(input)) => input.wrapped.into_ast().repr(),
(_,None) => "".to_owned(),
};
let intended_method = self.intended_method();
(expression,intended_method)
};
let intended_method = self.intended_method();

// We add the required imports before we create the node/edit its content. This way, we
// avoid an intermediate state where imports would already be in use but not yet available.
self.add_required_imports()?;
let id = match *self.mode {
match *self.mode {
Mode::NewNode {position} => {
let mut new_node = NewNodeInfo::new_pushed_back(expression);
new_node.metadata = Some(NodeMetadata {position,intended_method});
new_node.introduce_pattern = ASSIGN_NAMES_FOR_NODES;
self.add_required_imports()?;
let (expression,intended_method) = expr_and_method();
let mut new_node = NewNodeInfo::new_pushed_back(expression);
new_node.metadata = Some(NodeMetadata {position,intended_method});
new_node.introduce_pattern = ASSIGN_NAMES_FOR_NODES;
let graph = self.graph.graph();
if let Some(this) = self.this_arg.deref().as_ref() {
this.introduce_pattern(graph.clone_ref())?;
}
graph.add_node(new_node)?
graph.add_node(new_node)
},
Mode::EditNode {node_id} => {
self.add_required_imports()?;
let (expression,intended_method) = expr_and_method();
self.graph.graph().set_expression(node_id,expression)?;
self.graph.graph().module.with_node_metadata(node_id,Box::new(|md| {
md.intended_method = intended_method
}))?;
node_id
Ok(node_id)
}
};
Ok(id)
mode => Err(CannotCommitExpression{mode}.into())
}
}

/// Adds an example to the graph.
Expand Down Expand Up @@ -874,7 +903,8 @@ impl Searcher {
actions.extend(self.database.iterate_examples().map(Action::Example));
Self::add_enso_project_entries(&actions)?;
if self.ide.manage_projects().is_ok() {
actions.extend(std::iter::once(Action::CreateNewProject));
let create_project = action::ProjectManagement::CreateNewProject;
actions.extend(std::iter::once(Action::ProjectManagement(create_project)));
}
}
for response in completion_responses {
Expand Down Expand Up @@ -991,6 +1021,34 @@ impl Searcher {
}
Ok(())
}

fn load_project_list(&self) {
let logger = self.logger.clone_ref();
let ide = self.ide.clone_ref();
let data = self.data.clone_ref();
executor::global::spawn(async move {
let result:FallibleResult<Vec<Action>> = async {
let manage_projects = ide.manage_projects()?;
let projects = manage_projects.list_projects().await?;
Ok(projects.into_iter().map(|project| {
let id = Immutable(project.id);
let name = project.name.0.into();
let pm_action = action::ProjectManagement::OpenProject {id,name};
Action::ProjectManagement(pm_action)
}).collect_vec())
}.await;
let actions = match result {
Ok(actions) => actions,
Err(err) => {
error!(logger,"Cannot load projects list: {err}");
default()
}
};
info!(logger,"Received list of projects: {actions:?}");
let list = Rc::new(action::List::from_actions(actions));
data.borrow_mut().actions = Actions::Loaded {list}
})
}
}

/// A simple function call is an AST where function is a single identifier with optional
Expand Down
22 changes: 18 additions & 4 deletions src/rust/ide/src/controller/searcher/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@ pub type Suggestion = Rc<model::suggestion_database::Entry>;
/// Action of adding example code.
pub type Example = Rc<model::suggestion_database::Example>;

/// A variants of project management actions. See also [`Action`].
#[allow(missing_docs)]
#[derive(Clone,CloneRef,Debug,Eq,PartialEq)]
pub enum ProjectManagement {
CreateNewProject,
OpenProject {
id : Immutable<Uuid>,
name : ImString,
}
}

/// A single action on the Searcher list. See also `controller::searcher::Searcher` docs.
#[derive(Clone,CloneRef,Debug,Eq,PartialEq)]
pub enum Action {
Expand All @@ -23,9 +34,9 @@ pub enum Action {
/// Add to the current module a new function with example code, and a new node in
/// current scene calling that function.
Example(Example),
/// Create a new untitled project and open it.
CreateNewProject,
// In the future, other action types will be added (like project/module management, etc.).
/// The project management operation: creating or opening, projects.
ProjectManagement(ProjectManagement),
// In the future, other action types will be added (like module/method management, etc.).
}

impl Display for Action {
Expand All @@ -42,7 +53,10 @@ impl Display for Action {
write!(f, "{}", completion.name.clone())
}
Self::Example(example) => write!(f,"Example: {}", example.name),
Self::CreateNewProject => write!(f,"New Project"),
Self::ProjectManagement(action) => match action {
ProjectManagement::CreateNewProject => write!(f,"New Project"),
ProjectManagement::OpenProject {name,..} => write!(f,"{}", name),
}
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/rust/ide/src/ide/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ impl Integration {
executor::global::spawn(stream.for_each(move |notification| {
if let Some(model) = weak.upgrade() {
match notification {
controller::ide::Notification::NewProjectCreated => {
controller::ide::Notification::NewProjectCreated |
controller::ide::Notification::ProjectOpened => {
model.setup_and_display_new_project()
}
}
Expand Down

0 comments on commit 8f21727

Please sign in to comment.